find(), all(), where() — delegating to the Query Builder
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
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