Saya menyambut Anda, Habravite tersayang! Karena saya telah mengembangkan pada platform e-commerce Magento sejak 2013, setelah mengumpulkan keberanian dan percaya bahwa di bidang ini saya dapat menyebut diri saya, setidaknya, seorang pengembang yang percaya diri, saya memutuskan untuk menulis artikel pertama saya di hub tentang sistem ini. Dan saya akan mulai dengan implementasi REST API di Magento 2. Di sini, di luar kotak ada fungsi untuk memproses permintaan dan saya akan mencoba untuk mendemonstrasikannya menggunakan contoh modul sederhana. Artikel ini lebih ditujukan untuk mereka yang telah bekerja dengan Magenta. Jadi, yang tertarik, tolong, di bawah kucing.
Dasi
Imajinasi saya sangat buruk, jadi saya menghasilkan contoh berikut: bayangkan bahwa kita perlu mengimplementasikan blog, hanya pengguna dari panel admin yang dapat menulis artikel. Dari waktu ke waktu beberapa CRM mengetuk kami dan mengunggah artikel-artikel ini ke dirinya sendiri (mengapa tidak jelas, tapi itulah cara kami akan membenarkan menggunakan REST API). Untuk menyederhanakan modul, saya secara khusus menghilangkan implementasi menampilkan artikel di frontend dan di panel admin (Anda dapat mengimplementasikannya sendiri, saya sarankan
artikel yang bagus di grid). Hanya fungsionalitas pemrosesan permintaan yang akan terpengaruh di sini.
Pengembangan aksi
Pertama, buat
struktur modul, sebut saja
AlexPoletaev_Blog (kurangnya imajinasi belum hilang). Kami menempatkan modul di direktori
aplikasi / kode .
AlexPoletaev / Blog / etc / module.xml<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="AlexPoletaev_Blog" setup_version="1.0.0"/> </config>
AlexPoletaev / Blog / registration.php <?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'AlexPoletaev_Blog', __DIR__ );
Kedua file ini adalah minimum yang diperlukan untuk modul.
Jika semuanya dilakukan dalam Feng Shui, maka kita perlu membuat kontrak layanan (apa itu di dalam Magenta dan cara kerjanya, Anda dapat membaca di
sini dan di
sini ), yang akan kita lakukan:
AlexPoletaev / Blog / Api / Data / PostInterface.php <?php namespace AlexPoletaev\Blog\Api\Data; interface PostInterface { const ID = 'id'; const AUTHOR_ID = 'author_id'; const TITLE = 'title'; const CONTENT = 'content'; const CREATED_AT = 'created_at'; const UPDATED_AT = 'updated_at'; public function getId(); public function setId($id); public function getAuthorId(); public function setAuthorId($authorId); public function getTitle(); public function setTitle(string $title); public function getContent(); public function setContent(string $content); public function getCreatedAt(); public function setCreatedAt(string $createdAt); public function getUpdatedAt(); public function setUpdatedAt(string $updatedAt); }
AlexPoletaev / Blog / Api / PostRepositoryInterface.php <?php namespace AlexPoletaev\Blog\Api; use AlexPoletaev\Blog\Api\Data\PostInterface; use Magento\Framework\Api\SearchCriteriaInterface; interface PostRepositoryInterface { public function get(int $id); public function getList(SearchCriteriaInterface $searchCriteria); public function save(PostInterface $post); public function delete(PostInterface $post); public function deleteById(int $id); }
Mari kita menganalisis dua antarmuka ini lebih terinci. Antarmuka
PostInterface menampilkan tabel dengan artikel dari blog kami. Buat tabel di bawah ini. Setiap kolom dari database harus memiliki pengambil dan penyetel sendiri di antarmuka ini, kami akan mencari tahu mengapa ini penting nanti. Antarmuka
PostRepositoryInterface menyediakan seperangkat metode standar untuk berinteraksi dengan database dan menyimpan entitas yang dimuat dalam cache. Metode yang sama digunakan untuk API. Catatan penting lainnya, keberadaan PHPDocs yang benar di antarmuka ini
diperlukan , karena Magenta, saat memproses permintaan REST, menggunakan refleksi untuk menentukan parameter input dan mengembalikan nilai dalam metode.
Dengan menggunakan
skrip instal, buat tabel tempat posting dari blog akan disimpan:
AlexPoletaev / Blog / Setup / InstallSchema.php <?php namespace AlexPoletaev\Blog\Setup; use AlexPoletaev\Blog\Api\Data\PostInterface; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; use Magento\Security\Setup\InstallSchema as SecurityInstallSchema; class InstallSchema implements InstallSchemaInterface { public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $table = $setup->getConnection() ->newTable( $setup->getTable(PostResource::TABLE_NAME) ) ->addColumn( PostInterface::ID, Table::TYPE_INTEGER, null, ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], 'Post ID' ) ->addColumn( PostInterface::AUTHOR_ID, Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => true,], 'Author ID' ) ->addColumn( PostInterface::TITLE, Table::TYPE_TEXT, 255, [], 'Title' ) ->addColumn( PostInterface::CONTENT, Table::TYPE_TEXT, null, [], 'Content' ) ->addColumn( 'created_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 'Creation Time' ) ->addColumn( 'updated_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE], 'Update Time' ) ->addForeignKey( $setup->getFkName( PostResource::TABLE_NAME, PostInterface::AUTHOR_ID, SecurityInstallSchema::ADMIN_USER_DB_TABLE_NAME, 'user_id' ), PostInterface::AUTHOR_ID, $setup->getTable(SecurityInstallSchema::ADMIN_USER_DB_TABLE_NAME), 'user_id', Table::ACTION_SET_NULL ) ->addIndex( $setup->getIdxName( PostResource::TABLE_NAME, [PostInterface::AUTHOR_ID], AdapterInterface::INDEX_TYPE_INDEX ), [PostInterface::AUTHOR_ID], ['type' => AdapterInterface::INDEX_TYPE_INDEX] ) ->setComment('Posts') ; $setup->getConnection()->createTable($table); $setup->endSetup(); } }
Tabel akan memiliki kolom berikut (jangan lupa, kami memiliki semuanya sesederhana mungkin):
- peningkatan id - otomatis
- author_id - pengidentifikasi pengguna admin (kunci asing pada bidang user_id dari tabel admin_user)
- judul - judul
- konten - teks artikel
- Created_at - tanggal pembuatan
- updated_at - edit tanggal
Sekarang Anda perlu membuat set standar kelas
Model Magenta,
ResourceModel dan
Koleksi . Mengapa saya tidak akan melukis kelas-kelas ini, topik ini luas dan melampaui ruang lingkup artikel ini, yang tertarik, dapat google sendiri. Singkatnya, kelas-kelas ini diperlukan untuk memanipulasi entitas (artikel) dari database. Saya menyarankan Anda untuk membaca tentang Model Domain, Repositori dan pola-pola Lapisan Layanan.
AlexPoletaev / Blog / Model / Post.php <?php namespace AlexPoletaev\Blog\Model; use AlexPoletaev\Blog\Api\Data\PostInterface; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\Model\AbstractModel; class Post extends AbstractModel implements PostInterface { protected $_idFieldName = PostInterface::ID;
AlexPoletaev / Blog / Model / ResourceModel / Post.php <?php namespace AlexPoletaev\Blog\Model\ResourceModel; use AlexPoletaev\Blog\Api\Data\PostInterface; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; class Post extends AbstractDb { const TABLE_NAME = 'alex_poletaev_blog_post'; protected function _construct() //@codingStandardsIgnoreLine { $this->_init(self::TABLE_NAME, PostInterface::ID); } }
AlexPoletaev / Blog / Model / ResourceModel / Post / Collection.php <?php namespace AlexPoletaev\Blog\Model\ResourceModel\Post; use AlexPoletaev\Blog\Model\Post; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; class Collection extends AbstractCollection { protected function _construct() //@codingStandardsIgnoreLine { $this->_init(Post::class, PostResource::class); } }
Pembaca yang penuh perhatian akan melihat bahwa model kami mengimplementasikan antarmuka yang dibuat sebelumnya dan semua getter dan setternya.
Pada saat yang sama, kami menerapkan repositori dan metode-metodenya:
AlexPoletaev / Blog / Model / PostRepository.php <?php namespace AlexPoletaev\Blog\Model; use AlexPoletaev\Blog\Api\Data\PostInterface; use AlexPoletaev\Blog\Api\Data\PostSearchResultInterface; use AlexPoletaev\Blog\Api\Data\PostSearchResultInterfaceFactory; use AlexPoletaev\Blog\Api\PostRepositoryInterface; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use AlexPoletaev\Blog\Model\ResourceModel\Post\Collection as PostCollection; use AlexPoletaev\Blog\Model\ResourceModel\Post\CollectionFactory as PostCollectionFactory; use AlexPoletaev\Blog\Model\PostFactory; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; class PostRepository implements PostRepositoryInterface { private $registry = []; private $postResource; private $postFactory; private $postCollectionFactory; private $postSearchResultFactory; public function __construct( PostResource $postResource, PostFactory $postFactory, PostCollectionFactory $postCollectionFactory, PostSearchResultInterfaceFactory $postSearchResultFactory ) { $this->postResource = $postResource; $this->postFactory = $postFactory; $this->postCollectionFactory = $postCollectionFactory; $this->postSearchResultFactory = $postSearchResultFactory; } public function get(int $id) { if (!array_key_exists($id, $this->registry)) { $post = $this->postFactory->create(); $this->postResource->load($post, $id); if (!$post->getId()) { throw new NoSuchEntityException(__('Requested post does not exist')); } $this->registry[$id] = $post; } return $this->registry[$id]; } public function getList(SearchCriteriaInterface $searchCriteria) { $collection = $this->postCollectionFactory->create(); foreach ($searchCriteria->getFilterGroups() as $filterGroup) { foreach ($filterGroup->getFilters() as $filter) { $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq'; $collection->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); } } $searchResult = $this->postSearchResultFactory->create(); $searchResult->setSearchCriteria($searchCriteria); $searchResult->setItems($collection->getItems()); $searchResult->setTotalCount($collection->getSize()); return $searchResult; } public function save(PostInterface $post) { try { $this->postResource->save($post); $this->registry[$post->getId()] = $this->get($post->getId()); } catch (\Exception $exception) { throw new StateException(__('Unable to save post #%1', $post->getId())); } return $this->registry[$post->getId()]; } public function delete(PostInterface $post) { try { $this->postResource->delete($post); unset($this->registry[$post->getId()]); } catch (\Exception $e) { throw new StateException(__('Unable to remove post #%1', $post->getId())); } return true; } public function deleteById(int $id) { return $this->delete($this->get($id)); } }
Metode
\AlexPoletaev\Blog\Model\PostRepository::getList()
harus mengembalikan data dalam format tertentu, jadi kita juga memerlukan antarmuka ini:
AlexPoletaev / Blog / Api / Data / PostSearchResultInterface.php <?php namespace AlexPoletaev\Blog\Api\Data; use Magento\Framework\Api\SearchResultsInterface; interface PostSearchResultInterface extends SearchResultsInterface { public function getItems(); public function setItems(array $items); }
Untuk membuatnya lebih mudah untuk menguji modul kami, kami akan membuat dua skrip konsol yang menambah dan menghapus data uji dari tabel:
AlexPoletaev / Blog / Console / Command / DeploySampleDataCommand.php <?php namespace AlexPoletaev\Blog\Console\Command; use AlexPoletaev\Blog\Api\PostRepositoryInterface; use AlexPoletaev\Blog\Model\Post; use AlexPoletaev\Blog\Model\PostFactory; use Magento\User\Api\Data\UserInterface; use Magento\User\Model\User; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class DeploySampleDataCommand extends Command { const ARGUMENT_USERNAME = 'username'; const ARGUMENT_NUMBER_OF_RECORDS = 'number_of_records'; private $postFactory; private $postRepository; private $user; public function __construct( PostFactory $postFactory, PostRepositoryInterface $postRepository, UserInterface $user ) { parent::__construct(); $this->postFactory = $postFactory; $this->postRepository = $postRepository; $this->user = $user; } protected function configure() { $this->setName('alex_poletaev:blog:deploy_sample_data') ->setDescription('Blog: deploy sample data') ->setDefinition([ new InputArgument( self::ARGUMENT_USERNAME, InputArgument::REQUIRED, 'Username' ), new InputArgument( self::ARGUMENT_NUMBER_OF_RECORDS, InputArgument::OPTIONAL, 'Number of test records' ), ]) ; parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output) { $username = $input->getArgument(self::ARGUMENT_USERNAME); $user = $this->user->loadByUsername($username); if (!$user->getId() && $output->getVerbosity() > 1) { $output->writeln('<error>User is not found</error>'); return null; } $records = $input->getArgument(self::ARGUMENT_NUMBER_OF_RECORDS) ?: 3; for ($i = 1; $i <= (int)$records; $i++) { $post = $this->postFactory->create(); $post->setAuthorId($user->getId()); $post->setTitle('test title ' . $i); $post->setContent('test content ' . $i); $this->postRepository->save($post); if ($output->getVerbosity() > 1) { $output->writeln('<info>Post with the ID #' . $post->getId() . ' has been created.</info>'); } } } }
AlexPoletaev / Blog / Console / Command / RemoveSampleDataCommand.php <?php namespace AlexPoletaev\Blog\Console\Command; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\App\ResourceConnection; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class RemoveSampleDataCommand extends Command { private $resourceConnection; public function __construct( ResourceConnection $resourceConnection ) { parent::__construct(); $this->resourceConnection = $resourceConnection; } protected function configure() { $this->setName('alex_poletaev:blog:remove_sample_data') ->setDescription('Blog: remove sample data') ; parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output) { $connection = $this->resourceConnection->getConnection(); $connection->truncateTable($connection->getTableName(PostResource::TABLE_NAME)); if ($output->getVerbosity() > 1) { $output->writeln('<info>Sample data has been successfully removed.</info>'); } } }
Fitur utama Magento 2 adalah penggunaan yang luas dari penerapan
Dependency Injection sendiri . Agar Magenta tahu antarmuka mana yang sesuai dengan implementasi, kita perlu menentukan dependensi ini dalam file di.xml. Pada saat yang sama, kami akan mendaftarkan skrip konsol yang baru dibuat dalam file ini:
AlexPoletaev / Blog / etc / di.xml <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="AlexPoletaev\Blog\Api\Data\PostInterface" type="AlexPoletaev\Blog\Model\Post"/> <preference for="AlexPoletaev\Blog\Api\PostRepositoryInterface" type="AlexPoletaev\Blog\Model\PostRepository"/> <preference for="AlexPoletaev\Blog\Api\Data\PostSearchResultInterface" type="Magento\Framework\Api\SearchResults" /> <type name="Magento\Framework\Console\CommandList"> <arguments> <argument name="commands" xsi:type="array"> <item name="deploy_sample_data" xsi:type="object">AlexPoletaev\Blog\Console\Command\DeploySampleDataCommand</item> <item name="remove_sample_data" xsi:type="object">AlexPoletaev\Blog\Console\Command\RemoveSampleDataCommand</item> </argument> </arguments> </type> </config>
Sekarang daftarkan rute untuk REST API, ini dilakukan di file webapi.xml:
AlexPoletaev / Blog / etc / webapi.xml <?xml version="1.0"?> <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd"> <route url="/V1/blog/posts" method="POST"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="save"/> <resources> <resource ref="anonymous"/> </resources> </route> <route url="/V1/blog/posts/:id" method="DELETE"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="deleteById"/> <resources> <resource ref="anonymous"/> </resources> </route> <route url="/V1/blog/posts/:id" method="GET"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="get"/> <resources> <resource ref="anonymous"/> </resources> </route> <route url="/V1/blog/posts" method="GET"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="getList"/> <resources> <resource ref="anonymous"/> </resources> </route> </routes>
Di sini kami memberi tahu Magente antarmuka mana dan metode mana dari antarmuka ini yang digunakan ketika meminta URL tertentu dan dengan metode http tertentu (POST, GET, dll.). Juga, untuk menyederhanakan, sumber daya
anonymous
digunakan, yang memungkinkan siapa pun untuk mengetuk API kami, jika tidak, Anda perlu mengonfigurasi hak akses (ACL).
Klimaks
Semua langkah selanjutnya mengasumsikan bahwa Anda telah mengaktifkan mode pengembang. Ini menghindari manipulasi yang tidak perlu dengan penyebaran statika konten dan kompilasi DI.
Daftarkan modul baru kami, jalankan perintah:
php bin/magento setup:upgrade
.
Periksa bahwa tabel
alex_poletaev_blog_post baru telah
dibuat .
Selanjutnya, muat data uji menggunakan skrip khusus kami:
php bin/magento -v alex_poletaev:blog:deploy_sample_data admin
Parameter
admin dalam skrip ini adalah
nama pengguna dari tabel
admin_user (mungkin berbeda untuk Anda), dengan kata lain, pengguna dari panel admin, yang akan ditulis dalam kolom author_id.
Sekarang Anda dapat memulai pengujian. Untuk tes, saya menggunakan Magento 2.2.4, domain
http://m224ce.local/
.
Salah satu cara untuk menguji REST API adalah dengan membuka
http://m224ce.local/swagger
dan menggunakan fungsi
getList
, tetapi ingat bahwa metode
getList
tidak berfungsi dengan benar. Saya juga menguji semua metode dengan curl, contoh:
Dapatkan artikel dengan
id = 2 curl -X GET -H "Accept: application/json" "http://m224ce.local/rest/all/V1/blog/posts/2"
Jawabannya adalah:
{"id":2,"author_id":1,"title":"test title 2","content":"test content 2","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"}
Dapatkan daftar artikel dengan
author_id = 2 curl -g -X GET -H "Accept: application/json" "http://m224ce.local/rest/all/V1/blog/posts?searchCriteria[filterGroups][0][filters][0][field]=author_id&searchCriteria[filterGroups][0][filters][0][value]=1&searchCriteria[filterGroups][0][filters][0][conditionType]=eq"
Jawabannya adalah:
{"items":[{"id":1,"author_id":1,"title":"test title 1","content":"test content 1","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"},{"id":2,"author_id":1,"title":"test title 2","content":"test content 2","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"},{"id":3,"author_id":1,"title":"test title 3","content":"test content 3","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"}],"search_criteria":{"filter_groups":[{"filters":[{"field":"author_id","value":"1","condition_type":"eq"}]}]},"total_count":3}
Hapus artikel dengan
id = 3 curl -X DELETE -H "Accept: application/json" "http://m224ce.local/rest/all/V1/blog/posts/3"
Jawabannya adalah:
true
Simpan artikel baru
curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{"post": {"author_id": 1, "title": "test title 4", "content": "test content 4"}}' "http://m224ce.local/rest/all/V1/blog/posts"
Jawabannya adalah:
{"id":4,"author_id":1,"title":"test title 4","content":"test content 4","created_at":"2018-06-06 21:44:24","updated_at":"2018-06-06 21:44:24"}
Harap dicatat bahwa untuk permintaan dengan metode http POST, Anda harus melewati kunci
posting , yang sebenarnya sesuai dengan parameter input ($ post) untuk metode tersebut
\AlexPoletaev\Blog\Api\PostRepositoryInterface::save()
Denouement
Bagi mereka yang tertarik dengan apa yang terjadi selama permintaan dan bagaimana Magenta memprosesnya, di bawah ini saya akan memberikan beberapa tautan ke metode dengan komentar saya. Jika sesuatu tidak berhasil, maka metode ini harus didebitkan terlebih dahulu.
Pengontrol yang bertanggung jawab untuk memproses permintaan
\ Magento \ Webapi \ Controller \ Rest :: dispatch ()Selanjutnya dipanggil
\ Magento \ Webapi \ Controller \ Rest :: processApiRequest ()Banyak metode lain disebut di dalam
processApiRequest
, tetapi yang terpenting berikutnya
\ Magento \ Webapi \ Controller \ Rest \ InputParamsResolver :: resol ()\ Magento \ Webapi \ Controller \ Rest \ Router :: match () - rute tertentu ditentukan (di dalam, melalui metode
\Magento\Webapi\Model\Rest\Config::getRestRoutes()
, semua rute yang sesuai ditarik dari permintaan dari permintaan). Objek rute berisi semua data yang diperlukan untuk memproses permintaan - kelas, metode, hak akses, dll.
\ Magento \ Framework \ Webapi \ ServiceInputProcessor :: process ()-
\Magento\Framework\Reflection\MethodsMap::getMethodParams()
, di mana parameter metode ditarik melalui refleksi
\ Magento \ Framework \ Webapi \ ServiceInputProcessor :: convertValue () - beberapa opsi untuk mengubah array menjadi DataObject atau menjadi array dari DataObject
\ Magento \ Framework \ Webapi \ ServiceInputProcessor :: _ createFromArray () - konversi langsung, di mana melalui refleksi keberadaan getter dan setter diperiksa (ingat, saya katakan di atas bahwa kami akan kembali kepada mereka?) Dan bahwa mereka memiliki ruang lingkup publik. Selanjutnya, objek diisi dengan data melalui setter.
Pada akhirnya, dalam metode ini
\ Magento \ Webapi \ Controller \ Rest :: processApiRequest () , melalui
call_user_func_array
metode objek repositori dipanggil.
Epilog
Repositori modul GithubAda dua cara untuk menginstal:
1) Melalui komposer. Untuk melakukan ini, tambahkan objek berikut ke array
repositories
di file composer.json
{ "type": "git", "url": "https://github.com/alexpoletaev/magento2-blog-demo" }
Kemudian ketikkan perintah berikut di terminal:
composer require alexpoletaev/magento2-blog-demo:dev-master
2) Unduh file modul dan salin secara manual ke direktori
app/code/AlexPoletaev/Blog
Apa pun metode yang Anda pilih, pada akhirnya Anda harus menjalankan pemutakhiran:
php bin/magento setup:upgrade
Semoga artikel ini bermanfaat bagi seseorang. Jika Anda memiliki komentar, saran atau pertanyaan, maka selamat datang untuk berkomentar. Terima kasih atas perhatian anda