Magento
Adobe製の高機能ECプラットフォーム。B2BおよびB2Cの複雑な要件に対応可能。
CMS
Magento
概要
Magentoは、Adobe製の高機能ECプラットフォームです。B2BおよびB2Cの複雑な要件に対応可能で、大規模ECサイト向けのエンタープライズソリューションです。
詳細
Magento(マジェント)は、2008年にリリースされたオープンソースのECプラットフォームで、2018年にAdobeに買収されました。PHPで開発され、Zend Frameworkをベースに構築されています。Community Edition(無料)とCommerce Cloud(有料)の2つのエディションがあります。250,000以上のサイトで利用され、市場シェアは7%です。マルチストア管理、B2B機能、高度な在庫管理、価格管理、マーケティングツール、PWA(Progressive Web App)対応など、エンタープライズレベルの機能を提供します。依存性注入(DI)、プラグイン(インターセプター)、イベント/オブザーバーパターンなど、高度な開発パターンを採用しています。GraphQLとREST APIの両方をサポートし、ヘッドレスコマースやオムニチャネル戦略に対応。最新のPWA Studioによりモバイルファーストの開発も可能です。
メリット・デメリット
メリット
- 高機能: エンタープライズレベルの豊富な機能
- スケーラビリティ: 大規模トラフィックに対応可能
- B2B対応: 見積もり、承認フローなどB2B機能が充実
- カスタマイズ性: モジュラーアーキテクチャで高い拡張性
- PWA対応: モバイルファーストの開発が可能
- 国際化対応: 多言語・多通貨・複数税制に対応
- Adobe統合: Adobe Experience Cloudとの連携
デメリット
- 高い複雑性: 学習曲線が急で習得に時間がかかる
- リソース消費大: サーバーリソースを大量に必要とする
- 高い学習コスト: 開発者の習得に時間とコストがかかる
- 開発コスト: カスタマイズに高額な費用が発生
- ホスティング要件: 高スペックなサーバーが必要
主要リンク
- Magento公式サイト
- Adobe Commerce Developer Documentation
- Magento GitHub リポジトリ
- Magento DevDocs
- Magento Marketplace
- Magento Community
使い方の例
Magentoモジュールの基本構造
// app/code/Vendor/Module/registration.php
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Vendor_Module',
__DIR__
);
// app/code/Vendor/Module/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="Vendor_Module" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
<module name="Magento_Customer"/>
</sequence>
</module>
</config>
// app/code/Vendor/Module/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="Vendor\Module\Api\ProductInterface"
type="Vendor\Module\Model\Product" />
<type name="Vendor\Module\Model\Product">
<arguments>
<argument name="logger" xsi:type="object">Psr\Log\LoggerInterface</argument>
</arguments>
</type>
</config>
カスタムモデルとリポジトリパターン
// Api/Data/CustomProductInterface.php
<?php
namespace Vendor\Module\Api\Data;
interface CustomProductInterface
{
const ENTITY_ID = 'entity_id';
const SKU = 'sku';
const CUSTOM_ATTRIBUTE = 'custom_attribute';
/**
* @return int|null
*/
public function getId();
/**
* @param int $id
* @return $this
*/
public function setId($id);
/**
* @return string
*/
public function getSku();
/**
* @param string $sku
* @return $this
*/
public function setSku($sku);
/**
* @return string|null
*/
public function getCustomAttribute();
/**
* @param string $customAttribute
* @return $this
*/
public function setCustomAttribute($customAttribute);
}
// Model/CustomProduct.php
<?php
namespace Vendor\Module\Model;
use Magento\Framework\Model\AbstractModel;
use Vendor\Module\Api\Data\CustomProductInterface;
class CustomProduct extends AbstractModel implements CustomProductInterface
{
protected function _construct()
{
$this->_init(\Vendor\Module\Model\ResourceModel\CustomProduct::class);
}
public function getSku()
{
return $this->getData(self::SKU);
}
public function setSku($sku)
{
return $this->setData(self::SKU, $sku);
}
public function getCustomAttribute()
{
return $this->getData(self::CUSTOM_ATTRIBUTE);
}
public function setCustomAttribute($customAttribute)
{
return $this->setData(self::CUSTOM_ATTRIBUTE, $customAttribute);
}
}
// Api/CustomProductRepositoryInterface.php
<?php
namespace Vendor\Module\Api;
use Vendor\Module\Api\Data\CustomProductInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
interface CustomProductRepositoryInterface
{
/**
* @param int $id
* @return \Vendor\Module\Api\Data\CustomProductInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getById($id);
/**
* @param \Vendor\Module\Api\Data\CustomProductInterface $product
* @return \Vendor\Module\Api\Data\CustomProductInterface
* @throws \Magento\Framework\Exception\CouldNotSaveException
*/
public function save(CustomProductInterface $product);
/**
* @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
* @return \Vendor\Module\Api\Data\CustomProductSearchResultsInterface
*/
public function getList(SearchCriteriaInterface $searchCriteria);
}
プラグイン(インターセプター)の実装
// etc/di.xml - プラグイン設定
<type name="Magento\Catalog\Api\ProductRepositoryInterface">
<plugin name="vendor_module_product_save_plugin"
type="Vendor\Module\Plugin\ProductSavePlugin"
sortOrder="10" />
</type>
// Plugin/ProductSavePlugin.php
<?php
namespace Vendor\Module\Plugin;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Api\Data\ProductInterface;
use Psr\Log\LoggerInterface;
class ProductSavePlugin
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Before plugin - 実行前の処理
*/
public function beforeSave(
ProductRepositoryInterface $subject,
ProductInterface $product,
$saveOptions = false
) {
$this->logger->info('商品保存前: ' . $product->getSku());
// 価格の自動調整(例)
if ($product->getPrice() < 100) {
$product->setPrice($product->getPrice() * 1.1);
}
return [$product, $saveOptions];
}
/**
* After plugin - 実行後の処理
*/
public function afterSave(
ProductRepositoryInterface $subject,
ProductInterface $result,
ProductInterface $product,
$saveOptions = false
) {
$this->logger->info('商品保存後: ' . $result->getId());
// カスタム処理(キャッシュクリア、通知など)
$this->clearCustomCache($result->getId());
return $result;
}
/**
* Around plugin - 処理の前後をラップ
*/
public function aroundDelete(
ProductRepositoryInterface $subject,
\Closure $proceed,
ProductInterface $product
) {
$this->logger->info('商品削除開始: ' . $product->getSku());
// 削除前の検証
if ($this->canDelete($product)) {
$result = $proceed($product);
$this->logger->info('商品削除完了');
return $result;
}
throw new \Magento\Framework\Exception\LocalizedException(
__('この商品は削除できません。')
);
}
}
GraphQL APIの実装
// etc/schema.graphqls
type Query {
customProducts(
filter: CustomProductFilterInput
pageSize: Int = 20
currentPage: Int = 1
): CustomProducts @resolver(class: "Vendor\\Module\\Model\\Resolver\\CustomProducts")
}
type CustomProducts {
items: [CustomProduct]
page_info: SearchResultPageInfo
total_count: Int
}
type CustomProduct {
id: Int
sku: String
name: String
price: Float
custom_attribute: String
categories: [CategoryInterface]
}
input CustomProductFilterInput {
sku: FilterTypeInput
name: FilterTypeInput
price: FilterRangeTypeInput
custom_attribute: FilterTypeInput
}
// Model/Resolver/CustomProducts.php
<?php
namespace Vendor\Module\Model\Resolver;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
class CustomProducts implements ResolverInterface
{
private $valueFactory;
private $customProductRepository;
public function __construct(
ValueFactory $valueFactory,
\Vendor\Module\Api\CustomProductRepositoryInterface $customProductRepository
) {
$this->valueFactory = $valueFactory;
$this->customProductRepository = $customProductRepository;
}
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) {
$searchCriteria = $this->buildSearchCriteria($args);
$searchResult = $this->customProductRepository->getList($searchCriteria);
$products = [];
foreach ($searchResult->getItems() as $product) {
$products[] = [
'id' => $product->getId(),
'sku' => $product->getSku(),
'name' => $product->getName(),
'price' => $product->getPrice(),
'custom_attribute' => $product->getCustomAttribute()
];
}
return [
'items' => $products,
'page_info' => [
'page_size' => $searchCriteria->getPageSize(),
'current_page' => $searchCriteria->getCurrentPage(),
'total_pages' => ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize())
],
'total_count' => $searchResult->getTotalCount()
];
}
}
イベントとオブザーバーパターン
// etc/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="checkout_cart_product_add_after">
<observer name="vendor_module_cart_add_observer"
instance="Vendor\Module\Observer\CartAddObserver" />
</event>
<event name="sales_order_place_after">
<observer name="vendor_module_order_place_observer"
instance="Vendor\Module\Observer\OrderPlaceObserver" />
</event>
</config>
// Observer/CartAddObserver.php
<?php
namespace Vendor\Module\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
use Psr\Log\LoggerInterface;
class CartAddObserver implements ObserverInterface
{
private $logger;
private $messageManager;
public function __construct(
LoggerInterface $logger,
\Magento\Framework\Message\ManagerInterface $messageManager
) {
$this->logger = $logger;
$this->messageManager = $messageManager;
}
public function execute(Observer $observer)
{
$item = $observer->getEvent()->getQuoteItem();
$product = $observer->getEvent()->getProduct();
// カスタムロジック(例:在庫チェック、価格調整など)
if ($item->getQty() > 10) {
// 大量購入割引を適用
$price = $item->getProduct()->getPrice();
$discountedPrice = $price * 0.9; // 10%割引
$item->setCustomPrice($discountedPrice);
$item->setOriginalCustomPrice($discountedPrice);
$item->getProduct()->setIsSuperMode(true);
$this->messageManager->addSuccessMessage(
__('大量購入割引が適用されました!')
);
}
$this->logger->info(
'カートに商品追加: ' . $product->getSku() . ', 数量: ' . $item->getQty()
);
}
}
B2B機能の実装例
// B2B見積もりリクエスト
<?php
namespace Vendor\Module\Model;
use Magento\Framework\Model\AbstractModel;
class QuoteRequest extends AbstractModel
{
protected function _construct()
{
$this->_init(\Vendor\Module\Model\ResourceModel\QuoteRequest::class);
}
/**
* 見積もりリクエストの作成
*/
public function createQuoteRequest($customerId, $items)
{
$this->setCustomerId($customerId);
$this->setStatus('pending');
$this->setCreatedAt(date('Y-m-d H:i:s'));
// 商品情報の保存
$itemsData = [];
foreach ($items as $item) {
$itemsData[] = [
'product_id' => $item['product_id'],
'qty' => $item['qty'],
'requested_price' => $item['requested_price'] ?? null
];
}
$this->setItems(json_encode($itemsData));
return $this->save();
}
/**
* 見積もりの承認
*/
public function approveQuote($adminUserId, $approvedPrices)
{
$this->setStatus('approved');
$this->setApprovedBy($adminUserId);
$this->setApprovedAt(date('Y-m-d H:i:s'));
$this->setApprovedPrices(json_encode($approvedPrices));
// 承認後の処理(メール送信、注文作成など)
$this->_eventManager->dispatch('quote_request_approved', ['quote' => $this]);
return $this->save();
}
}
// Controller/Adminhtml/Quote/Approve.php
<?php
namespace Vendor\Module\Controller\Adminhtml\Quote;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
class Approve extends Action
{
private $quoteRequestFactory;
private $jsonHelper;
public function __construct(
Context $context,
\Vendor\Module\Model\QuoteRequestFactory $quoteRequestFactory,
\Magento\Framework\Json\Helper\Data $jsonHelper
) {
parent::__construct($context);
$this->quoteRequestFactory = $quoteRequestFactory;
$this->jsonHelper = $jsonHelper;
}
public function execute()
{
$quoteId = $this->getRequest()->getParam('quote_id');
$approvedPrices = $this->jsonHelper->jsonDecode(
$this->getRequest()->getParam('approved_prices')
);
try {
$quote = $this->quoteRequestFactory->create()->load($quoteId);
$quote->approveQuote(
$this->_auth->getUser()->getId(),
$approvedPrices
);
$this->messageManager->addSuccessMessage(__('見積もりが承認されました。'));
} catch (\Exception $e) {
$this->messageManager->addErrorMessage(__('エラーが発生しました: %1', $e->getMessage()));
}
return $this->_redirect('*/*/index');
}
}
これらの例は、Magentoの主要な開発パターンを示しています。モジュール開発、依存性注入、プラグインシステム、GraphQL API、イベント/オブザーバーパターン、B2B機能など、エンタープライズレベルのECサイト開発に必要な機能を網羅しています。