LazyCodet

a

16:17:35 18/6/2025 - 3 views -
Programming

Bagisto - Zero to Hero

Flow request handles of Bagisto

​For example, you access the URL:

https://example.com/admin/settings/users

Request to the system Laravel (index.php)

  • ​File public/index.php is the entry point

  • ​It bootstraps the Laravel app (bootstrap/app.php)

  • ​Then, Laravel handles middleware, routes, v.v​

​Middleware runs for the first time

​The router handles the URL

​Laravel finds the matching route in:

packages/Webkul/Admin/src/Routes/web.php

​In Magisto, if overridden, this route can be reloaded from Customize/Admin/Providers/AdminServiceProvider. For example:

Route::get('admin/catalog/products', [ProductController::class, 'index']);

Controller

​...

Datagrid

​Datagrid in Bagisto is a table, for example, this is a datagrid of the users page:

Pasted Image

​The data and column definition in the class packages\Customize\Admin\src\DataGrids\Settings\UserDataGrid.php

<?php

namespace Customize\Admin\DataGrids\Settings         ;

use Illuminate\Support\Facades\DB         ;
use Illuminate\Support\Facades\Storage         ;
use Webkul\User\Repositories\RoleRepository         ;

use Webkul\Admin\DataGrids\Settings\UserDataGrid          as BaseUserDataGrid;
use Webkul\Core\Repositories\CountryRepository         ;

class UserDataGrid extends BaseUserDataGrid
{
    /**
     * Index.
     *
     * @var string
     */
    protected $primaryColumn = 'user_id';

    /**
     * Constructor for the class.
     *
     * @return void
     */
    public function __construct(protected RoleRepository $roleRepository, protected CountryRepository $countryRepository)
    {
    }

    /**
     * Prepare query builder.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function prepareQueryBuilder()
    {
        $queryBuilder = DB::table('admins')
            ->leftJoin('roles', 'admins.role_id', '=', 'roles.id')
            ->leftJoin('countries', 'admins.country_id', '=', 'countries.id')
            ->select(
                'admins.id as user_id',
                'admins.name as user_name',
                'admins.image as user_image',
                'admins.status',
                'admins.email',
                'roles.name as role_name',
                'countries.name as country_name',
                'admins.whatsapp',
            );

        $this->addFilter('user_id', 'admins.id');
        $this->addFilter('user_name', 'admins.name');
        $this->addFilter('role_name', 'roles.name');
        $this->addFilter('status', 'admins.status');

        return $queryBuilder;
    }

    /**
     * Add columns.
     *
     * @return void
     */
    public function prepareColumns()
    {
        $this->addColumn([
            'index' => 'user_id',
            'label' => trans('admin::app.settings.users.index.datagrid.id'),
            'type' => 'integer',
            'filterable' => true,
            'sortable' => true,
        ]);

        $this->addColumn([
            'index' => 'user_name',
            'label' => trans('admin::app.settings.users.index.datagrid.name'),
            'type' => 'string',
            'searchable' => true,
            'filterable' => true,
            'sortable' => true,
        ]);

        $this->addColumn([
            'index' => 'email',
            'label' => trans('admin::app.settings.users.index.datagrid.email'),
            'type' => 'string',
            'searchable' => true,
            'filterable' => true,
            'sortable' => true,
        ]);

        $this->addColumn([
            'index' => 'user_img',
            'label' => trans('admin::app.settings.users.index.datagrid.name'),
            'type' => 'string',
            'closure' => function ($row) {
                if ($row->user_image) {
                    return Storage::url($row->user_image);
                }

                return null;
            },
        ]);

        $this->addColumn([
            'index' => 'status',
            'label' => trans('admin::app.settings.users.index.datagrid.status'),
            'type' => 'boolean',
            'searchable' => true,
            'filterable' => true,
            'filterable_options' => [
                [
                    'label' => trans('admin::app.settings.users.index.datagrid.active'),
                    'value' => 1,
                ],
                [
                    'label' => trans('admin::app.settings.users.index.datagrid.inactive'),
                    'value' => 0,
                ],
            ],
            'sortable' => true,
            'closure' => function ($value) {
                if ($value->status) {
                    return trans('admin::app.settings.users.index.datagrid.active');
                }

                return trans('admin::app.settings.users.index.datagrid.inactive');
            },
        ]);

        $this->addColumn([
            'index' => 'role_name',
            'label' => trans('admin::app.settings.users.index.datagrid.role'),
            'type' => 'string',
            'searchable' => true,
            'filterable' => true,
            'filterable_type' => 'dropdown',
            'filterable_options' => $this->roleRepository->all(['name as label', 'name as value'])->toArray(),
            'sortable' => true,
        ]);
    }
}

​You can see the header of the table definition in the prepareColumns function, and the data rows definition in the prepareQueryBuilder function

Apply Tailwind Css

​When write code, you maybe face to face with the issue the class name CSS of tailwind does not apply, you need to do:

cd /var/app/primass/packages/Customize/Admin
npm run build

Overwrite Datagrid

​For example, in Bagisto, there is Order Datagrid in packages/Webkul/Admin/src/DataGrids/Customers/View/OrderDataGrid.php , so I clone it to packages/Customize/Admin/src/DataGrids/Orders/OrderDataGrid.php. Now I want to overwrite it to the default OrderDatagrid of Bagisto. How to do it? Let's go!

​In packages/Customize/Admin/src/Providers/AdminServiceProvider.php

namespace Customize\Admin\Providers       ;

use Webkul\Admin\DataGrids\Customers\View\OrderDataGrid        as WebkulOrderDataGrid;
use Customize\Admin\DataGrids\Orders\OrderDataGrid        as CustomizeOrderDataGrid;


class AdminServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        // ...
        $this->app->bind(WebkulOrderDataGrid::class, CustomizeOrderDataGrid::class);
    }
}

Overwrite Languages

​In Bagisto, every module will have a language definition, for example:

📁packages
  📁 Webkul/
    📁 Admin/
      📁 src
        📁 Providers
          📄 AdminServiceProvider.php
        📁 Resources/
          📁 lang/
            📁 en/
              📄 app.php
              📄 validation.php
            📁 ja/
              📄 app.php
              📄 validation.php
    📁 DataTransfer/
      📁 src
        📁 Providers
          📄 DataTransferServiceProvider.php
        📁 Resources/
          📁 lang/
            📁 en/
              📄 app.php
              📄 validation.php
            📁 ja/
              📄 app.php
              📄 validation.php

​When you customize the language of a module, you need to create the structure like this and register it to the service provider, for example:

📁packages
  📁 Customize/
    📁 DataTransfer/
      📁 src
        📁 Providers
          📄 DataTransferServiceProvider.php
        📁 Resources/
          📁 lang/
            📁 en/
              📄 app.php
              📄 validation.php
            📁 ja/
              📄 app.php
              📄 validation.php

​In DataTransferServiceProvider.php

class DataTransferServiceProvider extends ServiceProvider
{
     //...
     /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'data_transfer');
    }
    //...
}

​This will overwrite the language of DataTransfer module

Build a command to go to the definition of the language key

Create app/Console/Commands/FindLangKey.php

<?php

namespace App\Console\Commands     ;

use Illuminate\Console\Command     ;
use File;

class FindLangKey extends Command
{
    protected $signature = 'lang:find {key} {locale=en}';
    protected $description = 'Find the file containing the key lang (including the module)';

    public function handle()
    {
        $key = $this->argument('key');
        $locale = $this->argument('locale');

        $segments = explode('::', $key);
        if (count($segments) !== 2) {
            $this->error('Key is not in correct package format::key.path');
            return;
        }

        [$package, $path] = $segments;
        $pathsToSearch = base_path("packages/Customize/" . ucfirst($package) . "/src/Resources/lang/$locale"     );

        if (!is_dir($pathsToSearch)) {
            $this->error("Lang directory not found at: $pathsToSearch"     );
            return;
        }

        $files = File::allFiles($pathsToSearch);
        foreach ($files as $file) {
            $content = File::getRequire($file->getPathname());
            $pathPartArray = explode('.', $path);
            array_shift($pathPartArray); // bỏ đi phần tử đầu, vì nó là tên file /lang/en/<tên file>.php
            $found = $this->searchKey($content, $pathPartArray);
            if ($found) {
                $fullPath = $file->getPathname();
                $relativePath = str_replace(base_path() . '/', '', $fullPath);

                // Find a line that contains the key
                $lines = file($fullPath);
                $searchString = "'" . end($pathPartArray) . "'";

                $lineNumber = 1;
                foreach ($lines as $index => $line) {
                    if (strpos($line, $searchString) !== false) {
                        $lineNumber = $index + 1;
                        break;
                    }
                }

                $this->info("✅ Key in: {$relativePath}:{$lineNumber}"     );
                return;
            }
        }

        $this->warn("❌ Not found key - " . $pathsToSearch);
    }

    private function searchKey($array, $keys)
    {
        $key = array_shift($keys);
        if (!array_key_exists($key, $array)) return false;

        $value = $array[$key];
        if (empty($keys)) return true;

        if (!is_array($value)) return false;

        return $this->searchKey($value, $keys);
    }
}

​Usage:

php artisan lang:find "admin::app.customers.customers.index.datagrid.suspended"

For example, the ​output:

✅ Key in: packages/Customize/Admin/src/Resources/lang/en/app.php:1775

Knowledge

​Access the attributes of products

​You don't need to join the product table with the attributes table, you can access them directly, for example:

packages/Webkul/Shop/src/Http/Resources/ProductResource.php

public function toArray($request)
    {
        $productTypeInstance = $this->getTypeInstance();

        return [
            'id'          => $this->id,
            'sku'         => $this->sku,
            'name'        => $this->name,
            ...
            ...
            'mileage' => $this->mileage, // <---
            'registration_year' => $this->registration_year, // <---
        ];
    }

Send mail to the customers

​In Bagisto, when a user register an account, it will send a welcome email to the user.

Component Template

packages/Webkul/Shop/src/Resources/views/components/categories/categories.blade.php

<v-shop-category     ></v-shop-category     >

@pushOnce('scripts')
    <script     
        type="text/x-template"
        id="v-shop-category-template"
    >
        <div    >Hello world</div    >   
    </script     >

    <script      type="module">
        app.component('v-shop-category', {
            template: '#v-shop-category-template'
        });
    </script     >
@endPushOnce
​

index.blade.php​

@include('shop::components.categories.categories')

Loading gloabal

​For example, I want create a global variable it can use in anywhere (child component):

packages/Webkul/Shop/src/Resources/assets/js/app.js

...
import { createApp, reactive } from "vue/dist/vue.esm-bundler";

const pageLoadStatus = reactive({
    isPageReady: false
});

/**
 * Main root application registry.
 */
window.app = createApp({
    ...
});
...
[
    ...
].forEach((plugin) => app.use(plugin));

/**
 * Provide pageLoadStatus globally for injection if needed
 */
app.config.globalProperties.$pageLoadStatus = pageLoadStatus;

export default app;

​another page/component

<template     v-cloak v-if="$pageLoadStatus.isPageReady">
	<x-shop::layouts.footer     />
</template    >
getProducts() {
	this.$axios.get(this.src)
	.then(response => {
		...
		this.$pageLoadStatus.isPageReady = true;
	});
},

Check Which is current route ?

data() {
   return {
     fromBuyNow: @json(request()->routeIs('shop.checkout.onepage.index'))
  }
}

​11. Modal Proxy

​Ở bagisto có 1 cái gọi là các module proxy, giả sử ở modal

class Cart extends Model implements CartContract
{
    public function shipping(): \Illuminate\Database\Eloquent\Relations\HasOne
    {
        return $this->hasOne(CartShippingProxy::modelClass());
    }
}

packages/Webkul/Checkout/src/Models/CartShippingProxy.php

<?php

namespace Webkul\Checkout\Models;

use Konekt\Concord\Proxies\ModelProxy;

class CartShippingProxy extends ModelProxy {}

packages/Webkul/Checkout/src/Providers/ModuleServiceProvider.php

<?php

namespace Webkul\Checkout\Providers;

use Webkul\Core\Providers\CoreModuleServiceProvider;

class ModuleServiceProvider extends CoreModuleServiceProvider
{
    /**
     * Models.
     *
     * @var array
     */
    protected $models = [
        ...
        \Webkul\Checkout\Models\CartShipping::class,
    ];
}