Ответы пользователя по тегу Laravel
  • Как сделать unique валидацию столбцов в laravel вместе с другим столбцом?

    pvsaintpe
    @pvsaintpe
    Senior PHP Developer
    для начала исправьте вашу структуру, сделайте поля start / end: timestamp или datetime чтобы те хранили часы тоже, иначе закрыв сессию одним днем, вы не сможете с вашей проверкой открыть сессию повторно в этот же день

    во-вторых, где выхотите валидировать в БД или в Laravel, я тут вижу скрин с базы.

    если у вас Postgres, на уровне БД можно контролировать добавив Exclude constraint

    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Support\Facades\Schema;
    use Umbrellio\Postgres\Schema\Blueprint;
    
    class UpdateSessionsTable extends Migration
    {
        public function up()
        {
            Schema::table('sessions', function (Blueprint $table) {
                $table->datetime('start')->change();
                $table->datetime('end')->change();
                $table
                    ->exclude(['room_id', 'start', 'end'])
                    ->method('gist')
                    ->using('daterange(start, end)', '&&')
                    ->using('room_id', '=');
            });
        }
        ...
    }


    такая миграция добавит в вашу БД такой констрейнт
    ALTER TABLE sessions
        ADD CONSTRAINT sessions_room_id_start_end_excl
            EXCLUDE USING gist (room_id WITH =, daterange(start, end) WITH &&)


    поддержку exclude-ограничений в Laravel можно добавить используя расширение для Postgres:
    composer require umbrellio/laravel-pg-extensions

    Чтобы добавить валидацию в Laravel и можно было ее переиспользовать, вам нужно добавить макрос в Query\Builder сделать это можно так
    use Illuminate\Database\Query\Builder;
    use Illuminate\Support\ServiceProvider;
    
    class QueryServiceProvider extends ServiceProvider
    {
        public function boot()
        {
            Builder::macro(
                'overlaps',
                function ($startDate, $endDate, $startDateColumn, $endDateColumn, $bounds = '[]') {
                    $expression = implode(' && ', [
                        "daterange(?::date, ?::date, '{$bounds}')",
                        "daterange({$startDateColumn}, {$endDateColumn}, '{$bounds}')",
                    ]);
    
                    return $this->whereRaw($expression, [$startDate, $endDate]);
                }
            );
        }
    }


    т.к. в Request ну или где вы делаете валидацию через UniqueDatabaseRule, используется Query\Builder

    ну и в реквесте делайте так
    use Illuminate\Foundation\Http\FormRequest;
    
    class SaveSessionRequest extends FormRequest
    {
        public function rules(): array
        {
            return [
                'session.room_id' => Rule::unique('sessions', 'room_id')->overlaps(
                    $this->input('session.start'),
                    $this->input('session.end'),
                    'start',
                    'end',
                    '[]'
                ),
               // ..
            ];
        }
    
        // ...
    }


    Либо если не хотите макросом, перенести этот where прямо в ваш Request

    А если у вас MySQL, то вам нужно придумать что-то аналогичное, т.к. из коробки MySQL такие финты не поддерживает.
    Ответ написан
    Комментировать