laravel: мощный json с помощью jsonSerialize()

laravel делает довольно хорошую работу по прозрачному переводу моделей в json до тех пор, пока вы сохраняете простоту. однако эта простота использования довольно быстро разрушается, как только вы решаете внести значительные изменения в ваши представления json. если вы когда-нибудь сталкивались с борьбой hidden против visible, или возились с casts или accessors, вы, вероятно, понимаете.

К счастью, laravel предоставляет простой способ определить json-сериализацию модели в одном методе: jsonSerialize().

зачем использовать jsonSerialize?

хотя это и не «канонический» способ сериализации, jsonSerialize() обеспечивает как мощность, так и улучшенную читаемость.

Используя jsonSerialize(), мы можем добавлять, удалять или изменять любое поле в нашей модели, не прибегая к смешиванию различных значений конфигурации. а поскольку наше определение json находится в одном методе, наш код стал немного более читабельным.

пролет

мы можем многое сделать с помощью jsonSerialize, но в этой статье мы рассмотрим только основы, а именно:

  • простая реализация для изменения структуры json и возвращаемых данных
  • включение отношений в наш json
  • подклассификация моделей для создания новых схем сериализации

структура данных

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

Наш проект — это простой api для просмотра коллекции виниловых пластинок. структура данных состоит из records, причем каждая запись имеет еще одну songs.

api имеет три конечные точки:

миграции для наших таблиц показаны в нижней части статьи.

в остальной части статьи мы рассмотрим модели и контроллеры.

простой формат json с помощью jsonSerialize

первая модель, которую мы рассмотрим, это Song.php.

До добавления jsonSerialize модель выглядела следующим образом:

app/Models/Song.php

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Song extends Model
{
    use HasFactory;

    protected $table = 'songs';
}
Вход в полноэкранный режим Выйти из полноэкранного режима

когда мы вызываем конечную точку GET /api/songs, чтобы вернуть все песни в нашей базе данных, результат выглядит следующим образом:

[
  {
    "id": 1,
    "record_id": 1,
    "title": "Bait and Switch",
    "duration_seconds": 148,
    "created_at": "2022-05-02T22:32:01.000000Z",
    "updated_at": "2022-05-02T22:32:01.000000Z"
  },
  {
    "id": 2,
    "record_id": 1,
    "title": "Sneaking into yer House",
    "duration_seconds": 80,
    "created_at": "2022-05-02T22:32:14.000000Z",
    "updated_at": "2022-05-02T22:32:14.000000Z"
  },
  {
    "id": 3,
    "record_id": 1,
    "title": "Dippity Do-Nut",
    "duration_seconds": 112,
    "created_at": "2022-05-03T11:16:25.000000Z",
    "updated_at": "2022-05-03T11:16:25.000000Z"
  }
]
Вход в полноэкранный режим Выход из полноэкранного режима

Это не очень хороший результат. Давайте исправим это с помощью jsonSerialize()!

В нижней части нашей модели Song мы добавим этот метод:

    /**
     * Define the json format of the model
     */
    public function jsonSerialize():Array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            // cast seconds to minutes and seconds
            'duration' => gmdate("i:s", $this->duration_seconds),
            // a new value
            'long_enough_for_radio_play' => $this->duration_seconds > 120 ? true: false,
        ];
    } // jsonSerialize
Войти в полноэкранный режим Выйти из полноэкранного режима

давайте посмотрим, что здесь происходит.

Сначала мы объявим публичный метод jsonSerialize(). Этот метод вызывается каждый раз, когда эта модель сериализуется в json как для отдельных объектов, так и для коллекций.

В примере выше мы явно включили id и title, но опустили обе временные метки записи. В результате id и title отображаются в нашем json, но, например, created_at нет.

Мы также видим, что применили преобразование к столбцу duration_seconds для преобразования секунд в минуты и секунды, присвоив его ключу duration, и добавили новый ключ/значение long_enough_for_radio_play, чтобы указать, что песня длится более двух минут.

Это один из самых мощных аспектов jsonSerialize(): мы можем применять приведения и преобразования к любому столбцу, переименовывать ключи и даже вводить новые пары ключ/значение.

Когда мы снова вызываем GET /api/songs, наш вывод json выглядит следующим образом:

[
  {
    "id": 1,
    "title": "Bait and Switch",
    "duration": "02:28",
    "long_enough_for_radio_play": true
  },
...
]
Вход в полноэкранный режим Выход из полноэкранного режима

Наши новые ключи на месте, а временные метки исчезли!

составные объекты с помощью jsonSerialize

До сих пор мы рассматривали сериализацию простых объектов. теперь перейдем к составным объектам: объектам, которые содержат другие объекты.

Давайте рассмотрим модель для Record:

app/Models/Record.php

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsHasMany;

class Record extends Model
{
    use HasFactory;

    protected $table = 'records';

    /**
     * Returns all songs for record
     *
     * @return HasMany
     */
    protected function songs():HasMany
    {
        return $this->hasMany('AppModelsSong', 'record_id', 'id')->orderBy('id', 'asc');
    }

    /**
     * Define the json format of the model
     */
    public function jsonSerialize():Array
    {
        return [
            'id' => $this->id,
            'artist' => $this->artist,
            'title' => $this->title,
            'songs' => $this->songs, // array of songs
        ];
    } // jsonSerialize
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Поскольку записи содержат песни, мы хотим, чтобы наша модель Record включала массив моделей Song этой записи. Для этого нужно сделать две вещи.

Во-первых, мы напишем функцию songs(). Эта функция создает отношение hasMany() к songs и возвращает его. Если вы уже много работали с eloquent, это должно быть знакомо.

Затем, в нашем методе jsonSerialize(), мы включаем эти песни, вызывая метод songs(). В результате мы получаем массив песен с ключом songs.

Следует отметить, что формат json всех этих песен определяется собственным методом Song модели jsonSerialize(). очень удобно!

Как только мы это напишем, наша конечная точка GET /api/records/{id}, которая возвращает Record::all(), даст нам результат, который выглядит следующим образом:

{
  "id": 1,
  "artist": "Emily's Sassy Lime",
  "title": "Dippity Do-Nut",
  "songs": [
    {
      "id": 1,
      "title": "Bait and Switch",
      "duration": "02:28",
      "long_enough_for_radio_play": true
    },
    {
      "id": 2,
      "title": "Sneaking into yer House",
      "duration": "01:20",
      "long_enough_for_radio_play": false
    },
    {
      "id": 3,
      "title": "Dippity Do-Nut",
      "duration": "01:52",
      "long_enough_for_radio_play": false
    }
  ]
}
Перейдите в полноэкранный режим Выход из полноэкранного режима

подклассификация для разных форматов

иногда нам нужны две разные схемы json для одной и той же модели. например, если мы хотим написать конечную точку, которая возвращает список всех наших записей, нам, вероятно, нужен массив более мелких объектов; «дайджест». возможно, нам нужны только поля artist, title и id.

Мы можем сделать это, подклассифицировав нашу модель Record и перегрузив jsonSerialize. Сделав нашу новую модель, назовем ее RecordDigest, подклассом Record, мы сможем получить доступ ко всем методам и данным суперкласса, если захотим, но сериализация json по умолчанию будет нашей новой моделью. давайте посмотрим на модель RecordDigest.

app/Models/RecordDigest.php

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsHasMany;

class RecordDigest extends Record
{

    /**
     * Define the json format of the model
     */
    public function jsonSerialize():Array
    {
        return [
            'id' => $this->id,
            'artist' => $this->artist,
            'title' => $this->title,
        ];
    } // jsonSerialize
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это совершенно новая модель, но поскольку она наследуется от Record, она по-прежнему обращается к таблице records, и у нас по-прежнему есть доступ к методу songs(), если мы захотим его использовать.

Если мы напишем конечную точку GET /api/records, которая получит RecordDigest::all(), мы увидим, что наш вывод json выглядит следующим образом:

[
  {
    "id": 1,
    "artist": "Emily's Sassy Lime",
    "title": "Dippity Do-Nut"
  },
  {
    "id": 2,
    "artist": "Bratmobile",
    "title": "Pottymouth"
  }
]
Вход в полноэкранный режим Выход из полноэкранного режима

именно то, что мы хотели.

заключение

С помощью jsonSerialize() можно сделать многое. Я часто использую его как способ введения данных hateoas в мои возвраты, или для адаптации данных или структуры json в зависимости, скажем, от роли доступа пользователя. Создавая такие формализованные способы определения нашего json, мы получаем не только мощь, но и последовательность и улучшенную читабельность.

дополнительный код

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

models

Song.php

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Song extends Model
{
    use HasFactory;

    protected $table = 'songs';

    /**
     * Define the json format of the model
     */
    public function jsonSerialize():Array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            // cast seconds to minutes and seconds
            'duration' => gmdate("i:s", $this->duration_seconds),
        ];
    } // jsonSerialize
}
Вход в полноэкранный режим Выход из полноэкранного режима

Record.php

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsHasMany;

class Record extends Model
{
    use HasFactory;

    protected $table = 'records';

    /**
     * Returns all songs for record
     *
     * @return HasMany
     */
    protected function songs():HasMany
    {
        return $this->hasMany('AppModelsSong', 'record_id', 'id')->orderBy('id', 'desc');
    }

    /**
     * Define the json format of the model
     */
    public function jsonSerialize():Array
    {
        return [
            'id' => $this->id,
            'artist' => $this->artist,
            'title' => $this->title,
            'songs' => $this->songs, // array of songs
        ];
    } // jsonSerialize
}
Войти в полноэкранный режим Выход из полноэкранного режима

RecordDigest.php

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsHasMany;

class RecordDigest extends Record
{

    /**
     * Define the json format of the model
     */
    public function jsonSerialize():Array
    {
        return [
            'id' => $this->id,
            'artist' => $this->artist,
            'title' => $this->title,
        ];
    } // jsonSerialize
}
Вход в полноэкранный режим Выход из полноэкранного режима

контроллер

RecordController.php

<?php

namespace AppHttpControllersapi;

use IlluminateHttpRequest;
use IlluminateHttpJsonResponse;
use AppHttpControllersController;

use AppModelsSong;
use AppModelsRecord;
use AppModelsRecordDigest;

class RecordController extends Controller
{
    /**
     * Get all songs
     *
     * @return JsonResponse
     */
    public function getAllSongs(Request $request):JsonResponse
    {
        $songs = Song::all();

        return response()->json($songs, 200);
    }

    /**
     * Get one record
     *
     * @return JsonResponse
     */
    public function getRecord(Int $id, Request $request):JsonResponse
    {
        $records = Record::find($id);

        return response()->json($records, 200);
    }

    /**
     * Get all records as digests
     *
     * @return JsonResponse
     */
    public function getAllRecords(Request $request):JsonResponse
    {
        $records = RecordDigest::all();

        return response()->json($records, 200);
    }
Вход в полноэкранный режим Выход из полноэкранного режима

маршруты

routes/api.php

Route::get('songs', 'AppHttpControllersapiRecordController@getAllSongs');
Route::get('records', 'AppHttpControllersapiRecordController@getAllRecords');
Route::get('records/{id}', 'AppHttpControllersapiRecordController@getRecord');
Вход в полноэкранный режим Выйти из полноэкранного режима

миграции

миграция записей

<?php

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

class CreateRecordsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('records', function (Blueprint $table) {
            $table->id();
            $table->string('artist');
            $table->string('title');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('records');
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

миграция песен

<?php

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

class CreateSongsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('songs', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('record_id');
            $table->foreign('record_id')->references('id')->on('records');
            $table->string("title");
            $table->integer("duration_seconds");
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('songs');
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Оцените статью
Procodings.ru
Добавить комментарий