WithDatastoreDecorator Trait

The WithDatastoreDecorator trait provides automatic implementations of the base Datastore interface methods by delegating to a $handler property. It eliminates boilerplate code when your Core datastore implementation is a pure pass-through to a handler.

What It Provides

This trait implements three methods:

Each method simply forwards the call to $this->handler with the same parameters.

Requirements

To use this trait, your class must:

  1. Implement Datastore — the trait provides the method bodies.
  2. Have a $handler property — must be of type DatastoreHandler.
  3. Initialize the handler — typically via constructor injection.

Basic Usage

<?php

use PHPNomad\Datastore\Interfaces\Datastore;
use PHPNomad\Datastore\Interfaces\DatastoreHandler;
use PHPNomad\Datastore\Traits\WithDatastoreDecorator;

final class PostDatastore implements Datastore
{
    use WithDatastoreDecorator;

    public function __construct(
        private DatastoreHandler $handler
    ) {}
}

That's it. The trait provides get(), save(), and delete() automatically.

Generated Code

The trait generates code equivalent to:

final class PostDatastore implements Datastore
{
    private DatastoreHandler $handler;

    public function get(array $args = []): iterable
    {
        return $this->handler->get($args);
    }

    public function save(Model $item): Model
    {
        return $this->handler->save($item);
    }

    public function delete(Model $item): void
    {
        $this->handler->delete($item);
    }
}

By using the trait, you avoid writing this repetitive delegation code.

When to Use This Trait

Use WithDatastoreDecorator when:

When NOT to Use This Trait

Don't use WithDatastoreDecorator if you need to:

In these cases, implement the methods manually.

Example: Custom Logic in save()

If you need custom behavior in one method, implement it manually and use the trait for the others:

final class PostDatastore implements Datastore
{
    use WithDatastoreDecorator {
        save as private traitSave; // Rename trait's save method
    }

    public function __construct(
        private DatastoreHandler $handler,
        private LoggerStrategy $logger
    ) {}

    // Custom save with logging
    public function save(Model $item): Model
    {
        $this->logger->info("Saving post: {$item->getId()}");
        return $this->traitSave($item); // Delegate to trait
    }

    // get() and delete() provided by trait
}

Alternatively, just implement save() yourself and let the trait handle get() and delete():

final class PostDatastore implements Datastore
{
    use WithDatastoreDecorator;

    public function __construct(
        private DatastoreHandler $handler,
        private LoggerStrategy $logger
    ) {}

    // Custom save with logging
    public function save(Model $item): Model
    {
        $this->logger->info("Saving post: {$item->getId()}");
        return $this->handler->save($item);
    }

    // get() and delete() provided by trait
}

Combining with Other Decorator Traits

You can use multiple decorator traits together when your interface extends multiple capabilities:

interface PostDatastore extends 
    Datastore,
    DatastoreHasPrimaryKey,
    DatastoreHasCounts
{
    // get(), save(), delete(), find(), count()
}

final class PostDatastoreImpl implements PostDatastore
{
    use WithDatastoreDecorator;        // get(), save(), delete()
    use WithDatastorePrimaryKeyDecorator {
        // Resolve conflict: both traits provide get(), save(), delete()
        WithDatastorePrimaryKeyDecorator::get insteadof WithDatastoreDecorator;
        WithDatastorePrimaryKeyDecorator::save insteadof WithDatastoreDecorator;
        WithDatastorePrimaryKeyDecorator::delete insteadof WithDatastoreDecorator;
    }
    use WithDatastoreCountDecorator;   // count()

    public function __construct(
        private DatastoreHandlerHasPrimaryKey & DatastoreHandlerHasCounts $handler
    ) {}
}

Note: In practice, you'd typically use only WithDatastorePrimaryKeyDecorator since it extends WithDatastoreDecorator and includes all base methods. The example above shows how to resolve conflicts if needed.

Handler Type Requirements

The $handler property must implement DatastoreHandler:

interface DatastoreHandler
{
    public function get(array $args = []): iterable;
    public function save(Model $item): Model;
    public function delete(Model $item): void;
}

Most handlers extend this interface with additional capabilities (e.g., DatastoreHandlerHasPrimaryKey), which is fine—the trait only calls the base methods.

Best Practices

What's Next