Chapter 03

クラス設計

ta.toshio
ta.toshio
2021.04.22に更新

リポジトリパターン

/var/www/vendor/avored/framework/src/Database
$ tree -L 2
.
|-- Contracts
|   |-- AddressModelInterface.php
|   |-- AdminUserModelInterface.php
|   |-- AttributeDropdownOptionModelInterface.php
|   |-- AttributeModelInterface.php
|   |-- AttributeProductValueModelInterface.php
|   |-- BaseInterface.php
|   |-- CategoryFilterModelInterface.php
|   |-- CategoryModelInterface.php
|   |-- ConfigurationModelInterface.php
|   |-- CountryModelInterface.php
|   |-- CurrencyModelInterface.php
|   |-- CustomerGroupModelInterface.php
|   |-- CustomerModelInterface.php
|   |-- LanguageModelInterface.php
|   |-- MenuGroupModelInterface.php
|   |-- MenuModelInterface.php
|   |-- OrderCommentModelInterface.php
|   |-- OrderModelInterface.php
|   |-- OrderProductAttributeModelInterface.php
|   |-- OrderProductModelInterface.php
|   |-- OrderStatusModelInterface.php
|   |-- PageModelInterface.php
|   |-- PermissionModelInterface.php
|   |-- ProductImageModelInterface.php
|   |-- ProductModelInterface.php
|   |-- PromotionCodeModelInterface.php
|   |-- PropertyModelInterface.php
|   |-- RoleModelInterface.php
|   |-- StateModelInterface.php
|   |-- TaxGroupModelInterface.php
|   `-- TaxRateModelInterface.php
|-- Models
|   |-- Address.php
|   |-- AdminUser.php
|   |-- Attribute.php
|   |-- AttributeDropdownOption.php
|   |-- AttributeProductValue.php
|   |-- BaseModel.php
|   |-- Category.php
|   |-- CategoryFilter.php
|   |-- Configuration.php
|   |-- Country.php
|   |-- Currency.php
|   |-- Customer.php
|   |-- CustomerGroup.php
|   |-- Language.php
|   |-- Menu.php
|   |-- MenuGroup.php
|   |-- Order.php
|   |-- OrderComment.php
|   |-- OrderProduct.php
|   |-- OrderProductAttribute.php
|   |-- OrderStatus.php
|   |-- Page.php
|   |-- Permission.php
|   |-- Product.php
|   |-- ProductImage.php
|   |-- ProductPropertyBooleanValue.php
|   |-- ProductPropertyDatetimeValue.php
|   |-- ProductPropertyDecimalValue.php
|   |-- ProductPropertyIntegerValue.php
|   |-- ProductPropertyTextValue.php
|   |-- ProductPropertyVarcharValue.php
|   |-- PromotionCode.php
|   |-- Property.php
|   |-- PropertyDropdownOption.php
|   |-- Role.php
|   |-- State.php
|   |-- TaxGroup.php
|   `-- TaxRate.php
|-- Repository
|   |-- AddressRepository.php
|   |-- AdminUserRepository.php
|   |-- AttributeDropdownOptionRepository.php
|   |-- AttributeProductValueRepository.php
|   |-- AttributeRepository.php
|   |-- BaseRepository.php
|   |-- CategoryFilterRepository.php
|   |-- CategoryRepository.php
|   |-- ConfigurationRepository.php
|   |-- CountryRepository.php
|   |-- CurrencyRepository.php
|   |-- CustomerGroupRepository.php
|   |-- CustomerRepository.php
|   |-- LanguageRepository.php
|   |-- MenuGroupRepository.php
|   |-- MenuRepository.php
|   |-- OrderCommentRepository.php
|   |-- OrderProductAttributeRepository.php
|   |-- OrderProductRepository.php
|   |-- OrderRepository.php
|   |-- OrderStatusRepository.php
|   |-- PageRepository.php
|   |-- PermissionRepository.php
|   |-- ProductImageRepository.php
|   |-- ProductRepository.php
|   |-- PromotionCodeRepository.php
|   |-- PropertyRepository.php
|   |-- RoleRepository.php
|   |-- StateRepository.php
|   |-- TaxGroupRepository.php
|   `-- TaxRateRepository.php
`-- Traits
    `-- FilterTrait.php

リポジトリインターフェース例

リポジトリ具象クラス例

カートまわり

概要

登場人物
カートマネージャー
カートプロダクト

CartProductクラスを内包するカートコレクションを管理する。
カートコレクションの実態はLaravelのCollection。
カートコレクションをSessionに保存する。
カートコレクションを管理するセッションを管理している。

UML

カートマネージャー

カートをハンドルするクラス

  • カートに商品の出し入れ
  • 合計金額算出

プロパティ、メソッド

カートプロダクト

カートに保持する商品を表すクラス

  • 商品と個数

プロパティ、メソッド


カートマネージャーの使用例

https://github.com/avored/laravel-ecommerce/blob/master/app/Http/Controllers/Cart/CartController.php

チェックアウトまわり

概要

カートの中身を表示、税金を計算(たぶん間違えている)。
支払い方法リスト表示、選択することができる。
配送住所リスト表示、選択することができる。

実装例

https://github.com/avored/laravel-ecommerce/blob/master/app/Http/Controllers/Checkout/CheckoutController.php

    public function __construct(
        CountryModelInterface $countryRepository,
        AddressModelInterface $addressRepository
    ) {
        $this->CountryRepository = $countryRepository;
        $this->addressRepository = $addressRepository;
    }

    /**
     * Show the application dashboard.
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function show()
    {
        $addresses = Collection::make([]);
        if (Auth::guard('customer')->check()) {
            $addresses = $this->addressRepository->getByCustomerId(Auth::guard('customer')->user()->id);
        }

        $paymentOptions = Payment::all();
        $shippingOptions = Shipping::all();
        $countryOptions = $this->CountryRepository->options();

        return view('checkout.show')
            ->with(compact('shippingOptions', 'paymentOptions', 'addresses', 'countryOptions'));
    }

オーダーまわり

オーダー処理実装例

https://github.com/avored/laravel-ecommerce/blob/master/app/Http/Controllers/Order/OrderController.php

メイン処理抜粋

    /**
     * Place the Order .
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function place(OrderPlaceRequest $request)
    {
// ログインしてればそれ、してなければ送信されたメールアドレスでユーザー見つける
// 送信されたメールアドレスでユーザーデータなければ新規作成
        $this->customer($request);
// 送信データにaddress_idあれば、そのidからaddressテーブルから
// 取得したデータを利用して、address_idがなければ、送信された住所情報をaddress
// テーブルに作成して使用
        $this->shippingAddress($request);
// 上のaddressがbilling_addressになっただけ
        $this->billingAddress($request);
// CacheOnDeliveryというモジュールを利用。実装は空。
// https://github.com/avored/laravel-ecommerce/blob/master/modules/avored/cash-on-delivery/src/CashOnDelivery.php
        $this->paymentOption();
// 実際の処理はこれ。OrderStatus::whereIsDefault(1)->first()
        $this->orderStatus();

        $orderData = [
            'shipping_option' => $request->get('shipping_option'),
            'payment_option' => $request->get('payment_option'),
            'order_status_id' => $this->orderStatus->id,
            'currency_id' => $this->getCurrency()->id,
            'customer_id' => $this->customer->id,
            'shipping_address_id' => $this->shippingAddress->id,
            'billing_address_id' => $this->billingAddress->id,
        ];
        $order = $this->orderRepository->create($orderData);
// order_products作成。 必要ならorder_product_attributesも作成。
        $this->syncProducts($order, $request);
// セッションクリア
        Cart::clear();

        return redirect()
            ->route('order.successful', $order->id)
            ->with('success', 'Order Placed Successfuly!');
    }

プロパティ、メソッド

UML

メニューまわり

https://github.com/avored/framework/tree/master/src/Menu

概要

メニューを表現している。

登場人物

  • MenuBuilder
  • MenuItem
  • MenuProvider

解説

MenuBuilderがMenuItemを使用して親メニューを作成。親メニューに子メニュー(subMenu)を追加してぶらさげる。MenuBuilderを利用するクラスはMenuProvider。

以下肝となる処理抜粋。

    /**
     * Make a Front End Menu an Object.
     * @param string $key
     * @param callable $callable
     * @return self
     */
    public function make($key, callable $callable)
    {
        $menu = new MenuItem($callable);
        $menu->key($key);
        $this->collection->put($key, $menu);

        return $this;
    }

    /**
     *  AvoRed Front Menu Construct method.
     */
    public function __construct($callable)
    {
        $this->callback = $callable;
        $callable($this);
    }
    
    /**
     * Get/Set Admin Menu Identifier.
     * @return \AvoRed\Framework\Menu\Menu|string
     */
    public function key($key = null)
    {
        if (null !== $key) {
            $this->key = $key;

            return $this;
        }

        return $this->key;
    }
    
    /**
     * Get/Set Admin Menu Sub Menu.
     * @param null|string $key
     * @param mixed $menuItem
     * @return \AvoRed\Framework\AdminMenu\AdminMenu
     */
    public function subMenu($key = null, $menuItem = null)
    {
        if (null === $menuItem) {
            return $this->subMenu;
        }
        $menu = new self($menuItem);
        $this->subMenu[$key] = $menu;

        return $this;
    }

    /**
     * To check if a menu has submenu or not.
     * @return bool
     */
    public function hasSubMenu()
    {
        if (isset($this->subMenu) && count($this->subMenu) > 0) {
            return true;
        }

        return false;
    }

使用例

Menu::make('catalog', function (MenuItem $menu) {
    $menu->label('avored::system.admin_menus.catalog')
        ->type(MenuItem::ADMIN)
        ->icon('store-front')
        ->route('#');
});

$catalogMenu = Menu::get('catalog');

$catalogMenu->subMenu('product', function (MenuItem $menu) {
    $menu->key('product')
        ->type(MenuItem::ADMIN)
        ->label('avored::system.admin_menus.product')
        ->route('admin.product.index');
});
$catalogMenu->subMenu('category', function (MenuItem $menu) {
    $menu->key('category')
        ->type(MenuItem::ADMIN)
        ->label('avored::system.admin_menus.category')
        ->route('admin.category.index');
});

Menu::make('cms', function (MenuItem $menu) {
    $menu->label('avored::system.admin_menus.cms')
        ->type(MenuItem::ADMIN)
        ->icon('news-paper')
        ->route('#');
});
$cmsMenu = Menu::get('cms');

$cmsMenu->subMenu('menu-group', function (MenuItem $menu) {
    $menu->key('menu-group')
        ->type(MenuItem::ADMIN)
        ->label('avored::system.admin_menus.menu')
        ->route('admin.menu-group.index');
});
$cmsMenu->subMenu('page', function (MenuItem $menu) {
    $menu->key('page')
        ->type(MenuItem::ADMIN)
        ->label('avored::system.admin_menus.page')
        ->route('admin.page.index');
});

登場人物

  • Builder
  • Breadcrumb

https://github.com/avored/framework/tree/master/src/Breadcrumb

Builderの抜粋

    /**
     * Breadcrumb Make an Object.
     *
     * @param string $name
     * @param callable $callable
     * @return void
     */
    public function make($name, callable $callable)
    {
        $breadcrumb = new Breadcrumb($callable);
        $breadcrumb->route($name);

        $this->collection->put($name, $breadcrumb);
    }
    
    /**
     * Render BreakCrumb for the Route Name.
     *
     * @param string $routeName
     * @return string|\Illuminate\View\View
     */
    public function render($routeName)
    {
        $breadcrumb = $this->collection->get($routeName);

        if (null === $breadcrumb) {
            return '';
        }

        return view('avored::breadcrumb.index')
                ->with(compact('breadcrumb'));
    }

Role, Permission

登場クラス

  • Permissionミドルウェア
  • AdminUserモデル
    • role_idを保持
    • belongsTo(Role::class)
  • Roleモデル
    • belongsToMany(Permission::class)
  • Permissionモデル
  • permission_roles中間テーブル
    • permission_id
    • role_id

具体的処理

middlwareで遷移先の認可があるかチェック。

<?php

namespace AvoRed\Framework\Support\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

class Permission
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        /** @var \AvoRed\Framework\Database\Models\AdminUser $user */
        $user = Auth::guard('admin')->user();
        $routeName = Route::currentRouteName();
        if ($user->hasPermission($routeName)) {
            return $next($request);
        }

        abort(403);
    }
}

AdminUserで自分のRoleが遷移先の認可に対応しているかチェック

    /**
     * To check if user has permission to access the given route name.
     * @return bool
     */
    public function hasPermission($routeName) : bool
    {
        if ($this->is_super_admin) {
            return true;
        }
        $role = $this->role;
        if ($role->permissions->pluck('name')->contains($routeName) == false) {
            return false;
        }

        return true;
    }