Marshal is serializing / marshalling data structures to the desired format. It is also deserializing / unmarshalling the format back to the data structures.
Especially useful for building the raw response for web services which then can be formatted to JSON for example.
If you need to serialize directly to a format, use the appropriate Marshal library:
Easiest way to install the library is via composer:
composer require kingson-de/marshal-serializer
The following PHP versions are supported:
- PHP 7.0
- PHP 7.1
- PHP 7.2
- PHP 7.3
Just run:
composer test
Or without code coverage:
composer quicktest
The first thing to do is to create a mapper that takes care of mapping your entities / models to the correct format.
You always need to inherit from the abstract Mapper class and implement a map
function with your type hinting.
There is also the option to use directly a callable to map data. This will be explained later.
It is always possible to use a callable in a mapper or vice versa.
<?php
use KingsonDe\Marshal\AbstractMapper;
class UserMapper extends AbstractMapper {
public function map(User $user) {
return [
'username' => $user->getUsername(),
'email' => $user->getEmail(),
'birthday' => $user->getBirthday()->format('Y-m-d'),
'followers' => count($user->getFollowers()),
];
}
}
Next step is to create the desired data structure either being an item/object or a collection.
<?php
use KingsonDe\Marshal\Data\Item;
$item = new Item(new UserMapper(), $user);
<?php
use KingsonDe\Marshal\Data\Collection;
$userCollection = [$user1, $user2, $user3];
$item = new Collection(new UserMapper(), $userCollection);
The final step is to map the data structures to the actual format.
<?php
use KingsonDe\Marshal\Marshal;
$data = Marshal::serialize($item);
You are also not forced to create data structures on your own, you can use the appropriate Marshal functions instead:
<?php
use KingsonDe\Marshal\Marshal;
$data = Marshal::serializeItem($mapper, $model);
// or
$data = Marshal::serializeCollection($mapper, $modelCollection);
// or
$data = Marshal::serializeCollectionCallable(function (User $user) {
return [
'username' => $user->getUsername(),
'email' => $user->getEmail(),
'birthday' => $user->getBirthday()->format('Y-m-d'),
'followers' => count($user->getFollowers()),
];
}, $modelCollection);
<?php
use KingsonDe\Marshal\Data\Item;
use KingsonDe\Marshal\Marshal;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
class UserController extends Controller {
public function indexAction(User $user) {
$item = new Item(new UserMapper(), $user);
$data = Marshal::serialize($item);
return new JsonResponse($data);
}
}
Mappers can even include other mappers with different data structures.
Therefore you can use item
, itemCallable
, collection
or collectionCallable
function from the AbstractMapper class.
<?php
use KingsonDe\Marshal\AbstractMapper;
class UserMapper extends AbstractMapper {
public function map(User $user) {
return [
'username' => $user->getUsername(),
'email' => $user->getEmail(),
'birthday' => $user->getBirthday()->format('Y-m-d'),
'followers' => $this->collection(new FollowerMapper(), $user->getFollowers),
'location' => $this->item(new LocationMapper(), $user->getLocation()),
];
}
}
<?php
use KingsonDe\Marshal\Data\Item;
use KingsonDe\Marshal\Marshal;
$item = new Item(new UserMapper(), $user, $followers, $location);
$data = Marshal::serialize($item);
<?php
use KingsonDe\Marshal\AbstractMapper;
class UserMapper extends AbstractMapper {
public function map(User $user, FollowerCollection $followers, Location $location) {
return [
'username' => $user->getUsername(),
'email' => $user->getEmail(),
'birthday' => $user->getBirthday()->format('Y-m-d'),
'followers' => $this->collection(new FollowerMapper(), $followers),
'location' => $this->item(new LocationMapper(), $location),
];
}
}
For collections the first parameter passed is the one which Marshal will use for iterating.
All other parameters in a collection will stay as it is.
For items/objects all parameters retain.
Collection mappers can discard single item's by returning null
.
<?php
use KingsonDe\Marshal\AbstractMapper;
class UserMapper extends AbstractMapper {
public function map(User $user) {
if ($user->isPrivate()) {
return null;
}
return [
'username' => $user->getUsername(),
];
}
}
To transform the actual format back to your structure use Marshal's deserialize functions. You need a class extending the AbstractObjectMapper which will be passed to the deserialize function.
<?php
use KingsonDe\Marshal\AbstractObjectMapper;
use KingsonDe\Marshal\Data\FlexibleData;
use KingsonDe\Marshal\Example\Model\User;
class UserObjectMapper extends AbstractObjectMapper {
/**
* @inheritdoc
*
* @return User
*/
public function map(FlexibleData $flexibleData, ...$additionalData) {
return new User(
$flexibleData['id'] ?? 0,
$flexibleData['email'] ?? '',
$flexibleData->find('username', '')
);
}
}
<?php
use KingsonDe\Marshal\Marshal;
$data = Marshal::serializeItem(new UserMapper(), $user);
$user = Marshal::deserialize(new UserObjectMapper(), $data);
Another option would be to use the deserializeCallable function.
<?php
use KingsonDe\Marshal\Marshal;
$data = Marshal::serializeItem(new UserMapper(), $user);
$user = Marshal::deserializeCallable(function (FlexibleData $flexibleData) {
return new User(
$flexibleData->get('id'),
$flexibleData->get('email'),
$flexibleData->get('username')
);
}, $data);
This project is released under the terms of the Apache 2.0 license.