Datastore Interface

The Datastore interface is the foundational contract for all data access in PHPNomad. It defines three core operations—get(), save(), and delete()—that form the basis of every datastore implementation. Every other datastore interface extends from this one.

Interface Definition

interface Datastore
{
    /**
     * Retrieves a collection of models based on the provided criteria.
     *
     * @param array $args Filtering criteria (implementation-defined)
     * @return iterable<Model> Collection of models matching the criteria
     */
    public function get(array $args = []): iterable;

    /**
     * Persists a model to storage (create or update).
     *
     * @param Model $item The model to save
     * @return Model The saved model (may include generated IDs or timestamps)
     */
    public function save(Model $item): Model;

    /**
     * Removes a model from storage.
     *
     * @param Model $item The model to delete
     * @return void
     */
    public function delete(Model $item): void;
}

Methods

get(array $args = []): iterable

Retrieves a collection of models matching the provided criteria.

Parameters:

Returns:

Common $args patterns:

// Filter by a single field
$posts = $datastore->get(['author_id' => 123]);

// Multiple conditions (AND logic, typically)
$posts = $datastore->get([
    'author_id' => 123,
    'status' => 'published'
]);

// Limit results
$posts = $datastore->get(['limit' => 10]);

// Pagination
$posts = $datastore->get(['limit' => 10, 'offset' => 20]);

// Empty args = all records
$posts = $datastore->get();

When to use:

Example:

// Get all published posts by a specific author
$posts = $postDatastore->get([
    'author_id' => 123,
    'status' => 'published'
]);

foreach ($posts as $post) {
    echo $post->title . "\n";
}

save(Model $item): Model

Persists a model to storage. This method handles both create and update operations—implementations typically determine which based on whether the model has a primary key.

Parameters:

Returns:

Behavior:

Example: Creating a new record

$newPost = new Post(
    id: null, // No ID yet
    title: 'My First Post',
    content: 'Hello world!',
    authorId: 123,
    publishedDate: new DateTime()
);

$savedPost = $postDatastore->save($newPost);

echo $savedPost->id; // Now has an auto-generated ID

Example: Updating an existing record

$existingPost = $postDatastore->find(42);

// Models are immutable, so we create a new instance with updated fields
$updatedPost = new Post(
    id: $existingPost->id,
    title: 'Updated Title',
    content: $existingPost->content,
    authorId: $existingPost->authorId,
    publishedDate: $existingPost->publishedDate
);

$postDatastore->save($updatedPost);

When to use:


delete(Model $item): void

Removes a model from storage.

Parameters:

Returns:

Behavior:

Example:

$post = $postDatastore->find(42);

$postDatastore->delete($post);

// Post 42 is now removed from storage

When to use:


Usage Patterns

Basic CRUD Operations

The Datastore interface provides everything needed for simple create-read-update-delete operations:

// CREATE
$newPost = new Post(null, 'Title', 'Content', 123, new DateTime());
$savedPost = $datastore->save($newPost);

// READ
$posts = $datastore->get(['author_id' => 123]);

// UPDATE
$updatedPost = new Post(
    $savedPost->id,
    'New Title',
    $savedPost->content,
    $savedPost->authorId,
    $savedPost->publishedDate
);
$datastore->save($updatedPost);

// DELETE
$datastore->delete($updatedPost);

Working with Immutable Models

PHPNomad models are immutable—once created, their properties cannot change. To "update" a model, you create a new instance with the changed values:

$post = $datastore->find(42);

// Create new instance with updated title
$updatedPost = new Post(
    id: $post->id,
    title: 'New Title',  // Changed
    content: $post->content,
    authorId: $post->authorId,
    publishedDate: $post->publishedDate
);

$datastore->save($updatedPost);

This ensures data consistency and makes debugging easier (you always know where state changes).

Filtering Semantics

The $args parameter in get() is implementation-defined, but most implementations follow these conventions:

For complex queries (OR conditions, comparisons like > or LIKE), use DatastoreHasWhere instead.

Extending the Base Interface

Most datastores extend Datastore with additional capabilities:

interface PostDatastore extends Datastore, DatastoreHasPrimaryKey
{
    // Inherits get(), save(), delete()
    // Adds find() from DatastoreHasPrimaryKey
    
    // Custom business methods can be added
    public function findPublishedPosts(int $authorId): iterable;
}

See Datastore Interfaces Overview for extension patterns.

Implementation Notes

When implementing this interface:

What's Next