WithDatastorePrimaryKeyDecorator Trait

The WithDatastorePrimaryKeyDecorator trait provides automatic implementations of the DatastoreHasPrimaryKey interface by delegating to a $handler property. It includes both the base Datastore methods and the find() method for primary key lookups.

What It Provides

This trait implements four methods:

All methods delegate to $this->handler.

Requirements

To use this trait, your class must:

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

Basic Usage

<?php

use PHPNomad\Datastore\Interfaces\DatastoreHasPrimaryKey;
use PHPNomad\Datastore\Interfaces\DatastoreHandlerHasPrimaryKey;
use PHPNomad\Datastore\Traits\WithDatastorePrimaryKeyDecorator;

final class PostDatastore implements DatastoreHasPrimaryKey
{
    use WithDatastorePrimaryKeyDecorator;

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

This datastore now supports get(), save(), delete(), and find() with zero boilerplate.

Generated Code

The trait generates code equivalent to:

final class PostDatastore implements DatastoreHasPrimaryKey
{
    private DatastoreHandlerHasPrimaryKey $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);
    }

    public function find(int $id): Model
    {
        return $this->handler->find($id);
    }
}

When to Use This Trait

Use WithDatastorePrimaryKeyDecorator when:

When NOT to Use This Trait

Don't use this trait if you need to:

In these cases, implement the methods manually.

Example: Custom Logic in find()

If you need custom behavior in find(), implement it manually and let the trait handle the rest:

final class PostDatastore implements DatastoreHasPrimaryKey
{
    use WithDatastorePrimaryKeyDecorator;

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

    // Override find() with logging
    public function find(int $id): Model
    {
        $this->logger->info("Finding post: {$id}");
        return $this->handler->find($id);
    }

    // get(), save(), delete() provided by trait
}

Combining with Other Decorator Traits

Most datastores implement multiple interfaces. You can combine traits:

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

final class PostDatastoreImpl implements PostDatastore
{
    use WithDatastorePrimaryKeyDecorator;  // get(), save(), delete(), find()
    use WithDatastoreWhereDecorator;       // where()
    use WithDatastoreCountDecorator;       // count()

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

All six methods are now auto-implemented via traits.

Adding Custom Business Methods

You can add custom methods alongside trait-provided ones:

interface PostDatastore extends DatastoreHasPrimaryKey
{
    public function findPublishedPosts(int $authorId): iterable;
}

final class PostDatastoreImpl implements PostDatastore
{
    use WithDatastorePrimaryKeyDecorator;

    public function __construct(
        private DatastoreHandlerHasPrimaryKey $handler
    ) {}

    // Custom business method
    public function findPublishedPosts(int $authorId): iterable
    {
        return $this->handler->get([
            'author_id' => $authorId,
            'status' => 'published',
        ]);
    }

    // get(), save(), delete(), find() provided by trait
}

Handler Type Requirements

The $handler property must implement DatastoreHandlerHasPrimaryKey:

interface DatastoreHandlerHasPrimaryKey extends DatastoreHandler
{
    public function find(int $id): Model;
}

This ensures the handler supports primary key lookups.

Best Practices

What's Next