I. Base Controller

Đây là controller gốc dược kế thừa từ Laravel và triển khai thêm các trait module nhằm tái sử dụng code một cách tối đa. Từ controller này sẽ triển khai các controller module khác

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests,
    
    
    // module quan li lien quan toi modulw se dc ke thua tu controller nay
        Traits\ModuleMethods, 
        // tap hop cac thuoc tinh va ham lien quan den view
        Traits\ModuleData, 
        // tap hop cac thuoc tinh va ham lien quan den view
        Traits\ViewMethods, 
        // tap hop cac thuoc tinh va ham lien quan den form
        Traits\FormMethods, 
        // tap hop cac thuoc tinh va ham lien quan den xu ly su kien nhu create, update, delete, restore
        Traits\CrudMethods,
        // tap hop cac thuoc tinh va ham lien quan den xu ly su kien nhu save , handle
        Traits\BaseCrud,
        // tap hop cac thuoc tinh va ham lien quan den xu ly su file
        Traits\FileMethods,
        // tap hop cac thuoc tinh va ham lien response 
        Traits\ResponseMethods;

        
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->init();
    }

    /**
     * thuc thi mot so thiet lap
     * @return void
     */
    public function init()
    {
        $this->moduleInit();
        $this->crudInit();
        $this->fileInit();
        $this->formInit();
        $this->activeMenu();
        $this->start();
    }

    /**
     * start 
     */
    public function start()
    {
        # code...
    }

}
?>

 

Controller sẽ được chia thành 2 nhóm chính để phục vụ cho việc uản lý code và bảo mật. cụ thể sẽ chia làm 2 nhóm cơ bản là admin và client, mỗi một nhóm sẽ có các phương thức và cách hoạt động khác nhau.

II. Admin Controller


<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;


use Illuminate\Http\Request;
use Crazy\Files\Filemanager;
use Crazy\Html\Menu;

class AdminController extends Controller
{
    /**
     * @var string $routeNamePrefix
     */
    protected $routeNamePrefix = 'admin.';

    /**
     * @var string $viewFolder thu muc chua view
     * khong nen thay doi lam gi
     */
    protected $viewFolder = 'admin';
    /**
     * @var string dường dãn thư mục chứa form
     */
    protected $formDir = 'admin/forms';

    /**
     * @var string $menuName
     */
    protected $menuName = 'admin_menu';
    

    protected $scope = 'admin';

}

 

Project Controller

Mẫu (controller chức năng quản lý project)


<?php

namespace App\Http\Controllers\Admin\General;

use Illuminate\Http\Request;
use App\Http\Controllers\Admin\AdminController;
use Crazy\Helpers\Arr;

use App\Repositories\Projects\ProjectRepository;
use App\Repositories\Tags\TagRefRepository;
use App\Repositories\Metadatas\MetadataRepository;
use App\Repositories\Files\FileRepository;

class ProjectController extends AdminController
{
    protected $module = 'projects';

    protected $moduleName = 'Dự án';

    protected $flashMode = true;

    
    
    public $featureImageWidth = 400;
    public $featureImageHeight = 400;
    
    public $socialImageWidth = 600;
    public $socialImageHeight = 315;
    
    
    // protected $makeThumbnail = true;
    
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct(ProjectRepository $projectRepository, TagRefRepository $tagRefRepository, MetadataRepository $metadataRepository, FileRepository $fileRepository)
    {
        $this->repository = $projectRepository;
        $this->tagRefRepository = $tagRefRepository;
        $this->metadataRepository = $metadataRepository;
        $this->fileRepository = $fileRepository;
        $this->init();
    }

    /**
     * can thiệp trước khi luu
     * @param Illuminate\Http\Request $request
     * @param Arr $data dũ liệu đã được validate
     * @return void
     */
    protected function beforeSave(Request $request, Arr $data)
    {
        if(!$request->id){
            $data->author_id = $request->user()->id;
        }
        $data->slug = $this->repository->getSlug(
            $request->custom_slug? $request->slug : $request->title,
            $request->id
        );
        $this->uploadImageAttachFile($request, $data, 'feature_image', 'static/projects');

        
        $this->makeSocialImage($data, $this->module);

        if($data->category_id){
            $data->category_map = $this->repository->makeCategoryMap($data->category_id);
        }
    }
/** * can thiệp sau khi luu * @param Illuminate\Http\Request $request * @param Model $project dũ liệu đã được luu * @param Arr $data * @return void */ public function afterSave(Request $request, $project, $data) { $tags = $this->tagRefRepository->updateTagRef('project', $project->id, $data->tags??[]); $meta = $data->copy( [ 'custom_slug', 'client_id', 'website', 'link', 'feature_image_keep_original' ] ); $meta['og_image_width'] = $this->featureImageWidth; $meta['og_image_height'] = $this->featureImageHeight; $metas = $this->metadataRepository->saveMany('project', $project->id, $meta); //nếu có gallery if($request->id == $project->id){ $this->fileRepository->deleteRefFileIgnoreList('project', $project->id, is_array($request->gallery_ids)?$request->gallery_ids:[]); } if($request->gallery_data){ $this->fileRepository->saveBase64List($request->gallery_data, 'project', $project->id, $request->user()->id); } } }

Chúng ta sẽ không thấy được các phương thứ để hiển thị form, danh sách rồi các chức năng liên quan trong controller. Vậy làm sao đề hiển thị? Và các phương thức đó ở đâu?

Thực chất hầu hết các tính năng đề đã được cung cấp trong các trait, khi chúng ta tạo các controller chúng ta chỉ cần khai báo lại các thuộc tính rồi hệ thống sẽ tự xử lý các bước còn lại.

Vậy các trường hợp dasc589 biệt ví dụ như upload ảnh hay thêm các thông tin khác thì sao?

Đây là một quy trình khép kín. Mọi thông tin cần được xử lý và lưu trữ sẽ được quy định trong Validator của chức năng

Một chức năng hoàn chỉnh bao gồm:

Route (có thể cả admin và client)

Controller (có thể cả admin và client)

Model

Repository (cung cấp cá phương thức truy vấn có thể được tái sử dụng nhiều lần ở mọi nơi)

Validator (Quy định các thông tin sẽ được tếp nhận và cách thức xác minh thông tin)

Mask (Mặt nạ - Đạ diện cho model trong view nhằm tránh sự can thiệp cào cơ sở dữ liệu tại view)

Tất cả sẽ được liên kết tại repository

 

Với các trường hợp đạc biệt như upload ảnh hay làm một việc gì đó trước hoặc sau khi lưu trru74, cập nhập dử liệu chúng ta sẽ khai báo các phương thức bắt sự kiện. Ví dụ trong Project controller ở trên chúng ta đã khai báo 2 phương thức beforeSaveafterSave đây họi là hướng sự kiện.

Một số các sự kiện trong CRUD:

<?php


namespace App\Http\Controllers\Traits;

use Crazy\Helpers\Arr;
use Illuminate\Http\Request;
// use Crazy\Html\HTML;


use Crazy\Laravel\Router;


/**
 * các thuộc tính và phương thức của form sẽ được triển trong ManagerController / hoặc admin controller
 *
 * @method void beforeSaveValidate( Request $request )
 * @method void beforeAjaxValidate( Request $request )
 * @method void beforeCreateValidate( Request $request )
 * @method void beforeAjaxCreateValidate( Request $request )
 * @method void beforeStoreValidate( Request $request )
 * @method void beforeUpdateValidate( Request $request )
 * @method void beforeAjaxUpdateValidate( Request $request )
 * @method void beforeValidate( Request $request )
 * @method void beforeAjaxValidate( Request $request )
 * @method void beforeHandleValidate( Request $request )
 * 
 * @method void beforeSave( Request $request, Arr $data ) 
 * @method void beforeAjaxSave( Request $request, Arr $data ) 
 * @method void beforeCreate( Request $request, Arr $data ) 
 * @method void beforeAjaxCreate( Request $request, Arr $data ) 
 * @method void beforeStore( Request $request, Arr $data ) 
 * @method void beforeUpdate( Request $request, Arr $data )
 * @method void beforeAjaxUpdate( Request $request, Arr $data )
 * @method void beforeMoveToTrash( Request $request, Arr $data ) 
 * @method void beforeRestore( Request $request)
 * @method void beforeDelete( Request $request)
 * 
 * @method void afterSave( Request $request, \App\Models\Model $result )
 * @method void afterAjaxSave( Request $request, \App\Models\Model $result )
 * @method void afterCreate( Request $request, \App\Models\Model $result ) 
 * @method void afterAjaxCreate( Request $request, \App\Models\Model $result ) 
 * @method void afterStore( Request $request, \App\Models\Model $result ) 
 * @method void afterUpdate( Request $request, \App\Models\Model $result ) 
 * @method void afterAjaxUpdate( Request $request, \App\Models\Model $result ) 
 * @method void afterMoveToTrash( Request $request, \App\Models\Model $result ) 
 * @method void afterRestore( Request $request, \App\Models\Model $result )
 * @method void afterDelete( Request $request, \App\Models\Model $result )
 * 
 * @method mixed done( Request $request, Arr $data )
 */
trait CrudMethods
{
   # code
}

 

III. Client Controller

Đây là controller gôc của phía client, tất cả các controller module đều được kế thừa từ controller này và thay đổi một số thuộc tính

 

<?php
namespace App\Http\Controllers\Clients;

use App\Engines\Breadcrumb;
use App\Engines\CacheEngine;
use App\Engines\ViewManager;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Crazy\Helpers\Arr;
use Illuminate\Contracts\Support\Htmlable;
use Mobile_Detect;

class ClientController extends Controller
{

    public static $isShare = false;
    /**
     * @var string $routeNamePrefix
     */
    protected $routeNamePrefix = 'clients.';

    /**
     * @var string $viewFolder thu muc chua view
     * khong nen thay doi lam gi
     */
    protected $viewFolder = 'clients';
    /**
     * @var string dường dãn thư mục chứa form
     */
    protected $formDir = 'clients/forms';

    /**
     * @var string $menuName
     */
    protected $menuName = 'client_menu';
    
    protected $scope = 'clients';

    /**
     * thời gian lưu chữ cache của view
     *
     * @var integer
     */
    public $cacheViewTime = 0;

    /**
     * thời gian lưu cach3 của data lấy từ db
     *
     * @var integer
     */
    public $cacheDataTime = 0;



    /**
     * path khi su dung view cache
     *
     * @var string
     */
    public $cacheViewPrefixPath = 'modules.';

    /**
     * Breakcrump
     * @var \App\Engines\Breadcrumb $breadcrumb
     */
    protected $breadcrumb  = null;


    /**
     * chế độ view
     *
     * @var string $viewMode 
     */
    public $viewMode = 'module';

    /**
     * device
     *
     * @var \Mobile_Detect
     */
    public $device = null;

    protected function shareDefaultData($name = null, $value = null)
    {
        if(self::$isShare) return true;
        ViewManager::share($name, $value);
        self::$isShare = true;
    }

    public function init()
    {
        $this->cacheViewTime = system_setting('cache_view_time', 0);
        $this->cachDatawTime = system_setting('cache_data_time', 0);
        $this->viewFolder = 'clients.'.theme_path();
        $this->breadcrumb = app(Breadcrumb::class);
        $this->device = app(Mobile_Detect::class);
        $this->shareDefaultData([
            'breadcrumb' => $this->breadcrumb ,
            'breakcrumb' => $this->breadcrumb 
        ]);
        parent::init();
    }

    
    /**
     * view
     * @param string $bladePath
     * @param array $data
     * @return \Illuminate\View\View
     */
    public function view(string $bladePath, array $data = [])
    {
        $bp = ($this->viewMode == 'module'?'modules.':'') . $bladePath;
        $viewdata = array_merge($data, [
            'module_slug' => $this->module,
            'module_name' => $this->moduleName,
            'route_name_prefix' => $this->routeNamePrefix
        ]);
        return ViewManager::theme($bp, $viewdata);
    }

    /**
     * giống view nhung trỏ sẵn vào module
     * @param string $bladeName
     * @param array $data dữ liệu truyền vào
     */
    public function viewModule($subModule, array $data = [])
    {
        return $this->view(($this->viewMode != 'module'?'modules.':'').$this->moduleBlade . ($subModule? '.' . $subModule:''), $data);
    }


    /**
     * lấy thông tin cche của view
     *
     * @param Request $request
     * @param string $bladeName
     * @param mixed $data
     * @param string $key
     * @param string $use_param
     * @return mixed
     */
    public function cacheView(Request $request, $bladeName=null, $data = null, $key = null, $use_param = false)
    {
        // trường hợp không cache 
        $id = ($user = $request->user()) ? $user->id : null;
        if($id || $this->cacheViewTime <= 0) {
            if(is_array($data)) $viewData = $data;
            elseif (is_callable($data) && is_array($calledData = $data($request))) $viewData = $calledData;
            else $viewData = [];
            $html = $this->view($bladeName, $viewData);
            return $html;
        }
        
        if(!$key) $key = $bladeName;
        $key = 'view-'.$key;
        if($use_param){
            $params = $request->all();
            ksort($params);
        }else{
            $params = [];
        }
        
        
        if(!($html = CacheEngine::get($key, $params)) ){
            $viewData = [];
            if(is_array($data)) $viewData = $data;
            elseif (is_callable($data) && is_array($calledData = $data($request))) {
                $viewData = $calledData;
            }
            $html = $this->view($bladeName, $viewData);
            if(!$id && $this->cacheViewTime > 0){
                $html = $html->render();
                CacheEngine::set($key, $html, $this->cacheViewTime, $params);
            }
        }
        return $html;
    }

    /**
     * lấy cache module hoặc tạo mới
     *
     * @param Request $request
     * @param string $subModule
     * @param array|callable $data
     * @param string $key
     * @param bool $use_param
     * @return View
     */
    public function cacheViewModule(Request $request, $subModule, $data = null, $key = null, $use_param = false)
    {
        if(!$key) $key = $subModule;
        $key = 'module-'.$key;
        return $this->cacheView($request,  $this->moduleBlade . '.' . $subModule, $data, $key,$use_param);
    }



    /**
     * thao tác với data trong csdl thông qua hàm callback
     *
     * @param string $key
     * @param callable $callback
     * @return mixed
     */
    public function cacheData($key, $callback)
    {
        $k = (static::class) . '-data-' .$key;

        if($this->cacheDataTime <= 0 || !($data = CacheEngine::get($k)) ){
            $d = null;
            if (is_callable($callback) && $calledData = $callback()) {
                $d = $calledData;
            }
            if ($d instanceof Htmlable) {
                $data = $d->toHtml();
            }elseif (is_a($d, \Illuminate\View\View::class)) {
                $data = $d->render();
            }
            elseif (is_object($d) && method_exists($d, 'render')) {
                $data = $d->render();
            }
            else{
                $data = $d;
            }
            
            if($this->cacheDataTime > 0){
                CacheEngine::set($k, $data, $this->cacheDataTime);
            }
        }
        return $data;
    }

    /**
     * cache theo url
     *
     * @param Request $request
     * @param bool $withQueryString
     * @param \Closure $callback
     * @return mixed
     */
    protected function cacheUrl(Request $request, $withQueryString = false, $callback = null)
    {
        $id = ($user = $request->user()) ? $user->id : null;
        if($id || $this->cacheViewTime <= 0) {
            if (is_callable($callback)) {
                return $callback($request);
            }
            return $callback;
        }
        $uri = $withQueryString ? $request->getRequestUri() : $request->getPathInfo();
        $isMobileKey = $this->device->isMobile() ? 'mobile-': 'desktop-';
        $urlKey = $isMobileKey.'cache-url-'.$uri;
        if(!($data = CacheEngine::get($urlKey)) ){
            $d = null;
            if (is_callable($callback) && $calledData = $callback($request)) {
                $d = $calledData;
            }
            if ($d instanceof Htmlable) {
                $data = $d->toHtml();
            }elseif (is_a($d, \Illuminate\View\View::class)) {
                $data = $d->render();
            }
            elseif (is_object($d) && method_exists($d, 'toArray')) {
                $data = $d->toArray();
            }
            elseif (is_object($d) && method_exists($d, 'render')) {
                $data = $d->render();
            }
            else{
                $data = $d;
            }
            
            if(!$id && $this->cacheViewTime > 0){
                CacheEngine::set($urlKey, $data, $this->cacheViewTime);
            }
        }
        return $data;
    }


    /**
     * lấy thông tin cache của view
     *
     * @param Request $request
     * @param string $key
     * @param \Closure $callback
     * @return mixed
     */
    public function cache(Request $request, $key, $callback = null)
    {
        $id = ($user = $request->user()) ? $user->id : null;
        if($id || $this->cacheViewTime <= 0) {
            if (is_callable($callback)) {
                return $callback($request);
            }
            return $callback;
        }
        $urlKey = 'cache-controller-'.$key.'-'.str_slug($request->getRequestUri());
        
        if(!($data = CacheEngine::get($urlKey))){
            $d = null;
            if (is_callable($callback) && $calledData = $callback($request)) {
                $d = $calledData;
            }
  
            if ($d instanceof Htmlable) {
                $data = $d->toHtml();
            }elseif (is_a($d, \Illuminate\View\View::class)) {
                $data = $d->render();
            }
            elseif (is_object($d) && method_exists($d, 'toArray')) {
                $data = $d->toArray();
            }
            elseif (is_object($d) && method_exists($d, 'render')) {
                $data = $d->render();
            }
            else{
                $data = $d;
            }
            if(!$id && $this->cacheViewTime > 0){
                CacheEngine::set($urlKey, $data, $this->cacheViewTime);
            }
        }
        return $data;
    }






    
    /**
     * lấy cache task của repository
     *
     * @param Request $request
     * @param string $key
     * @param \App\Repositories\Base\BaseRepository $repository
     * @return \App\Repositories\Base\BaseRepository|\App\Repositories\Base\CacheTask
     */
    public function cacheTask(Request $request, $key, $repository = null)
    {
        if(!$repository) $repository = $this->repository;
        return $repository->cache($key, $this->cacheDataTime, $request->all());
    }


}

?>

Ở phía client hầu như rất ít xử lý CRUD hầu hết là hiển thị dữ liệu. Quan trọng nhất là trải nghiệm của người dùng và mức độ chịu dựng requuest của hệ thống. do đó phải chú trọng về giao diện và tối ưu hóa các query. Việc chưa hoàn thành api cho các giao diện frontend đang được tạm khắc phục bằng cách dùng cache. Hy vọng thời gian tới có thể hàn thành.

 

Product Controller

Mẫu controller phía client


<?php

namespace App\Http\Controllers\Clients;

use App\Repositories\Products\CategoryRepository;
use Illuminate\Http\Request;
use Crazy\Helpers\Arr;

use App\Repositories\Products\ProductRepository;
use App\Repositories\Products\ReviewRepository;

class ProductController extends ClientController
{
    protected $module = 'products';

    protected $moduleName = 'Product';

    protected $flashMode = true;

    protected $perPage = 10;
    

    /**
     * category
     *
     * @var CategoryRepository
     */
    public $categoryRepository;


    /**
     * reviwws
     * @var ReviewRepository
     */
    public $reviewRepository;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct(ProductRepository $ProductRepository, CategoryRepository $categoryRepository, ReviewRepository $reviewRepository)
    {
        $this->repository = $ProductRepository;
        $this->categoryRepository = $categoryRepository;
        // theme dieu kien
        $this->repository->mode('mask');
        $this->categoryRepository->mode('mask')->addDefaultParam('deleted', 'deleted', 0);
        $this->reviewRepository = $reviewRepository->setActor('client');
        $this->init();

        
        $this->perPage = ($display = get_display_setting()) ? $display->product_per_page(10) : 10;
    }

    /**
     * xem chi tiết sản phẩm
     *
     * @param Request $request
     * @return View
     */
    public function viewProductDetail(Request $request)
    {
        // trả về cache url
        return $this->cacheUrl($request, false, function() use($request){
            // key trut cap
            $slug = $request->route('slug');
            $key = 'view-product-detail-';
            if($slug){
                $key .= 'slug-'.$slug;
                $args = ['slug' => $slug];
            }elseif($request->id){
                $key .= 'id-'.$request->id;
                $args = ['id' => $request->id];
            }else{
                return $this->view('errors.404');
            }
            
            if($product = $this->cacheTask($request, $key)->getProductDetail($args)){
                $product->applyMeta();
                set_active_model('product', $product);
                $this->breadcrumb->addProduct($product);
                $page_title = $product->name;
                $related_products = $this->cacheTask($request,$key.'--related')
                                    ->where('id', '!=', $product->id)
                                    ->getData([
                                        'category_id' => $product->category_id,
                                        '@order_by' => 'rand()',
                                        '@limit' => 8
                                    ]);
                $data = compact('page_title', 'product', 'related_products');
                return $this->view('products.detail', $data);
            }
            return $this->view('errors.404');
        });
    }

    /**
     * lấy dữ liệu sản phẩm và đổ về json
     *
     * @param Request $request
     * @return void
     */
    public function getProductJsonData(Request $request)
    {
        extract($this->apiDefaultData);

        $product = $this->cacheUrl($request, false, function() use($request){
            // key truy cap
            $slug = $request->route('slug');
            $key = 'view-product-detail-';
            if($slug){
                $key .= 'slug-'.$slug;
                $args = ['slug' => $slug];
            }elseif($request->id){
                $key .= 'id-'.$request->id;
                $args = ['id' => $request->id];
            }else{
                return ['status' => false, 'errors' => ['404' => "Không tìm thấy"]];
            }
            
            if($product = $this->cacheTask($request, $key)->getProductDetailData($args)){
                $product->applyMeta();
                $product->variants = $product->getVariantAttributes();
                $product->options = $product->getOrderAttributes();
                $product->url = $product->getViewUrl();
                return ['status' => true, 'data' => $product];
            }
            return ['status' => false, 'errors' => ['404' => "Không tìm thấy"]];
        });
        extract($product);
        return $this->json(compact($this->apiSystemVars));
    }

    /**
     * lấy danh sách hoặc tìm kiếm sản phẩm
     *
     * @param Request $request
     * @return \Illuminate\View\View
     */
    public function viewProducts(Request $request)
    {
        // cache view theo url
        return $this->cacheUrl($request, true, function() use($request){
            // yu khoa tim kiem
            $keyword = strlen($request->search)?$request->search:(
                strlen($request->s)?$request->s:(
                    strlen($request->keyword)?$request->keyword:(
                        strlen($request->keywords)?$request->keywords:(
                            strlen($request->tim)?$request->tim:(
                                $request->timkiem
                            )
                        )
                    )
                )
            );
            $sortby = $request->sortby??$request->orderBy;
            $page_title = "Sản phẩm";
            $categoryRoutekeys = [];
            $levelParams = [];
            $idMode = false;
            $args = [];
            $category = null;
            $category_map = [0];
            $key = 'product-list-';
            $ck = $key;
            // trường hợp 1 level (chỉ dùng slug)
            if($slug = $request->route('slug')) $categoryRoutekeys[] = $slug;
            // trường hop74 chỉ có 2 level (parent / child)
            elseif($child = $request->route('child')) $categoryRoutekeys = [$request->route('parent'), $child];
            // trường hợp có đến 4 level
            elseif($fourth = $request->route('fourth')) $categoryRoutekeys = [$request->route('first'), $request->route('second'), $request->route('third'), $fourth];
            // trường hợp chỉ có 3 level 
            elseif($third = $request->route('third')) $categoryRoutekeys = [$request->route('first'), $request->route('second'), $third];
            // nếu chỉ dùng id
            elseif($id = $request->cid??($request->category_id??($request->id??($request->category??$request->cate)))) {$levelParams[] = ['id' => $id]; $idMode = true;}
            // nếu có slug trong route thì foreach qua để lấy slug
            if($categoryRoutekeys){
                foreach ($categoryRoutekeys as $slug) {
                    $levelParams[] = ['slug' => $slug];
                }
            }
            // nếu có danh mục 
            if($levelParams){
                $key = 'product-category-';
                
                $t = count($levelParams);
                $lv = 0;
                $ck = $key;
                
                for ($i=0; $i < $t; $i++) { 
                    $params = $levelParams[$i];
                    // nếu có danh mục được set ở vòng lạp trước thì thêm nó vào danh mục được kích hoạt
                    // và thêm tham số id của nó làm parent_id để truy vấn danh mục hiện tại
                    // .. có thể dùng with được nhưng vẫn mất ngần ấy query thôi
                    
                    // tạo key để lấy cache nếu có
                    $k = $key . '-'. md5(json_encode($params));
                    if($cate = $this->getCategory($request, $k, $params)){
                        $category_map[] = $cate->id;
                        $category = $cate;
                        set_active_model('product_category', $category);$page_title = $category->name;

                        $params['parent_id'] = $category->id;
                        $ck = $k;
                        $lv++;
                    }else {
                        // nếu ko có danh mục tại vòng lặp hiện tại thì thoát khỏi vòng lặp ngay và luôn
                        break;
                    }
                }
                // dd(get_active_model('product_category'));
                // dd($category);
                // nếu level bằng tổng số danh mục
                if($lv==$t){
                    if($idMode) $category_map = array_merge([0],$category->getMap()); 
                    $args = [
                        '@category' => $category->id,
                        '@attribute_category_map' => $category_map
                    ];
                }
                // trường hợp ko có danh mục hiện tại nhưng có danh mục cha
                elseif($category){
                    // tra về view empty luôn
                    $data = compact('category', 'page_title');
                    return $this->viewModule('empty', $data);
                }
                // nếu ko có danh mục nào
                else{
                    return $this->view('errors.404');
                }

            }else{
                $args = [
                    '@attribute_category_map' => $category_map
                ];
            }
            $products = $this->cacheTask($request, $ck)->paginate($this->perPage)->search($request, $keyword, $args);
            // return($results[0]->category);
            if($category){
                $this->breadcrumb->addCategory($category);
            }else{
                $this->breadcrumb->add('Sản phẩm');
            }
            $data = compact('products', 'category', 'page_title', 'keyword', 'idMode', 'sortby');
            return $this->viewModule('list', $data);
        });

    }



    /**
     * kiểm tra giá sản phẩm theo thuộc tính nếu có
     *
     * @param Request $request
     * @return void
     */
    public function checkPrice(Request $request)
    {
        // return $request->all();
        extract($this->apiDefaultData);
        if($productData = $this->repository->checkPrice($request->product_id, is_array($request->attrs)?$request->attrs:[])){
            $status = true;
            $price = $productData['price'];
            $data = [
                'product' => $productData['product'],
                'price' => $price,
                'price_format' => get_currency_format($price)
            ];
        }
        return $this->json(compact($this->apiSystemVars));
    }



    /**
     * lay danh muc
     *
     * @param Request $request
     * @param string $key
     * @param array $args
     * @return \App\Models\Category
     */
    public function getCategory(Request $request, $key, $args = [])
    {
        $category = $this->cacheTask($request, $key, $this->categoryRepository)->mode('mask')->detail($args);
        if($category){
            set_web_data('model_data.product_category.'.$category->id, $category);
        }
        return $category;
    }

    /**
     * lấy danh sách sản phẩm
     *
     * @param Request $request
     * @param string $key
     * @param array $args
     * @return void
     */
    public function getProducts(Request $request, $key, $args = [])
    {
        return $this->cacheTask($request, $key, $this->repository)->search($request, $args);
    }




    /**
     * gửi đánh giá giá sản phẩm
     *
     * @param Request $request
     * @return void
     */
    public function makeReview(Request $request)
    {
        $validator = $this->reviewRepository->validator($request);
        $type = 'danger';
        extract($this->createReview($request));
        if($status){
            $type = 'success';
        }elseif(!$errors){
            $type = 'warning';
        }
        $redirectData = [
            'type' => $type,
            'message' => $message,
            'link' => $this->repository->findBy('id', $request->product_id)->getViewUrl(),
            'text' => 'Quay lại trang sản phẩm'
        ];
        if($errors){
            $redirectData['title'] = $message;
            $redirectData['message'] = implode('
', array_values($errors)); } return redirect()->route('client.alert')->with($redirectData); } /** * gui danh gia bang ajax * * @param Request $request * @return void */ public function ajaxMakeReview(Request $request) { return $this->json($this->createReview($request)); } protected function createReview(Request $request){ extract($this->apiDefaultData); $validator = $this->reviewRepository->validator($request); $status = false; $errors = []; $data = null; if(!$validator){ $message = 'Lỗi không xác định'; }elseif(!$validator->success()){ $message = 'Thiếu thông tin'; $errors = $validator->errors(); }elseif(!($review = $this->reviewRepository->create($validator->inputs()))){ $message = 'Lỗi hẽ thống! Vui lòng thử lại sau giây lát'; }else{ $message = 'Gửi đánh giá thành công!'; $data = $review; $status = true; } return compact($this->apiSystemVars); } }