Поля JSON становятся все более популярными, теперь они официально поддерживаются в MySQL 5.7.8. Их даже использует популярный пакет Spatie Laravel Medialibrary, так почему бы и нам не попробовать? В этом уроке мы покажем как это сделать.
Допустим, у нас есть интернет-магазина и нам нужно хранить в базе товары. Но мы не знаем какие у них будут свойства, для некоторых потребуются размеры, для других — цвет, страна и производитель и т.д. И вот здесь поле JSON может быть очень полезно — мы будем хранить там любые данные о свойствах товаров.
Вот форма для нашего товара:
Примечание : для упрощения я не сделал динамические поля в форме, это уже за пределами нашей статьи, просто жестко задал 5 полей.
Шаг 1. Бэкэнд: Миграция + Модель
Чтобы создать поле JSON, нам нужно в миграции Laravel использовать метод ->json():
Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->decimal('price', 15, 2); $table->json('properties'); $table->timestamps(); $table->softDeletes(); });
Далее нам нужно указать нашей модели app/Product.php автоматически преобразовать это поле из JSON формата в массив:
class Product extends Model { protected $casts = [ 'properties' => 'array' ];
Таким образом, мы сразу получим $product->properties в виде массива, без необходимости конвертирования через json_decode().
Шаг 2. Форма Blade c массивом
Как я говорил выше, я добавил пять полей для свойств. Пользователь может заполнить как одно из них так и все пять:
<form action="{{ route("admin.products.store") }}" method="POST"> @csrf <div class="form-group"> <label for="name">Name</label> <input type="text" name="name" class="form-control"> </div> <div class="form-group"> <label for="price">Price*</label> <input type="number" name="price" class="form-control" step="0.01"> </div> <div class="form-group"> <label for="properties">Properties</label> <div class="row"> <div class="col-md-2"> Key: </div> <div class="col-md-4"> Value: </div> </div> @for ($i=0; $i <= 4; $i++) <div class="row"> <div class="col-md-2"> <input type="text" name="properties[{{ $i }}][key]" class="form-control" value="{{ old('properties['.$i.'][key]') }}"> </div> <div class="col-md-4"> <input type="text" name="properties[{{ $i }}][value]" class="form-control" value="{{ old('properties['.$i.'][value]') }}"> </div> </div> @endfor </div> <div> <input class=" btn btn-danger" type="submit"> </div> </form>
Как видите, обычный цикл @for и пары ключ-значение в массиве от 0 до 4.
Шаг 3. Сохранение свойств
Наш метод ProductController::store() будет простым.
public function store(StoreProductRequest $request) { $product = Product::create($request->all()); return redirect()->route('admin.products.index'); }
Да, всё так. Валидация обязательных полей в StoreProductRequest, ничего сложного. Нам не нужно ничего делать с нашим полем JSON, так как мы передаем массив из Blade и он будет автоматически приведен к JSON.
Вот как это будет выглядеть в базе данных:
У нас только одна проблема. В этом примере я не проверяю пустые значения, поэтому они по-прежнему будут храниться в JSON, как показано ниже — посмотрите на два последних значения:
[ {"key":"Size","value":"XL"}, {"key":"Color","value":"Blue"}, {"key":"Country","value":"China"}, {"key":null,"value":null}, {"key":null,"value":null} ]
Чтобы избежать этого, нам нужно исключить значения null из массива. Я использую мутатор Eloquent и преобразую массив в модели app/Product.php:
public function setPropertiesAttribute($value) { $properties = []; foreach ($value as $array_item) { if (!is_null($array_item['key'])) { $properties[] = $array_item; } } $this->attributes['properties'] = json_encode($properties); }
Примечание: я догадываюсь, что есть более элегантные способы выполнения этой операции с массивом, но подумал, что в этой статье не очень уместно искать решение для массива в одну строку.
Шаг 4. Отображение свойств
В таблице товаров мы, вероятно, захотим показать что-то вроде этого:
Для этого мы просто сделаем @foreach в ячейке <td> файла resources/views/products/index.blade.php:
@foreach($products as $product) <tr> <td> {{ $product->name ?? '' }} </td> <td> {{ $product->price ?? '' }} </td> <td> @foreach ($product->properties as $property) <b>{{ $property['key'] }}</b>: {{ $property['value'] }}<br /> @endforeach </td>
Шаг 5. Редактирование/обновление свойств
Форма редактирования будет иметь такую же структуру, только значения полей будут установлены из массива. Вот часть файла resources/views/products/edit.blade.php:
<div class="form-group"> <label for="properties">Properties</label> <div class="row"> <div class="col-md-2"> Key: </div> <div class="col-md-4"> Value: </div> </div> @for ($i=0; $i <= 4; $i++) <div class="row"> <div class="col-md-2"> <input type="text" name="properties[{{ $i }}][key]" class="form-control" value="{{ $product->properties[$i]['key'] ?? '' }}"> </div> <div class="col-md-4"> <input type="text" name="properties[{{ $i }}][value]" class="form-control" value="{{ $product->properties[$i]['value'] ?? '' }}"> </div> </div> @endfor </div>
В контроллере с помощью метода update(), а он очень похож на метод store() — мы просто используем все запросы для обновления данных.
public function update(UpdateProductRequest $request, Product $product) { $product->update($request->all()); return redirect()->route('admin.products.index'); }
Вот и всё, JSON — это просто, не так ли?
Автор: Povilas Korop
Перевод: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.