ユーザが自分のアバター（画像アイコン）を登録できるモジュールの作成方法


<p><a href="https://adventar.org/calendars/2349" >Magento Advent Calender 2017</a> の9日目の記事です。<br>
ユーザが自分のアバター（画像アイコン）を登録できるモジュールを作成したいと思います。</p>
<img width="498" alt="スクリーンショット 2017-12-06 11.22.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/119742/6e1eb254-1535-85cc-6225-2ef3a57b9e9d.png">

<dl>
<dt>この記事から習得できる技術</dt>
<dd>
<ul>
<li>テーブルへの属性追加</li>
<li>画像の追加方法</li>
</ul>
</dd>
</dl>

<dl>
<dt>全体的なイメージ</dt>
<dd>
<ol>
<li>カラムをcustomerテーブルに追加する</li>
<li>画像をバックエンドから追加、表示</li>
<li>画像をフロントエンドから追加、表示</li>
</ol>
</dd>
</dl>

<h2>カラムをcustomerテーブルに追加する</h2>
<p>まず初めに、作成するモジュール名を決めてください。<br>
今回は、Veriteworks_Avatarというモジュール名で作成したいと思います。</p>

<h3>Veriteworks\Avatar\etc\module.xml</h3>
<p>&lt;sequence&gt;タグでは、Magento_Catalogモジュールを読み込んでいます。<br>
このAvatarモジュールが処理をする前に、読み込んでおきたいモジュールがあれば、&lt;sequence&gt;タグを使って読み込みましょう。</p>
```xml: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="Veriteworks_Avatar" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Customer"/>
        </sequence>
    </module>
</config>
```

<h3>Veriteworks\Avatar\registration.php</h3>
<p>MODULEにはmodule.xmlで定義したモジュール名を入れてください。</p>
```php:registration.php
<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Veriteworks_Avatar',
    __DIR__
);
```

<h3>Veriteworks\Avatar\etc\frontend\routes.xml<br>
Veriteworks\Avatar\etc\adminhtml\routes.xml</h3>
<p>URLのルーティングに関する処理を行います。</p>
```xml:frontend/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="viewfile" frontName="viewfile">
            <module name="Veriteworks_Avatar" />
        </route>
    </router>
</config>
```
```xml:adminhtml/routes.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="adminhtml">
            <module name="Veriteworks_Avatar" before="Magento_Backend" />
        </route>
        <route id="avatar" frontName="avatar">
            <module name="Veriteworks_Avatar" />
        </route>
    </router>
</config>
```

<h3>Veriteworks\Avatar\etc\adminhtml\system.xml</h3>
<p>ここで、設定ページで設定する項目を作成することができます。<br>
今回は、アバターモジュールの画像を表示するかしないかのプルダウンを作成しています。</p>
```xml:system.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="veriteworks" translate="label" sortOrder="10">
            <label>Veriteworks</label>
        </tab>
        <section id="avatar" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
            <tab>veriteworks</tab>
            <label>Avatar</label>
            <resource>Veriteworks_Avatar::veriteworks_avatar</resource>
            <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
                <label>General</label>
                <field id="enable" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enable</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <comment>
                        enable avatar.
                    </comment>
                </field>
            </group>
        </section>
    </system>
</config>
```

<p>次に、customer_entityテーブルにアバター画像の情報を属性として追加したいと思います。</p>
<h3>Veriteworks\Avatar\Setup\InstallData.php</h3>
<p>attributesInfoで、その属性の型を設定することができます。<br>
57-60行目で、管理画面の顧客グリッドでアバター画像を表示するための設定をしています。<br>
75行目では、サイト上のどのページでこの属性を使用できるようにするかの設定をしています。</p>
```php:InstallData.php
<?php
namespace Veriteworks\Avatar\Setup;

use Magento\Customer\Setup\CustomerSetupFactory;
use Magento\Customer\Model\Customer;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * Customer setup factory
     *
     * @var CustomerSetupFactory
     */
    private $customerSetupFactory;
    /**
     * @var AttributeSetFactory
     */
    private $attributeSetFactory;
    /**
     * Init
     *
     * @param CustomerSetupFactory $customerSetupFactory
     */
    public function __construct(
        CustomerSetupFactory $customerSetupFactory,
        AttributeSetFactory $attributeSetFactory
    ) {
        $this->customerSetupFactory = $customerSetupFactory;
        $this->attributeSetFactory = $attributeSetFactory;
    }

    /**
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();
        /** @var CustomerSetup $customerSetup */
        $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
        $attributesInfo = [
            'vwp_avatar' => [
                'label' => 'Avatar',
                'type' => 'varchar',
                'input' => 'image',
                'position' => 1000,
                'visible' => true,
                'required' => false,
                'system' => 0,
                'user_defined' => true,
                'is_used_in_grid' => true,
                'is_visible_in_grid' => true,
                'is_html_allowed_on_front' => true,
                'visible_on_front' => true,
            ]
        ];
        $customerEntity = $customerSetup->getEavConfig()->getEntityType('customer');
        $attributeSetId = $customerEntity->getDefaultAttributeSetId();
        /** @var $attributeSet AttributeSet */
        $attributeSet = $this->attributeSetFactory->create();
        $attributeGroupId = $attributeSet->getDefaultGroupId($attributeSetId);
        foreach ($attributesInfo as $attributeCode => $attributeParams) {
            $customerSetup->addAttribute(Customer::ENTITY, $attributeCode, $attributeParams);
        }
        $magentoUsernameAttribute = $customerSetup->getEavConfig()->getAttribute(Customer::ENTITY, 'vwp_avatar');
        $magentoUsernameAttribute->addData([
            'attribute_set_id' => $attributeSetId,
            'attribute_group_id' => $attributeGroupId,
            'used_in_forms' => ['customer_account_create', 'customer_account_edit', 'adminhtml_customer'],
        ]);
        $magentoUsernameAttribute->save();
        $setup->endSetup();
    }
}
```

<h2>画像をバックエンドから追加、表示</h2>
<p>次に実際に画像をアップロードし、ちゃんとテーブルに保存されるか確認したいと思います。<br>
管理画面から顧客情報を確認してみてください。</p>
<img width="144" alt="スクリーンショット 2017-11-30 11.14.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/119742/671e9c48-35eb-2bfc-0b0a-915b4f86b2bb.png"><p>まずグリッドにアバターの列が表示されているのがわかります。
そして、アカウント情報編集では、アバター画像がアップロードできるようボタンが追加されていると思います。</p>
<img width="1130" alt="スクリーンショット 2017-11-30 11.11.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/119742/52fc2444-cc31-b420-8e47-a469dd55282d.png"><br>
<p>アバター画像を保存すると、画像パスだけがグリッドに表示されるため、画像を表示するよう変更します。</p>

<h3>Veriteworks\Avatar\view\adminhtml\ui_component\customer_listing.xml</h3>
```xml:customer_listing.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <columns name="customer_columns" class="Magento\Customer\Ui\Component\Listing\Columns">
        <column name="vwp_avatar" class="Veriteworks\Avatar\Ui\Component\Listing\Columns\Avatar">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/thumbnail</item>
                    <item name="sortable" xsi:type="boolean">false</item>
                    <item name="has_preview" xsi:type="string">1</item>
                    <item name="label" xsi:type="string" translate="true">Avatar</item>
                    <item name="sortOrder" xsi:type="number">1</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>
```

<h3>Veriteworks\Avatar\Ui\Component\Listing\Columns\Avatar.php</h3>
```php:Avatar.php
<?php

namespace Veriteworks\Avatar\Ui\Component\Listing\Columns;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Framework\View\Element\UiComponent\ContextInterface;

/**
 * Class Avatar
 * @package Veriteworks\Avatar\Ui\Component\Listing\Columns
 */
class Avatar extends \Magento\Ui\Component\Listing\Columns\Column
{
    /**
     * @var \Magento\Framework\View\Element\AbstractBlock
     */
    protected $viewFileUrl;
    /**
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param \Magento\Catalog\Helper\Image $imageHelper
     * @param \Magento\Framework\UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        \Magento\Framework\UrlInterface $urlBuilder,
        \Magento\Framework\View\Asset\Repository $viewFileUrl,
        array $components = [],
        array $data = []
    ) {
        parent::__construct($context, $uiComponentFactory, $components, $data);
        $this->urlBuilder = $urlBuilder;
        $this->viewFileUrl = $viewFileUrl;
    }
    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            $fieldName = $this->getData('name');
            foreach ($dataSource['data']['items'] as & $item) {
                $customer = new \Magento\Framework\DataObject($item);
                $picture_url = !empty($customer["vwp_avatar"]) ? $this->urlBuilder->getUrl(
                    'customer/index/viewfile/image/'.base64_encode($customer["vwp_avatar"])) : $this->viewFileUrl->getUrl('Veriteworks_Avatar::images/no-profile-img.png');
                $item[$fieldName . '_src'] = $picture_url;
                $item[$fieldName . '_orig_src'] = $picture_url;
                $item[$fieldName . '_alt'] = 'The profile picture';
            }
        }
        return $dataSource;
    }
}
```
<p>画像がまだ保存されていない顧客のために、デフォルトのアバター画像を表示するよう設定をしています。<br>
画像は<br>
Veriteworks\Avatar\view\adminhtml\web\images<br>
Veriteworks\Avatar\view\frontend\web\images<br>
に追加しておきましょう。</p>
<img width="90" alt="スクリーンショット 2017-12-06 11.23.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/119742/00b9524a-2d80-0fc3-dc3f-1e92cc523790.png"><br>
<p>画像が表示、追加されているのがわかると思います。</p>

<h2>画像をフロントエンドから追加、表示</h2>
<p>次に画像をフロントエンドから追加し、表示したいと思います。</p>

<h3>Veriteworks\Avatar\view\frontend\layout\customer_account_edit.xml</h3>
<p>アカウント情報ページから画像を追加するので、このファイルを追加します。</p>
```xml:customer_account_edit.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
    <referenceContainer name="form.additional.info">
        <block class="Veriteworks\Avatar\Block\Form\ChangeAvatar" name="vwp_avatar_form_edit" template="form/changeAvatar.phtml"/>
    </referenceContainer>
</body>
</page>
```

<h3>Veriteworks\Avatar\view\frontend\templates\changeAvatar.phtml</h3>
```php:changeAvatar.phtml
</fieldset>
<fieldset class="fieldset avatar" data-container="change-avatar">
    <legend class="legend"><span data-title="change-avatar"><?php /* @escapeNotVerified */ echo __('Change Avatar') ?></span></legend><br>
    <div class="field avatar" data-container="change-avatar">
        <label class="label" for="vwp_avatar"><span><?php /* @escapeNotVerified */ echo __('Avatar') ?></span></label>
        <div class="control">
            <input type="file" name="vwp_avatar" id="vwp_avatar" title="<?php /* @escapeNotVerified */ echo __('Avatar') ?>" class="input-text"/>
        </div>
        <div>
            <img src="<?php echo $this->getImage() ?>" width="150px" height="150px" alt="profile-picture" title="<?php echo __('Upload new avatar'); ?>" class="profile-image"/>
        </div>
    </div>
</fieldset>
```

<h3>Veriteworks\Avatar\Controller\Avatar\View.php</h3>
```php:view.php
<?php

namespace Veriteworks\Avatar\Controller\Avatar;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Framework\Exception\NotFoundException;

/**
 * Class View
 * @package Veriteworks\Avatar\Controller\Avatar
 */
class View extends \Magento\Framework\App\Action\Action
{
    /**
     * @var \Magento\Framework\Controller\Result\RawFactory
     */
    protected $resultRawFactory;

    /**
     * @var \Magento\Framework\Url\DecoderInterface
     */
    protected $urlDecoder;

    /**
     * @var \Magento\Framework\App\Response\Http\FileFactory
     */
    protected $fileFactory;

    /**
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magento\Framework\Url\DecoderInterface $urlDecoder
     * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\Controller\Result\RawFactory $resultRawFactory,
        \Magento\Framework\Url\DecoderInterface $urlDecoder,
        \Magento\Framework\App\Response\Http\FileFactory $fileFactory
    ) {
        $this->resultRawFactory    = $resultRawFactory;
        $this->urlDecoder  = $urlDecoder;
        $this->fileFactory = $fileFactory;
        return parent::__construct($context);
    }

    /**
     * View action
     *
     * @return \Magento\Framework\View\Result\PageFactory
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function execute()
    {
        $file = null;
        $plain = false;
        if ($this->getRequest()->getParam('file')) {
            // download file
            $file = $this->urlDecoder->decode(
                $this->getRequest()->getParam('file')
            );
        } elseif ($this->getRequest()->getParam('image')) {
            // show plain image
            $file = $this->urlDecoder->decode(
                $this->getRequest()->getParam('image')
            );
            $plain = true;
        } else {
            throw new NotFoundException(__('Page not found.'));
        }

        /** @var \Magento\Framework\Filesystem $filesystem */
        $filesystem = $this->_objectManager->get('Magento\Framework\Filesystem');
        $directory = $filesystem->getDirectoryRead(DirectoryList::MEDIA);
        $fileName = CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER . '/' . ltrim($file, '/');
        $path = $directory->getAbsolutePath($fileName);

        if (!$directory->isFile($fileName)
            && !$this->_objectManager->get('Magento\MediaStorage\Helper\File\Storage')->processStorageFile($path)
        ) {
            throw new NotFoundException(__('Page not found.'));
        }

        if ($plain) {
            $extension = pathinfo($path, PATHINFO_EXTENSION);
            switch (strtolower($extension)) {
                case 'gif':
                    $contentType = 'image/gif';
                    break;
                case 'jpg':
                    $contentType = 'image/jpeg';
                    break;
                case 'png':
                    $contentType = 'image/png';
                    break;
                default:
                    $contentType = 'application/octet-stream';
                    break;
            }
            $stat = $directory->stat($fileName);
            $contentLength = $stat['size'];
            $contentModify = $stat['mtime'];

            /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
            $resultRaw = $this->resultRawFactory->create();
            $resultRaw->setHttpResponseCode(200)
                ->setHeader('Pragma', 'public', true)
                ->setHeader('Content-type', $contentType, true)
                ->setHeader('Content-Length', $contentLength)
                ->setHeader('Last-Modified', date('r', $contentModify));
            $resultRaw->setContents($directory->readFile($fileName));
            return $resultRaw;
        } else {
            $name = pathinfo($path, PATHINFO_BASENAME);
            $this->fileFactory->create(
                $name,
                ['type' => 'filename', 'value' => $fileName],
                DirectoryList::MEDIA
            );
        }
    }
}
```

<h3>Veriteworks\Avatar\Block\Form\ChangeAvatar.php</h3>
```php:changeAvatar.phtml
<?php
namespace Veriteworks\Avatar\Block\Form;

use Magento\Customer\Api\AccountManagementInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Customer\Api\CustomerMetadataInterface;

/**
 * Class ChangeAvatar
 * @package Veriteworks\Avatar\Block\Form
 */
class ChangeAvatar extends \Magento\Customer\Block\Form\Edit
{

    /**
     * @var \Magento\Newsletter\Model\Subscriber
     */
    protected $subscription;

    /**
     * @var \Magento\Customer\Model\Session
     */
    protected $customerSession;

    /**
     * @var \Magento\Newsletter\Model\SubscriberFactory
     */
    protected $subscriberFactory;

    /**
     * @var CustomerRepositoryInterface
     */
    protected $customerRepository;

    /**
     * @var AccountManagementInterface
     */
    protected $customerAccountManagement;


    /**
     * @return string
     */
    public function getImage()
    {
        $file = $this->customerSession->getCustomer()->getVwpAvatar();
        if(is_null($file)) return $this->_assetRepo->getUrl('Veriteworks_Avatar::images/no-profile-img.png');

        $directory = $this->_filesystem->getDirectoryRead(DirectoryList::MEDIA);
        $fileName = CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER . '/' . ltrim($file, '/');
        $imageUrl = $directory->getAbsolutePath($fileName);

        $currentStore = $this->_storeManager->getStore();
        $mediaUrl = $currentStore->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);
        $imagePath = $mediaUrl.$fileName;
        return $this->getUrl('viewfile/avatar/view/', ['image' => base64_encode($file)]);
    }
}
```
<p>対応するテンプレートとブロックとコントローラを作成します。</p>
<img width="498" alt="スクリーンショット 2017-12-06 11.22.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/119742/5631b999-32ee-26a0-e599-bf0762a64554.png">
<p>画像が表示、追加できるのがわかると思います。</p>

<h2>まとめ</h2>
<p>今回の記事では、ユーザが自分のアバター（画像アイコン）を登録できるモジュールの作成方法について説明しました。<br>
この記事から、テーブルへの属性追加と画像の追加方法が習得できます。</p>

<p>これを応用することで、例えばレビューやログイン画面などでユーザ名の横にアバターアイコンを表示することができます。</p>