Skip to content

Advanced Components

This guide covers the advanced components that can be used in a Botble CMS plugin.

Source Code

The src directory contains the PHP source code for your plugin, organized into subdirectories by purpose.

Models

Models represent database tables and define relationships between entities. They are located in the src/Models directory.

Example (src/Models/Item.php):

php
<?php

namespace Botble\Foo\Models;

use Botble\Base\Casts\SafeContent;
use Botble\Base\Enums\BaseStatusEnum;
use Botble\Base\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Item extends BaseModel
{
    protected $table = 'foo_items';

    protected $fillable = [
        'name',
        'description',
        'content',
        'status',
        'user_id',
        'image',
    ];

    protected $casts = [
        'status' => BaseStatusEnum::class,
        'name' => SafeContent::class,
        'description' => SafeContent::class,
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(\Botble\ACL\Models\User::class, 'user_id');
    }
}

Controllers

Controllers handle HTTP requests and return responses. They are located in the src/Http/Controllers directory.

Example (src/Http/Controllers/ItemController.php):

php
<?php

namespace Botble\Foo\Http\Controllers;

use Botble\Base\Events\CreatedContentEvent;
use Botble\Base\Events\DeletedContentEvent;
use Botble\Base\Events\UpdatedContentEvent;
use Botble\Base\Http\Controllers\BaseController;
use Botble\Base\Http\Responses\BaseHttpResponse;
use Botble\Foo\Forms\ItemForm;
use Botble\Foo\Http\Requests\ItemRequest;
use Botble\Foo\Models\Item;
use Botble\Foo\Tables\ItemTable;
use Exception;
use Illuminate\Http\Request;

class ItemController extends BaseController
{
    public function index(ItemTable $table)
    {
        $this->pageTitle(trans('plugins/foo::items.name'));

        return $table->renderTable();
    }

    public function create()
    {
        $this->pageTitle(trans('plugins/foo::items.create'));

        return ItemForm::create()->renderForm();
    }

    public function store(ItemRequest $request, BaseHttpResponse $response)
    {
        $item = Item::query()->create($request->validated());

        event(new CreatedContentEvent(FOO_MODULE_SCREEN_NAME, $request, $item));

        return $response
            ->setPreviousUrl(route('foo.index'))
            ->setNextUrl(route('foo.edit', $item->id))
            ->setMessage(trans('core/base::notices.create_success_message'));
    }

    public function edit(Item $item)
    {
        $this->pageTitle(trans('plugins/foo::items.edit') . ' "' . $item->name . '"');

        return ItemForm::createFromModel($item)->renderForm();
    }

    public function update(Item $item, ItemRequest $request, BaseHttpResponse $response)
    {
        $item->update($request->validated());

        event(new UpdatedContentEvent(FOO_MODULE_SCREEN_NAME, $request, $item));

        return $response
            ->setPreviousUrl(route('foo.index'))
            ->setMessage(trans('core/base::notices.update_success_message'));
    }

    public function destroy(Item $item, Request $request, BaseHttpResponse $response)
    {
        try {
            $item->delete();

            event(new DeletedContentEvent(FOO_MODULE_SCREEN_NAME, $request, $item));

            return $response->setMessage(trans('core/base::notices.delete_success_message'));
        } catch (Exception $exception) {
            return $response
                ->setError()
                ->setMessage($exception->getMessage());
        }
    }
}

Requests

Requests handle validation of form submissions. They are located in the src/Http/Requests directory.

Example (src/Http/Requests/ItemRequest.php):

php
<?php

namespace Botble\Foo\Http\Requests;

use Botble\Base\Enums\BaseStatusEnum;
use Botble\Support\Http\Requests\Request;
use Illuminate\Validation\Rule;

class ItemRequest extends Request
{
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'description' => 'nullable|string|max:400',
            'content' => 'nullable|string',
            'status' => Rule::in(BaseStatusEnum::values()),
            'image' => 'nullable|string|max:255',
        ];
    }

    public function attributes(): array
    {
        return [
            'name' => trans('plugins/foo::items.form.name'),
            'description' => trans('plugins/foo::items.form.description'),
            'content' => trans('plugins/foo::items.form.content'),
            'status' => trans('core/base::tables.status'),
            'image' => trans('core/base::forms.image'),
        ];
    }
}

Forms

Forms handle the creation and editing of models. They are located in the src/Forms directory.

Example (src/Forms/ItemForm.php):

php
<?php

namespace Botble\Foo\Forms;

use Botble\Base\Enums\BaseStatusEnum;
use Botble\Base\Forms\FieldOptions\TextFieldOption;
use Botble\Base\Forms\FieldOptions\TextareaFieldOption;
use Botble\Base\Forms\FieldOptions\SelectFieldOption;
use Botble\Base\Forms\FieldOptions\MediaImageFieldOption;
use Botble\Base\Forms\Fields\TextField;
use Botble\Base\Forms\Fields\TextareaField;
use Botble\Base\Forms\Fields\SelectField;
use Botble\Base\Forms\Fields\MediaImageField;
use Botble\Base\Forms\Fields\EditorField;
use Botble\Base\Forms\FormAbstract;
use Botble\Foo\Http\Requests\ItemRequest;
use Botble\Foo\Models\Item;

class ItemForm extends FormAbstract
{
    public function buildForm(): void
    {
        $this
            ->setupModel(new Item())
            ->setValidatorClass(ItemRequest::class)
            ->withCustomFields()
            ->add('name', TextField::class, TextFieldOption::make()
                ->label(trans('core/base::forms.name'))
                ->placeholder(trans('core/base::forms.name_placeholder'))
                ->required()
            )
            ->add('description', TextareaField::class, TextareaFieldOption::make()
                ->label(trans('core/base::forms.description'))
                ->placeholder(trans('core/base::forms.description_placeholder'))
                ->rows(4)
            )
            ->add('content', EditorField::class, [
                'label' => trans('core/base::forms.content'),
                'placeholder' => trans('core/base::forms.content_placeholder'),
                'rows' => 4,
            ])
            ->add('status', SelectField::class, SelectFieldOption::make()
                ->label(trans('core/base::tables.status'))
                ->choices(BaseStatusEnum::labels())
            )
            ->add('image', MediaImageField::class, MediaImageFieldOption::make()
                ->label(trans('core/base::forms.image'))
            )
            ->setBreakFieldPoint('status');
    }
}

Tables

Tables handle the display of data in the admin panel. They are located in the src/Tables directory.

Example (src/Tables/ItemTable.php):

php
<?php

namespace Botble\Foo\Tables;

use Botble\Base\Enums\BaseStatusEnum;
use Botble\Foo\Models\Item;
use Botble\Table\Abstracts\TableAbstract;
use Botble\Table\Actions\DeleteAction;
use Botble\Table\Actions\EditAction;
use Botble\Table\BulkActions\DeleteBulkAction;
use Botble\Table\Columns\CreatedAtColumn;
use Botble\Table\Columns\IdColumn;
use Botble\Table\Columns\NameColumn;
use Botble\Table\Columns\StatusColumn;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;

class ItemTable extends TableAbstract
{
    public function setup(): void
    {
        $this
            ->model(Item::class)
            ->addActions([
                EditAction::make()
                    ->route('foo.edit'),
                DeleteAction::make()
                    ->route('foo.destroy'),
            ]);
    }

    public function columns(): array
    {
        return [
            IdColumn::make(),
            NameColumn::make()->route('foo.edit'),
            CreatedAtColumn::make(),
            StatusColumn::make(),
        ];
    }

    public function buttons(): array
    {
        return $this->addCreateButton(route('foo.create'));
    }

    public function bulkActions(): array
    {
        return [
            DeleteBulkAction::make()->permission('foo.destroy'),
        ];
    }

    public function getFilters(): array
    {
        return [
            'name' => [
                'title' => trans('core/base::tables.name'),
                'type' => 'text',
                'validate' => 'required|string|max:120',
            ],
            'status' => [
                'title' => trans('core/base::tables.status'),
                'type' => 'select',
                'choices' => BaseStatusEnum::labels(),
                'validate' => 'required|in:' . implode(',', BaseStatusEnum::values()),
            ],
            'created_at' => [
                'title' => trans('core/base::tables.created_at'),
                'type' => 'date',
            ],
        ];
    }
}

Creating Custom Database Seeders

Seeders are useful for populating your database with test data or default content. To create a custom seeder for your plugin:

  1. Create a seeder class in the database/seeders directory
  2. Register the seeder in your plugin's service provider

Example seeder registration in your service provider:

php
if (app()->environment() !== 'production') {
    $this->app->register(
        \Botble\Foo\Database\Seeders\FooSeederProvider::class
    );
}

Creating Commands

You can add custom Artisan commands to your plugin by creating command classes in the src/Commands directory.

Example command class (src/Commands/GenerateFooCommand.php):

php
<?php

namespace Botble\Foo\Commands;

use Illuminate\Console\Command;

class GenerateFooCommand extends Command
{
    protected $signature = 'foo:generate {name : The name of the item}';

    protected $description = 'Generate a new foo item';

    public function handle(): int
    {
        $name = $this->argument('name');

        // Your command logic here
        $this->info("Generated new foo item: {$name}");

        return self::SUCCESS;
    }
}

Register the command in your service provider:

php
if ($this->app->runningInConsole()) {
    $this->commands([
        \Botble\Foo\Commands\GenerateFooCommand::class,
    ]);
}

Creating Events and Listeners

Events and listeners allow you to decouple various aspects of your application. To add events to your plugin:

  1. Create event classes in the src/Events directory
  2. Create listener classes in the src/Listeners directory
  3. Register them in an event service provider

Example event (src/Events/FooCreated.php):

php
<?php

namespace Botble\Foo\Events;

use Botble\Foo\Models\Item;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class FooCreated
{
    use Dispatchable, SerializesModels;

    public function __construct(public Item $item)
    {
    }
}

Example listener (src/Listeners/SendFooCreatedNotification.php):

php
<?php

namespace Botble\Foo\Listeners;

use Botble\Foo\Events\FooCreated;

class SendFooCreatedNotification
{
    public function handle(FooCreated $event): void
    {
        // Send notification logic here
    }
}

Register in an event service provider (src/Providers/EventServiceProvider.php):

php
<?php

namespace Botble\Foo\Providers;

use Botble\Foo\Events\FooCreated;
use Botble\Foo\Listeners\SendFooCreatedNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        FooCreated::class => [
            SendFooCreatedNotification::class,
        ],
    ];
}

Then register this provider in your main service provider:

php
public function register(): void
{
    $this->app->register(EventServiceProvider::class);
}

Creating Middleware

Middleware provides a convenient mechanism for filtering HTTP requests entering your application. To add middleware to your plugin:

  1. Create middleware classes in the src/Http/Middleware directory
  2. Register them in your service provider

Example middleware (src/Http/Middleware/CheckFooPermission.php):

php
<?php

namespace Botble\Foo\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class CheckFooPermission
{
    public function handle(Request $request, Closure $next)
    {
        if (!Auth::user()->hasPermission('foo.index')) {
            return redirect()->route('dashboard.index');
        }

        return $next($request);
    }
}

Register in your service provider:

php
protected function registerMiddlewares(): void
{
    $this->app['router']->aliasMiddleware('foo.permission', \Botble\Foo\Http\Middleware\CheckFooPermission::class);
}

public function boot(): void
{
    // ...
    $this->registerMiddlewares();
}

Creating Facades

Facades provide a static interface to classes that are available in the application's service container. To create a facade for your plugin:

  1. Create a facade class in the src/Facades directory
  2. Register the underlying class in your service provider

Example facade (src/Facades/FooHelper.php):

php
<?php

namespace Botble\Foo\Facades;

use Botble\Foo\Supports\FooHelper as FooHelperSupport;
use Illuminate\Support\Facades\Facade;

/**
 * @method static array getItems(array $args = [])
 *
 * @see \Botble\Foo\Supports\FooHelper
 */
class FooHelper extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'foo-helper';
    }
}

Implementation class (src/Supports/FooHelper.php):

php
<?php

namespace Botble\Foo\Supports;

use Botble\Foo\Models\Item;

class FooHelper
{
    public function getItems(array $args = []): array
    {
        return Item::query()
            ->where('status', 'published')
            ->orderBy('created_at', 'DESC')
            ->with($args['with'] ?? [])
            ->limit($args['limit'] ?? 10)
            ->get()
            ->toArray();
    }
}

Register in your service provider:

php
public function register(): void
{
    $this->app->bind('foo-helper', function () {
        return new FooHelper();
    });
}