Автоматическое конвертирование JSON в PHP-объекты
Доброго времени суток! В данной статье я покажу Вам один из способов автоматической конвертации JSON строки в объекты PHP. Т.е. представим следующую ситуацию - у нас есть некий API-сервис (в нашем примере Jsonplaceholder), запросы к которому отдают ответы в JSON-формате. Эти ответы потом нужно преобразовать в нечто такое, с чем мы дальнейшем можем работать в PHP. Но благодаря библиотеке square/pjson и аттрибутам PHP мы можем максимально автоматизировать этот процесс.
Итак, структура проекта следующая:
.
├── src
│ ├── Api
│ │ ├── Api.php
│ │ ├── Endpoint.php
│ │ ├── PhotosEndpoint.php
│ │ ├── PostEndpoint.php
│ │ └── UserEndpoint.php
│ ├── Objects
│ │ ├── Address.php
│ │ ├── Comment.php
│ │ ├── Company.php
│ │ ├── Geo.php
│ │ ├── Photo.php
│ │ ├── Post.php
│ │ └── User.php
│ └── index.php
├── composer.json
├── composer.lock
В папке src/Api у нас будут классы отвечающие за сетевое общение с разными сущностями, будь то статья (Post), пользователь (User) и т.д. В папке src/Objects - сами объекты, в которые мы будем конвертировать JSON-ответ.
Итак, разберем содержимое каждой папки по порядку.
src/Objects/Post.php
<?php
namespace Objects;
// импортируем классы из библиотеки
use Square\Pjson\Json;
use Square\Pjson\JsonSerialize;
class Post
{
// добавляем методы из трейта в класс Post
use JsonSerialize;
// каждое свойство класса Post соответствует свойству из JSON ответа
// аттрибут #[Json] обязателен
#[Json]
public int $id;
#[Json]
public int $userId;
#[Json]
public string $title;
#[Json]
public string $body;
}
Класс выше описывает следующий JSON-ответ:
{
"id": 1,
"userId": 1,
"title": "Post title",
"body": "Post text"
}
Т.е. как видно из примера, когда мы получим ответ от сервера, значения полей этого JSON-ответа станут значениями полей класса автоматически. Причем, типы данных полей класса могут быть не только встроенными в PHP типами, но и пользовательскими типами - классами. Пример ниже:
// User.php
<?php
namespace Objects;
use Square\Pjson\Json;
use Square\Pjson\JsonSerialize;
class User
{
use JsonSerialize;
#[Json]
public int $id;
#[Json]
public string $name;
#[Json]
public string $username;
#[Json]
public string $email;
#[Json]
public Address $address;
#[Json]
public string $phone;
#[Json]
public string $website;
#[Json]
public Company $company;
}
class Company
{
use JsonSerialize;
#[Json]
public string $name;
#[Json]
public string $catchPhrase;
#[Json]
public string $bs;
}
class Address
{
use JsonSerialize;
#[Json]
public string $street;
#[Json]
public string $suite;
#[Json]
public string $city;
#[Json]
public string $zipcode;
#[Json]
public Geo $geo;
}
class Geo
{
use JsonSerialize;
#[Json]
public float $lat;
#[Json]
public float $lng;
}
Теперь рассмотрим классы, отвечающие за взаимодействие с сетью и преобразование JSON-ответов в соответствуюшие объекты. Базовый класс Api\Endpoint заключает в себе базовую логику работы с сетью. В его задачи входит обратиться к удаленному серверу и получить JSON-ответ.
src/Api/Endpoint.php
<?php
namespace Api;
use Exception;
use Objects\Comment;
use Objects\Photo;
use Objects\Post;
class Endpoint
{
/**
* @throws Exception
*/
protected function endpoint(string $part): bool|string
{
$url = sprintf('%s/%s', 'https://jsonplaceholder.typicode.com', $part);
return $this->fetch($url);
}
/**
* @throws Exception
*/
private function fetch($uri): string
{
$handle = curl_init();
curl_setopt($handle, CURLOPT_URL, $uri);
curl_setopt($handle, CURLOPT_POST, false);
curl_setopt($handle, CURLOPT_HEADER, true);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 10);
$response = curl_exec($handle);
$headerLength = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
$httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
$body = substr($response, $headerLength);
if ($httpCode != 200) {
throw new Exception('Network problem', $httpCode);
}
return $body;
}
}
Остальные классы из пространства Api наследуются от него:
Api\PhotosEndpoint
<?php
namespace Api;
use Exception;
use Objects\Photo;
class PhotosEndpoint extends Endpoint
{
/**
* @return array<Photo>
* @throws Exception
*/
public function getPhotos(int $limit = 10): array
{
return array_slice(Photo::listFromJsonString($this->endpoint('photos')), 0, $limit);
}
}
Api\PostEndpoint
<?php
namespace Api;
use Exception;
use Objects\Comment;
use Objects\Post;
class PostEndpoint extends Endpoint
{
/**
* @return array<Post>
* @throws Exception
*/
public function getPosts(int $limit = 10): array
{
return array_slice(Post::listFromJsonString($this->endpoint('posts')), 0, $limit);
}
/**
* @param int $id
* @return Post
* @throws Exception
*/
public function getPost(int $id): Post
{
return Post::fromJsonString($this->endpoint(sprintf('posts/%d', $id)));
}
/**
* @return array<Comment>
* @throws Exception
*/
public function getPostComments(int $id): array
{
return Comment::listFromJsonString($this->endpoint(sprintf('posts/%d/comments', $id)));
}
}
Также есть класс Api\Api, который используется как центральное место для доступа ко всем API-endpoint:
<?php
namespace Api;
class Api
{
public static function posts()
{
return new PostEndpoint();
}
public static function photos()
{
return new Endpoint();
}
public static function users()
{
return new UserEndpoint();
}
}
Вызывается все это дело следующим образом:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
function m_print(string ...$messages): void
{
foreach ($messages as $message) {
print $message;
print PHP_EOL;
}
print PHP_EOL;
print '++++++++++++++++++++++++++++++++' . PHP_EOL;
}
(function()
{
try {
$user = \Api\Api::users()->getUser(1);
m_print($user->toJson(JSON_PRETTY_PRINT));
m_print('Address', $user->address->toJson(JSON_PRETTY_PRINT));
m_print('Address.Geo', $user->address->geo->toJson(JSON_PRETTY_PRINT));
m_print('Company', $user->company->toJson(JSON_PRETTY_PRINT));
}
catch (Exception $e)
{
if(404 === $e->getCode()) print($e->getMessage());
}
})();
Результат:
$ php src/index.php
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "[email protected]",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": -37.3159,
"lng": 81.1496
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
}
++++++++++++++++++++++++++++++++
Address
{
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": -37.3159,
"lng": 81.1496
}
}
++++++++++++++++++++++++++++++++
Address.Geo
{
"lat": -37.3159,
"lng": 81.1496
}
++++++++++++++++++++++++++++++++
Company
{
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
++++++++++++++++++++++++++++++++
Таким образом, с помощью библиотеки square/pjson можно сделать автоматическое преобразование JSONPHP Objects, при этом заметьте, что в Api реализованы только GET-методы, тогда как там же можно добавить еще методы POST, DELETE и другие.
-
- Михаил Русаков
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.