Простой CRUD на Laravel + Sleeping Owl для чайников

05 ноября vetrov

Основная цель применения уже готовой админки - упростить создание типовых вещей. И собственно в процессе изучения Laravel и админки Sleeping Owl первой задачей после разграничения доступа к админке является умение создать CRUD, хотя бы самый простейший.

Дабы не убивать в себе мотивацию бесполезным крудом сделаем полезный. Я работаю на крупном проекте (в роли PM) с адовым количеством функций и разделов в админке, среди всего хардкора есть очень простой раздел, который выручает очень сильно - "список базовых настроек портала". Мы там ввключаем/отключаем глобальные функции, меняем скорость обновления каких либо кешей, списки емайлов для рассылок делаем и много чего еще. Вот этот раздел "список базовых настроек портала" - по сути является простым и тупейшим интерфейсом для функций.

Подобный список настроек будет полезен во всех без исключения проектах и скорее всего существует в той или иной форме. По сути это простой круд. Его то и сделаем в качестве примера круда. В официальной документации не так подробно описано, а я являюсь чайником в программировании, поэтому будут описывать всё в подробностях.

Исходные данные (были лично у меня, не все обязательно)

  • Laravel 5.3
  • MySQL
  • PhpStorm 2016.2
  • macOS 10.12 Sierra
  • MAMP (php 5.6)
  • Развёрнутый проект
  • Настроенный простейший ограничитель входа в админку
  • Установленную Sleeping Owl, которая открывается по роуту yoursite.loc/admin и там 2 тестовых пункта меню видно
  • Настроенный GIT на битбакет

Подготовка

Для упрощения работы по советам из интернетов установил следующие пакеты, которые упростят вывод ошибок вместо стандартных непонятных серых и безликих ларавельских:

Выполняем команды (убедитесь что админка и сайт открываются без ошибок ларавельских):

  1. composer require graham-campbell/exceptions

  2. composer require filp/whoops --dev

  3. Далее, в файле config/app.php добавляем сервис-провайдер GrahamCampbell\Exceptions\ExceptionsServiceProvider::class в блок providers.

  4. После этого, в файле: App\Exceptions\Handler.php в блоке use меняем: use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; на use GrahamCampbell\Exceptions\NewExceptionHandler as ExceptionHandler;

  5. Командуем: composer global require "hirak/prestissimo:^0.3"

Всё, теперь у нас есть модные, сочные и великолепные ошибки.


Миграции

Наши настройки назовем "Fundamental Settings", скорее всего в проекте боьлшом или малом будут другие настройки и разделы и нужно фундаментальные (базовые) отличать от настроек всех остальных разделов, а что бы гарантированно отличать и ставлю приставку Fundamental

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFundamentalSettingsTable extends Migration
{
    /**
     * Run the migrations.
     * @return void
     */
    public function up()
    {
        Schema::create('fundamental_settings', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 100)->comment('Человеческое название настройки/параметра. Например "Email-ы для оповещений"');
            $table->string('var', 50)->comment('параметр, на который завязаться в коде. Изменять его уже нельзя ибо пото искать все вхождения в код.');
            $table->string('value', 254)->comment('Значение параметра (список почт, цифровое значение и тп.)');
            $table->string('description', 254)->comment('Описание параметра для вывода в админке в качестве подказки');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('fundamental_settings');
    }
}

Обратите внимание, что к миграциям добавляем еще комментарий: ->comment('Примечание для наследников'); ибо это поможет потом разобраться что к чему и почему = экономия времени.


Создаем модель

Как хотите так и создавайте. Я сделал в директории /app/Models, для админки наверно стоило бы другую папку завести, но пока для понимания работы пусть будет тут.

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class FundamentalSetting extends Model
{
    protected $table = 'fundamental_settings';
    //что разрешаем править
    protected $fillable = [
        'name', 'var', 'value', 'description',
    ];
}

Подготовка к созданию секции:

Для начала необходимо связать существующие Eloquent модели с классами, которые будут конфигурациями раздела.

Описание производится через добавление строк в параметр $sections, где в качестве ключа мы указываем название нашей модели, в качестве значения - класс который будет отвечать за конфигурацию.

Если переформулировать для простого понимания: В этом файле: /app/Providers/AdminSectionsServiceProvider.php в секции $sections слева указываем модель, справа указываем куда нужно положить и потом обращаться саму секцию которая будет сгенерирована:

namespace App\Providers;
use SleepingOwl\Admin\Providers\AdminSectionsServiceProvider as ServiceProvider;

class AdminSectionsServiceProvider extends ServiceProvider
{
    /**
     * @var array
     */
    protected $sections = [
        \App\Models\FundamentalSetting::class => 'App\Http\Sections\fundamentalSettings',
    ];

    /**
     * Register sections.
     * @return void
     */
    public function boot(\SleepingOwl\Admin\Admin $admin)
    {
        parent::boot($admin);
    }
}

Создаём секцию по заветам Sleeping OWl

php artisan sleepingowl:section:generate

Там она совсем пустая создасться. Нам не интересно такую ничего не умеющую смотреть, поэтому приведу заполненный методом тыка секцию /app/Http/Sections/FundamentalSettings.php:

namespace App\Http\Sections;

use SleepingOwl\Admin\Contracts\DisplayInterface;
use SleepingOwl\Admin\Contracts\FormInterface;
use SleepingOwl\Admin\Section;

use AdminColumn;
use AdminDisplay;
use AdminForm;
use AdminFormElement;
use SleepingOwl\Admin\Contracts\Initializable;

class fundamentalSettings extends Section implements Initializable
{
    /**
     * @var \App\Models\fundamentalSetting
     */
    protected $model = '\App\Models\fundamentalSetting';

    /**
     * Initialize class.
     */
    public function initialize()
    {
        // Добавление пункта меню и счетчика кол-ва записей в разделе
        $this->addToNavigation($priority = 500, function() {
            return \App\Models\FundamentalSetting::count();
        });

        $this->creating(function($config, \Illuminate\Database\Eloquent\Model $model) {
            //...
        });
    }

    /**
     * @var bool
     */
    protected $checkAccess = false;

    /**
     * Заголовок раздела и название пункта в меню
     * @var string
     */
    protected $title = 'Базовые настройки';

    /**
     * URL по которому будет доступен раздел
     * @var string
     */
    protected $alias = 'settings';

    /**
     * @return Первичная отображаемая таблица
     */
    public function onDisplay()
    {
        return AdminDisplay::table()/*->with('users')*/
            ->setHtmlAttribute('class', 'table-primary')
            ->setColumns(
                AdminColumn::text('id', '#')->setWidth('30px'),
                AdminColumn::link('name', 'Настройка')->setWidth('200px'),
                AdminColumn::text('value', 'Значение'),
                AdminColumn::text('description', 'Описание')
            )->paginate(20);
    }

    /**
     * @param int $id
     * @return FormInterface
     */
    public function onEdit($id)
    {
        // поле var - нельзя редактировать, ибо нефиг системообразующий код редактировать
        return AdminForm::panel()->addBody([
            AdminFormElement::text('name', 'Название настройки')->required(),
            AdminFormElement::text('value', 'Значение')->required(),
            AdminFormElement::textarea('description', 'Описание'),
            AdminFormElement::text('id', 'ID')->setReadonly(1),
            AdminFormElement::text('created_at')->setLabel('Создано')->setReadonly(1),

        ]);
    }

    /**
     * @return FormInterface
     */
    public function onCreate()
    {
        //return $this->onEdit(null);
        // а вот создать var можно. Один раз и навсегда
        return AdminForm::panel()->addBody([
            AdminFormElement::text('name', 'Название настройки')->required(),
            AdminFormElement::text('var', 'Постоянный системный код')->required(),
            AdminFormElement::text('value', 'Значение')->required(),
            AdminFormElement::textarea('description', 'Описание'),

        ]);

    }

    /**
     * @return void
     */
    public function onDelete($id)
    {
        // todo: remove if unused
    }

    /**
     * @return void
     */
    public function onRestore($id)
    {
        // todo: remove if unused
    }

    //заголовок для создания записи
    public function getCreateTitle()
    {
        return 'Создание базовой настройки';
    }

    // иконка для пункта меню - шестеренка
    public function getIcon()
    {
        return 'fa fa-gear';
    }
}

Заходим в админку

Видим результат:

1478373098195.jpg

Создается новая запись:

1478373253221.jpg

И в целом потыкать можно что получилось.


Возможные проблемы

В ходе работы столкнулся с такой проблемой, что ларавел отказывался видеть мой класс FundamentalSetting:

ошибка

В качестве решения укажем класс в автолаоде:

composer.json

    . . .

    "autoload-dev": {
        "classmap": [
            "tests/TestCase.php",
            "app/Models/fundamentalSettings.php"
        ]
    },
    . . .

Командуем: composer dump-autoload

В целом есть ощущение, что это костыль.

 

Правильный способ решения:

На самом деле что бы Laravel правильно подтянуд все классы, нужно правильно называть файлы и классы. Особое внимание обратить на наличие/отсутствие буквы s в конце названий классов/файлов классов и на большую/маленькую букву с которой начинается файл/класс.

Теперь исправить ошибку из примера выше сможете сами немного поэксперементировав (или тупо прочитав текст ошибки) с названием файла/класса. Готовое решение ошибки специально не привожу :-)


Вот собственно и всё. Дальнейшее использование этих записей - отдельная история.

Спасибо за внимание. 05.11.2016 manual made by Evgeny Vetrov