Laravel и использование поля JSON в MySQL

Laravel и использование поля JSON в MySQL

Поля 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
Перевод: Demiurge Ash