Skip to main content

Usage Examples and Best Practices

Best Practices

1. Using a Constructor with Property Promotion

The best practice is to use the constructor to declare public properties via promotion. This simplifies object creation and makes the code cleaner and more understandable.

When properties are declared at the class level and we rely solely on the from() method, we lose flexibility and direct control over the object creation process. This approach may seem attractive due to its simplicity, but it limits our ability to directly interact with the object via its constructor, especially when actions like validation or data transformation are needed.

Bad Practice Example:

class User extends Data {
public string $name;
public int $age;
}

$data = [
'name' => 'John Doe',
'age' => '30'
];

// Creating the object via from
$user = User::from($data);

Here, we lose the ability to explicitly control the creation process, which can lead to confusion if additional actions are required during object creation, such as validation or data transformation.

Good Practice Example:

class User extends Data {
public function __construct(
public string $name,
public int $age
) {}
}

$data = [
'name' => 'John Doe',
'age' => 30
];

// Creating the object using the constructor
$user = new User(name: $data['name'], age: $data['age']);

Here, we maintain direct control over the object creation process and can easily manage its behavior during instantiation.

2. Combining Attributes for a Single Field

You can combine several attributes for a single field. This gives you powerful capabilities for data transformation, especially when multiple processing stages are needed.

Example: if we need to parse the user field from user_id and use a parser that finds the user by this ID, we can apply both ParseFrom and ParseBy attributes together.

Example:

use Looqey\Speca\Data;
use Looqey\Speca\Attributes\ParseBy;
use Looqey\Speca\Attributes\ParseFrom;
use Looqey\Speca\Contracts\Transformer;

class SomeAggregate extends Data {
public function __construct(
#[ParseBy(UserIdToUserParser::class)]
#[ParseFrom('user_id')]
public User $user
) {}
}

class UserIdToUserParser implements Transformer {
public function transform(mixed $value, Property $property): mixed {
// Find the user by ID
return User::findById($value);
}
}

$data = [
'user_id' => '12345'
];

$user = SomeAggregate::from($data);

Here, the ParseFrom attribute indicates that the user field data comes from the user_id field, and the ParseBy attribute uses a parser that finds the user by ID. This allows you to combine parsing rules while keeping the logic flexible and scalable.

3. Caching for Repeated Queries

If attributes or parsers can reuse data (for example, a user with the same user_id), consider implementing caching for such data at the input level. For instance, a request-level storage could be used to load the user object once by ID and return it when constructing other Data objects, significantly speeding up the processing.

Example:

class UserIdToUserParser implements Transformer {
private static array $cache = [];

public function transform(mixed $value, Property $property): mixed {
// If the object is already in the cache, return it
if (isset(self::$cache[$value])) {
return self::$cache[$value];
}

// If the object is not in the cache, load it and save it to the cache
$user = User::findById($value);
self::$cache[$value] = $user;
return $user;
}
}

This approach is effective when working with data that repeats frequently and helps avoid redundant queries or operations.

4. Managing Inclusions and Exclusions

When dealing with "heavy" or lazy data, it is crucial to manage which fields should be included or excluded from serialization efficiently. Using the include and exclude methods helps you control which data should be included in the output, allowing you to save resources.

Example:

$user = new User('John Doe', 30);
$user->include('profile'); // Include profile in the output
$user->exclude('password'); // Exclude password from the output

$data = $user->toArray();

This method allows you to include or exclude data as needed, which is perfect for working with large or sensitive data.