Products CRUD

Everything is the same as creating CRUD for Brands except few things. We will only discuss NEW things here.

Database migration for products table:

php artisan make:model Product -m
public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();

        $table->string('image')->nullable();
        $table->string('name')->unique();
        $table->foreignId('brand_id');
        $table->decimal('price');
        $table->boolean('is_active')->default(true);

        SpeedAdminHelpers::createdByUpdatedByMigrations($table);

        $table->timestamps();
    });
}

Database migration for "category_product" table for many_to_many relation between products and categories

php artisan make:migration create_table_category_product --create=category_product
public function up()
{
    Schema::create('category_product', function (Blueprint $table) {
        $table->id();
        $table->foreignId('product_id');
        $table->foreignId('category_id');
        $table->timestamps();
    });
}

Add relations in Product Model

Product.php
public function brand()
{
    return $this->belongsTo(Brand::class);
}

public function categories()
{
    return $this->belongsToMany(Category::class);
}

Add getGridQuery($request) function to product model

As shown in the image above, in the products grid (datatable) we want to show tje following columns:

  • Product name

  • Image

  • Brand name

  • Categories names

  • Price

  • Categories count

  • Active

We also want to make sure that we can search products by brand_name or categories names. To do this, we need to modify our query. Our getGridQuery() function will look like following.

File: Product.php
...
public function getGridQuery($request)
{
    $query = $this->with(['brand', 'categories'])
        ->leftJoin('brands', 'brands.id', '=', 'products.brand_id')
        ->leftJoin('category_product', 'category_product.product_id', '=', 'products.id')
        ->leftJoin('categories', 'categories.id', '=', 'category_product.category_id')
        ->select([
            'products.id',
            'products.image',
            'products.name',
            'products.brand_id',
            'products.price',
            'products.is_active', 
            'brands.name as brand_name', 
            \DB::raw('count(categories.id) as categories_count')
        ])
        ->groupBy([
            'products.id',
            'products.image',
            'products.name',
            'products.brand_id',
            'products.price',
            'products.is_active',
            'brands.name',
        ]);

    return $query;
}
...

Add Grid Columns

File: Product.php
public function __construct()
{
    ...

    $this->addGridColumns();

    ...
}

public function addGridColumns()
{
    $this->addGridColumn([
        'id' => 'image', 
        'title' => __('Image'), 
        'order_by' => 'products.image',
        'render_function' => function ($product) {
            return GridHelper::renderImage($product->image);
        }
    ]);
    $this->addGridColumn([
        'id' => 'name', 
        'title' => __('Name'),
        'order_by' => 'products.name', 
        'search_by' => 'products.name', 
        'render_function' => function ($product) {
            $default_locale = config('speed-admin.default_model_locale');
            return $product->name->{$default_locale};
        }
    ]);
    $this->addGridColumn([
        'id' => 'brand', 
        'title' => __('Brand'),
        'order_by' => 'brands.name',
        'search_by' => 'brands.name',
        'render_function' => function ($product) {
            return $product->brand_name;
        }
    ]);
    $this->addGridColumn([
        'id' => 'categories', 
        'title' => __('Categories'),
        'search_by' => 'categories.name', 
        'render_function' => function ($product) {
            $html = '';
            foreach ($product->categories as $category) {
                $html .= '<span class="badge badge-primary p-1 m-1">'.$category->name.'</span>';
            }
            return $html;
        }
    ]);
    $this->addGridColumn([
        'id' => 'price', 
        'title' => __('Price'),
        'order_by' => 'products.price', 
        'search_by' => 'products.price', 
        'render_function' => function ($product) {
            return $product->price;
        }
    ]);
    $this->addGridColumn([
        'id' => 'categories_count', 
        'title' => __('Categories Count'), 
        'order_by' => 'categories_count',
        'render_function' => function ($product) {
            return $product->categories_count;
        }
    ]);
    $this->addGridColumn([
        'id' => 'is_active', 
        'title' => __('Active'),
        'order_by' => 'products.is_active',
        'render_function' => function ($product) {
            return GridHelper::renderBoolean($product->is_active);
        }
    ]);
}

Add form fields

All fields are the same as shown in Brands CRUD tutorial except for the following two fields (see image below):

  • Select field for selecting Brand

  • Select field for selecting multiple categories

File: Product.php
public function __construct()
{
    ...

    $this->addFormFields();

    ...
}

public function addFormFields()
{
    $this->addFormItem([
        'id' => 'main-row',
        'type' => 'div',
        'class' => 'row'
    ]);
    $this->addFormItem([
        'id' => 'left-col',
        'parent_id' => 'main-row',
        'type' => 'div',
        'class' => 'col-md-4'
    ]);
    $this->addFormItem([
        'id' => 'right-col',
        'parent_id' => 'main-row',
        'type' => 'div',
        'class' => 'col-md-8'
    ]);

    $this->addFormItem([
        'id' => 'image',
        'parent_id' => 'left-col',
        'type' => 'image',
        'label' => __('Image'),
        'name' => 'image',
        'upload_path' => 'products',
        'validation_rules' => ['image' => 'required|image|max:2048'],
    ]);

    $this->addFormItem([
        'id' => 'name',
        'parent_id' => 'right-col',
        'type' => 'text',
        'validation_rules' => ['name' => 'required|unique:products,name,{{$id}}'],
        'label' => __('Name'),
        'name' => 'name'
    ]);

    $this->addFormItem([
        'id' => 'price',
        'parent_id' => 'right-col',
        'type' => 'decimal',
        'validation_rules' => ['price' => 'required|numeric'],
        'label' => __('Price'),
        'name' => 'price'
    ]);

    $this->addFormItem([
        'id' => 'brand',
        'parent_id' => 'right-col',
        'type' => 'belongsTo',
        'relation_name' => 'brand',
        'model' => '\App\Models\Brand',
        'where' => function($query){
            return $query->where('is_active', 1);
        },
        'show_add_new_button' => true,
        'validation_rules' => ['brand' => 'required'],
        'label' => __('Brand'),
        'name' => 'brand'
    ]);

    $this->addFormItem([
        'id' => 'categories',
        'parent_id' => 'right-col',
        'type' => 'belongsToMany',
        'relation_name' => 'categories',
        'model' => '\App\Models\Category',
        'where' => function($query){
            return $query->where('is_active', 1);
        },
        'show_select_from_table_button' => true,
        'show_add_new_button' => true,
        'validation_rules' => ['categories' => 'required|array|min:1'],
        'label' => __('Categories'),
        'name' => 'categories'
    ]);

    $this->addFormItem([
        'id' => 'is_active',
        'parent_id' => 'right-col',
        'type' => 'checkbox',
        'label' => __('Active'),
        'name' => 'is_active',
    ]);
}

Last updated