Поля 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>
Форма редактирования будет иметь такую же структуру, только значения полей будут установлены из массива. Вот часть файла 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
Перевод: Demiurge Ash