系统学习magento二次开发,推荐小册:《Magento中文全栈二次开发 》
本小册面向Magento2以上版本,书代码及示例兼容magento2.0-2.4版本。涵盖了magento前端开发,后端开发,magento2主题,magento2重写,magento2 layout,magento2控制器,magento2 block等相关内容,带领您成为magento开发技术专家。
有几种方法可以在Magento 2中创建客户。客户可以使用注册表单自行创建帐户,可以通过管理界面创建客户,甚至还有一个内置的Magento 2导入功能,可以从CSV文件中批量导入大量客户,前提是CSV中的客户数据已准备好导入。
但是,如果我们有成千上万的客户,他们的数据在创建之前仍然需要处理怎么办?执行此操作的最佳方法是以编程方式创建客户。
在本文中,我们将介绍以编程方式创建客户的主题,为此,我们将创建一个简单的Magento 2模块,
该模块将具有自定义控制台命令和几个模型,这些模型将用于读取,处理和创建客户。
### 结构
让我们首先在Magento 2安装的目录中创建一个模块。在此示例中,我将使用 Inchoo 作为模块供应商,并将模块命名为 CustomerCreation,但您可以根据需要命名它们。/app/code
在我们的模块中,我们将需要以下目录和文件:
registration.php /etc/module.xml /Console/Command/CreateCustomers.php /etc/di.xml /Model/Customer.php /Model/Import/CustomerImport.php
registation.php
为了使我们的客户创建模块正常工作,我们需要在Magento系统中注册我们的模块。将以下代码复制到文件中:registration.php
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Inchoo_CustomerCreation', __DIR__ );
### module.xml
我们的模块还需要声明它的名称和存在。将以下代码复制到文件中:/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="Inchoo_CustomerCreation" setup_version="1.0.0" /> </config>
### 其它文件
现在我们已经设置了模块,我们需要一种方法来触发我们的客户创建过程。我们可以做到这一点的一种方法是创建自定义控制台命令。
让我们从在文件中导入一堆类开始,让我们也定义类并使其扩展导入的 Command 类:/Console/Command/CreateCustomers.php
<?php namespace Inchoo\CustomerCreation\Console\Command; use Exception; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Console\Cli; use Magento\Framework\Filesystem; use Magento\Framework\App\State; use Magento\Framework\App\Area; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Inchoo\CustomerCreation\Model\Customer; class CreateCustomers extends Command { // everything else goes here }
方法:
public function __construct( Filesystem $filesystem, Customer $customer, State $state ) { parent::__construct(); $this->filesystem = $filesystem; $this->customer = $customer; $this->state = $state; }
我们还需要通过使用类中的方法为其设置名称来配置控制台命令。此方法继承自 Command 类,也可用于设置命令说明、输入参数和其他选项。configure()
public function configure(): void { $this->setName('create:customers'); }
现在让我们定义调用命令时将触发的方法。此方法也是从 Command 类继承而来的,我们将在其中编写我们的逻辑。execute()bin/magento create:customers
在该方法中,我们首先必须将区号设置为 .如果我们不这样做,客户创建过程稍后将产生错误。execute()global
之后,我们必须获取包含所有客户数据的 CSV 文件的绝对路径。在此示例中,CSV 文件已命名并位于目录中(相对于根目录,而不是我们的模块)。customers.csv/pub/media/fixtures
获得绝对路径后,我们必须调用该方法(此方法稍后将在我们的客户模型中定义),并将 CSV 文件的绝对路径作为参数传递给它。如果有任何错误,我们需要捕获它们并在CLI中显示错误消息:install()
public function execute(InputInterface $input, OutputInterface $output): ?int { try { $this->state->setAreaCode(Area::AREA_GLOBAL); $mediaDir = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); $fixture = $mediaDir->getAbsolutePath() . 'fixtures/customers.csv'; $this->customer->install($fixture, $output); return Cli::RETURN_SUCCESS; } catch (Exception $e) { $msg = $e->getMessage(); $output->writeln("<error>$msg</error>", OutputInterface::OUTPUT_NORMAL); return Cli::RETURN_FAILURE; } }
为了使我们的自定义命令正常工作,我们还必须使用依赖注入来配置命令名称。
将以下代码添加到文件中:/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"> <type name="Magento\Framework\Console\CommandList"> <arguments> <argument name="commands" xsi:type="array"> <item name="CreateCustomers" xsi:type="object">Inchoo\CustomerCreation\Console\Command\CreateCustomers</item> </argument> </arguments> </type> </config>
是时候创建我们的客户模型了。这是我们将读取CSV数据,对其进行处理,将其存储在数组中并将其发送以进行保存的地方。
在文件中,导入以下类并定义 Customer 类:/Model/Customer.php
<?php namespace Inchoo\CustomerCreation\Model; use Exception; use Generator; use Magento\Framework\Filesystem\Io\File; use Magento\Store\Model\StoreManagerInterface; use Inchoo\CustomerCreation\Model\Import\CustomerImport; use Symfony\Component\Console\Output\OutputInterface; class Customer { // everything else goes here }
我们还必须在此类中创建一个构造函数并注入一些依赖项:
private $file; private $storeManagerInterface; private $customerImport; private $output; public function __construct( File $file, StoreManagerInterface $storeManagerInterface, CustomerImport $customerImport ) { $this->file = $file; $this->storeManagerInterface = $storeManagerInterface; $this->customerImport = $customerImport; }
让我们在 Customer 类中定义方法。这是我们之前在自定义控制台命令中调用的方法。install()
我们首先检索商店和网站 ID。之后,我们检索 CSV 标头,然后遍历每个 CSV 行,读取这些行中包含的数据并将它们传递给我们尚未定义的方法。createCustomer()
public function install(string $fixture, OutputInterface $output): void { $this->output = $output; // get store and website ID $store = $this->storeManagerInterface->getStore(); $websiteId = (int) $this->storeManagerInterface->getWebsite()->getId(); $storeId = (int) $store->getId(); // read the csv header $header = $this->readCsvHeader($fixture)->current(); // read the csv file and skip the first (header) row $row = $this->readCsvRows($fixture, $header); $row->next(); // while the generator is open, read current row data, create a customer and resume the generator while ($row->valid()) { $data = $row->current(); $this->createCustomer($data, $websiteId, $storeId); $row->next(); } }
要读取 CSV 标题和行,我们使用生成器。生成器允许我们编写代码,这些代码可以迭代一组数据,而无需在内存中构建数组。如果我们有一个大的CSV文件,这可以帮助我们不超过内存限制。该方法将读取行数据并将其映射到从该方法检索的标头。readCsvRows()readCsvHeader()
创建以下方法:
private function readCsvRows(string $file, array $header): ?Generator { $handle = fopen($file, 'rb'); while (!feof($handle)) { $data = []; $rowData = fgetcsv($handle); if ($rowData) { foreach ($rowData as $key => $value) { $data[$header[$key]] = $value; } yield $data; } } fclose($handle); } private function readCsvHeader(string $file): ?Generator { $handle = fopen($file, 'rb'); while (!feof($handle)) { yield fgetcsv($handle); } fclose($handle); }
我们在此类中的最后一个方法是方法。createCustomer()
传递给该方法的参数是一个关联数组,其中包含来自 CSV 文件的键值对,每个键是标题列表(例如 email_address)的不同字段名称,每个值都是关联的记录(例如 john.doe@email.com)。$data
在我们的方法中,我们需要定义数组。这也将是一个关联数组,我们需要在其中将数组中的值与该方法需要保存客户的键配对。如果发现任何错误,其错误消息将打印在我们的 CLI 中。$customerData$dataimportCustomerData()
下面的示例假设CSV文件具有Magento创建客户所需的确切数据,但是,在大多数情况下,情况并非如此。也许客户出生日期的格式不正确,或者我们可能没有客户组 ID,只有客户组名称。在这种情况下,在填充数组之前,我们需要确保处理数组中的数据,使其包含正确的值。$customerData$data
private function createCustomer(array $data, int $websiteId, int $storeId): void { try { // collect the customer data $customerData = [ 'email' => $data['email_address'], '_website' => 'base', '_store' => 'default', 'confirmation' => null, 'dob' => null, 'firstname' => $data['firstname'], 'gender' => null, 'group_id' => $data['customer_group_id'], 'lastname' => $data['last_name'], 'middlename' => null, 'password_hash' => $data['password_hash'], 'prefix' => null, 'store_id' => $storeId, 'website_id' => $websiteId, 'password' => null, 'disable_auto_group_change' => 0, 'some_custom_attribute' => 'some_custom_attribute_value' ]; // save the customer data $this->customerImport->importCustomerData($customerData); } catch (Exception $e) { $this->output->writeln( '<error>'. $e->getMessage() .'</error>', OutputInterface::OUTPUT_NORMAL ); } }
### 实体
对于最后一步,我们需要创建客户导入模型。在文件中,让我们从Magento CustomerImportExport模块导入客户类并定义我们的CustomerImport类。使其扩展导入的客户类。/Module/Import/CustomerImport.php
我们的类将包含用于准备和保存客户数据的方法。importCustomerData()
<?php namespace Inchoo\CustomerCreation\Model\Import; use Magento\CustomerImportExport\Model\Import\Customer; class CustomerImport extends Customer { // everything else goes here }
现在我们的类已经定义,我们需要创建处理客户创建过程最后步骤的方法。在此方法中,我们将创建或更新客户实体,并保存为该客户提供的任何属性数据。然后,该方法将返回客户实体 ID。此方法实际上是对我们正在扩展的 Customer 类中找到的函数的轻微修改。importCustomerData()
public function importCustomerData(array $rowData) { $this->prepareCustomerData($rowData); $entitiesToCreate = []; $entitiesToUpdate = []; $entitiesToDelete = []; $attributesToSave = []; $processedData = $this->_prepareDataForUpdate($rowData); $entitiesToCreate = array_merge($entitiesToCreate, $processedData[self::ENTITIES_TO_CREATE_KEY]); $entitiesToUpdate = array_merge($entitiesToUpdate, $processedData[self::ENTITIES_TO_UPDATE_KEY]); foreach ($processedData[self::ATTRIBUTES_TO_SAVE_KEY] as $tableName => $customerAttributes) { if (!isset($attributesToSave[$tableName])) { $attributesToSave[$tableName] = []; } $attributesToSave[$tableName] = array_diff_key( $attributesToSave[$tableName], $customerAttributes ) + $customerAttributes; } $this->updateItemsCounterStats($entitiesToCreate, $entitiesToUpdate, $entitiesToDelete); /** * Save prepared data */ if ($entitiesToCreate || $entitiesToUpdate) { $this->_saveCustomerEntities($entitiesToCreate, $entitiesToUpdate); } if ($attributesToSave) { $this->_saveCustomerAttributes($attributesToSave); } return $entitiesToCreate[0]['entity_id'] ?? $entitiesToUpdate[0]['entity_id'] ?? null; }
为了完成工作,我们需要调用 and 命令来让我们的模块和自定义命令正常工作。bin/magento setup:upgradebin/magento setup:di:compile
使用 运行客户创建脚本后,还需要确保重新索引客户网格索引器。我们可以通过调用命令来做到这一点。bin/magento create:customersbin/magento indexer:reindex customer_grid
### 小结
就是这样,我们现在应该有一个功能齐全的模块,使我们能够在几分钟内以编程方式创建数千个客户。如果您有任何问题或问题,请在下面发表评论。