0

find(), all(), where() — delegating to the Query Builder

Advanced5 min read·fw-08-002
sql

Concept

find(), all(), where() are the primary query methods on the Model base class. They delegate to the Query Builder and hydrate results back into model instances.

find(int|string $id): ?static: Fetch a single model by primary key. SELECT * FROM users WHERE id = ? LIMIT 1. Returns null if not found. findOrFail() throws ModelNotFoundException if not found.

all(): array: Fetch all rows. Returns an array of model instances. Avoid on large tables — use chunking instead.

where(string $column, string $operator, mixed $value): QueryBuilder: Starts a query with a WHERE constraint. Returns a Query Builder (not a collection yet) — chain more methods or call get().

get(): array: Executes the query and returns an array of model instances. Calls hydrate() on each row.

first(): ?static: Execute and return first result or null. firstOrFail() throws if null.

Proxying to Query Builder: The Model needs to forward method calls to its Query Builder. The clean way: static::where(...) returns a Builder instance. The Builder knows the model class and uses hydrate() to convert DB rows to model instances.

Scope: static::query() returns a fresh Builder pre-configured with the table and model class. All query methods start from there.

Code Example

php
<?php
namespace Framework\Orm;

use Framework\Database\QueryBuilder;

abstract class Model
{
    // ... from fw-08-001

    // --- Static query methods ---

    public static function all(): array
    {
        return static::query()->get();
    }

    public static function find(int|string $id): ?static
    {
        return static::query()->where(static::newInstance()->primaryKey, '=', $id)->first();
    }

    public static function findOrFail(int|string $id): static
    {
        $model = static::find($id);
        if ($model === null) {
            throw new ModelNotFoundException(static::class, $id);
        }
        return $model;
    }

    public static function where(string $column, string $operator, mixed $value): ModelQueryBuilder
    {
        return static::query()->where($column, $operator, $value);
    }

    public static function query(): ModelQueryBuilder
    {
        return new ModelQueryBuilder(static::$connection, static::getTable(), static::class);
    }

    protected static function newInstance(array $attributes = []): static
    {
        return new static($attributes);
    }

    public static function create(array $attributes): static
    {
        $model = new static($attributes);
        $model->save();
        return $model;
    }
}

class ModelQueryBuilder extends QueryBuilder
{
    public function __construct(
        \Framework\Database\Connection $connection,
        string $table,
        private readonly string $modelClass,
    ) {
        parent::__construct($connection, $table);
    }

    public function get(): array
    {
        $rows = parent::get(); // returns raw arrays from DB
        return array_map(fn($row) => $this->hydrate($row), $rows);
    }

    public function first(): ?object
    {
        $row = parent::first();
        return $row ? $this->hydrate($row) : null;
    }

    private function hydrate(array $row): object
    {
        $model = new ($this->modelClass)();
        $model->fill($row);
        $model->exists = true;
        return $model;
    }
}

class ModelNotFoundException extends \RuntimeException
{
    public function __construct(string $class, mixed $id)
    {
        parent::__construct("No query results for model [{$class}] with ID [{$id}].", 404);
    }
}

// Usage
$user  = User::find(1);            // User instance or null
$user  = User::findOrFail(1);      // User instance or throws
$users = User::all();              // all User instances
$users = User::where('active', '=', true)->get();  // array of User
$first = User::where('role', '=', 'admin')->first(); // first admin or null