Attributes
Speca uses PHP attributes to manage the rules for instantiating and serializing object fields. Let's look at the existing attributes from the perspective of their intended purpose.
Helper Declaration
Set
Declares that the property is an array of elements of a specified type.
Arguments
of
specifies the target type of the array elements. If the type isData
, no further arguments are required.parser
is similar to the singleParseBy
attribute for the array element.serializer
is similar to the singleSerializeBy
attribute for the array element.
Example
Consider declaring a set of elements for a class that is not a Data object:
class CompositionTag {
public function __construct(
public string $id,
public string $name
)
{}
}
class Composition extends Data {
public function __construct(
#[Set(of: CompositionTag::class,
parser: CompositionTagParser::class,
serializer: CompositionTagSerializer::class)]
public array $tags
) {}
}
class CompositionTagParser implements Transformer {
public function transform(mixed $value, Property $property): mixed {
return new CompositionTag(
id: $value['id'],
name: $value['name']
);
}
}
class CompositionTagSerializer implements Transformer {
public function transform(mixed $value, Property $property): mixed {
// Just making sure..
assert($value instanceof CompositionTag);
// And returning the array
return [
'id' => $value->id,
'name' => $value->name
];
}
}
A lot of boilerplate, right? Let's simplify it!
class CompositionTag extends Data {
public function __construct(
public string $id,
public string $name
)
{}
}
class Composition extends Data {
public function __construct(
#[Set(of: CompositionTag::class)]
public array $tags
) {}
}
That's it — we just used Data
in the CompositionTag
class. Thanks to this, the array will be automatically assembled according to the rules described in the CompositionTag
class.
Parsing
ParseBy
Assigns a property parser.
Example
use Looqey\Speca\Data;
use Looqey\Speca\Attributes\ParseBy;
class User extends Data {
public function __construct(
#[ParseBy(NameArrayToStringParser::class)]
public string $name;
)
{}
}
use Looqey\Speca\Contracts\Transformer;
use Looqey\Speca\Core\Property;
class NameArrayToStringParser implements Transformer {
public function transform(mixed $value, Property $property): mixed {
return $value['first_name'] . ' ' . $value['surname'];
}
}
$data = [
'name' => [
'first_name' => 'John',
'surname' => 'Doe'
]
];
$it = User::from($data);
// $it->name = 'John Doe'
Special Cases
Using with a constructor without property promotion
A custom parser will be applied to the property BEFORE it is passed to the constructor:
class User extends Data {
#[ParseBy(NameArrayToStringParser::class)]
public string $name;
public function __construct(string $name) {
// In $name we will get "John Doe"!
$this->name = 'rough '.$name;
}
}
ParseFrom
Defines possible sources for parsing a field. For example:
use Looqey\Speca\Data;
use Looqey\Attributes\ParseFrom;
class Painting extends Data {
public function __construct(
#[ParseFrom('technique_description', 'techniqueDescription', 'technique')]
public string $techniqueDescription;
)
{}
}
When instantiating an object using the from
method, Speca will take the first existing parameter from the input data (fallback scheme). When using this attribute on a field, its original name is not considered (it won't be used to search for the value in the data source).
$data = [
// This field will be used to populate techniqueDescription by default
'technique_description' => 'Sfumato creates soft transitions, layered glazes add depth, light shapes form.',
// If the previous field didn't exist, this field would be used
'techniqueDescription' => 'Fine hatching adds texture, layered washes create depth, contrast defines form.',
// ..similar to the previous field
'technique' => 'Glazes build depth, soft blending smooths edges, light and shadow shape form.',
];
$it = Painting::from($data);
Interaction
Attributes can be used together. This will proceed in the following order:
- Retrieve the valid field value (
ParseFrom
). - Parse the field value (
ParseTo
).
Serialization
SerializeBy
Assigns a property serializer.
use Looqey\Speca\Data;
class User extends Data {
public function __construct(
#[SerializeBy(NameSerializer::class)]
public string $name
)
{}
}
use Looqey\Speca\Contracts\Transformer;
use Looqey\Speca\Core\Property;
class NameSerializer implements Transformer {
public function transform(mixed $value, Property $property): mixed {
$parts = explode(' ', $value);
return ['first' => $parts[0], 'last' => $parts[1] ?? ''];
}
}
$data = [
'name' => 'John Doe'
];
$user = User::from($data);
$output = $user->toArray();
// $output['name'] will be an array with keys "first" and "last"