Skip to main content

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 is Data, no further arguments are required.
  • parser is similar to the single ParseBy attribute for the array element.
  • serializer is similar to the single SerializeBy 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:

  1. Retrieve the valid field value (ParseFrom).
  2. 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"