Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ $loaded->delete();

That is the core promise of the library: minimal ceremony, direct results.

## Documentation

Use the docs based on how much detail you need:

- [docs/guide.md](docs/guide.md) for setup, model definition, CRUD, querying, validation, strict fields, and database notes
- [docs/api-reference.md](docs/api-reference.md) for method-by-method behavior and return types
- [EXAMPLE.md](EXAMPLE.md) for shorter copy-paste examples

## What you get

### Full record lifecycle helpers
Expand Down Expand Up @@ -244,6 +252,7 @@ The repository includes:

## Learn more

- Need fuller ORM docs? Start with [docs/guide.md](docs/guide.md) and [docs/api-reference.md](docs/api-reference.md).
- Want to see planned improvements? See [ROADMAP.md](ROADMAP.md).
- Want fuller usage examples? See [EXAMPLE.md](EXAMPLE.md).
- Want to contribute? See [CONTRIBUTING.md](CONTRIBUTING.md).
356 changes: 356 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
# API Reference

This reference documents the public API of `Freshsauce\Model\Model` as implemented in [`src/Model/Model.php`](../src/Model/Model.php).

## Class configuration

These are the static members you are expected to override in subclasses.

### `protected static $_tableName`

Required. The database table for the model.

```php
protected static $_tableName = 'categories';
```

### `protected static $_primary_column_name`

Optional. Defaults to `id`.

```php
protected static $_primary_column_name = 'code';
```

### `protected static bool $_strict_fields`

Optional. Defaults to `false`. When enabled, unknown assignments throw `UnknownFieldException`.

### `public static $_db`

Inherited shared PDO connection. Redeclare this in a subclass only when that subclass needs an isolated connection.

## Construction and state

### `__construct(array $data = [])`

Initialises the model and hydrates known columns from the provided array.

### `hasData(): bool`

Returns whether the internal data object exists.

### `dataPresent(): bool`

Returns `true` when data is present, otherwise throws `MissingDataException`.

### `hydrate(array $data): void`

Maps known table columns from the input array onto the model. Unknown keys are ignored.

### `clear(): void`

Sets all known columns to `null` and resets dirty tracking.

### `toArray()`

Returns an associative array of known table columns and their current values.

### `markFieldDirty(string $name): void`

Marks a field dirty manually.

### `isFieldDirty(string $name): bool`

Returns whether a field is dirty.

### `clearDirtyFields(): void`

Clears the dirty-field tracking state.

### `__sleep()`

Serialises the `data` and `dirty` properties.

### `__serialize(): array`

Returns a normalised serialisable payload containing `data` and `dirty`.

### `__unserialize(array $data): void`

Restores serialised model state.

## Property access

### `__set(string $name, mixed $value): void`

Assigns a value to the model.

Behavior:

- in strict mode, resolves the name against real fields first
- creates the internal data object on first assignment
- marks the field as dirty

### `__get(string $name): mixed`

Returns a field value from the internal data store.

Throws:

- `MissingDataException` when data has not been initialised
- `UnknownFieldException` when the field is not present in the current data object

### `__isset(string $name): bool`

Returns whether the current data object contains the field.

## Connection and database helpers

### `connectDb(string $dsn, string $username, string $password, array $driverOptions = []): void`

Creates and stores the PDO connection for the current model class hierarchy.

Notes:

- sets `PDO::ATTR_ERRMODE` to `PDO::ERRMODE_EXCEPTION`
- clears cached prepared statements for the previous connection
- clears cached column metadata

### `driverName(): string`

Returns the active PDO driver name.

### `refreshTableMetadata(): void`

Clears the cached table-column list for the current model class.

Use this after runtime schema changes.

### `execute(string $query, array $params = []): PDOStatement`

Prepares and executes a statement, returning the `PDOStatement`.

### `datetimeToMysqldatetime(int|string $dt): string`

Converts a Unix timestamp or date string into `Y-m-d H:i:s`.

Invalid date strings are converted as timestamp `0`.

### `createInClausePlaceholders(array $params): string`

Returns a comma-separated placeholder string for `IN (...)` clauses.

Examples:

- `[1, 2, 3]` -> `?,?,?`
- `[]` -> `NULL`

## Record lifecycle

### `save(): bool`

Calls `update()` when the primary key is non-`null`; otherwise calls `insert()`.

Primary key values `0` and `'0'` count as existing primary keys and therefore use the update path.

### `insert(bool $autoTimestamp = true, bool $allowSetPrimaryKey = false): bool`

Inserts the current model as a new row.

Behavior:

- auto-fills `created_at` and `updated_at` when enabled and the fields exist
- runs `validateForSave()` and `validateForInsert()`
- clears dirty flags on success
- updates the model's primary key from the database when the key is generated by the database
- supports default-values inserts when there are no dirty fields

Set `$allowSetPrimaryKey` to `true` to include the current primary key value in the insert.

### `update(bool $autoTimestamp = true): bool`

Updates the current row by primary key.

Behavior:

- auto-fills `updated_at` when enabled and the field exists
- runs `validateForSave()` and `validateForUpdate()`
- updates only dirty known fields
- returns `false` when there are no dirty fields to write
- treats zero changed rows as success when the target row still exists

### `delete()`

Deletes the current row by primary key. Returns the result of `deleteById()`.

### `deleteById(int|string $id): bool`

Deletes one row by primary key. Returns `true` only when one row was removed.

### `deleteAllWhere(string $where, array $params = []): PDOStatement`

Deletes rows matching a condition fragment. Returns the raw statement.

Pass only the expression that belongs after `WHERE`.

## Reading records

### `getById(int|string $id): ?static`

Returns one model instance for the matching primary key, or `null`.

### `first(): ?static`

Returns the row with the lowest primary key value, or `null`.

### `last(): ?static`

Returns the row with the highest primary key value, or `null`.

### `find($id)`

Returns an array of model instances matching the primary key value.

This is intentionally different from `getById()`, which returns a single instance or `null`.

### `count(): int`

Returns the total row count for the model table.

### `exists(): bool`

Returns whether the table contains at least one row.

## Conditional reads

### `fetchWhere(string $SQLfragment = '', array $params = [], bool $limitOne = false): array|static|null`

Base helper for conditional reads.

Pass only the fragment that belongs after `WHERE`.

### `fetchAllWhere(string $SQLfragment = '', array $params = []): array`

Returns all matching rows as model instances.

### `fetchOneWhere(string $SQLfragment = '', array $params = []): ?static`

Returns the first matching row as a model instance, or `null`.

### `fetchAllWhereOrderedBy(string $orderByField, string $direction = 'ASC', string $SQLfragment = '', array $params = [], ?int $limit = null): array`

Returns all matching rows ordered by a real model field.

Constraints:

- `$orderByField` must resolve to a real model field
- `$direction` must be `ASC` or `DESC`
- `$limit`, when provided, must be greater than `0`

### `fetchOneWhereOrderedBy(string $orderByField, string $direction = 'ASC', string $SQLfragment = '', array $params = []): ?static`

Returns the first matching row using explicit ordering.

### `pluck(string $fieldname, string $SQLfragment = '', array $params = [], ?string $orderByField = null, string $direction = 'ASC', ?int $limit = null): array`

Returns one column from matching rows as a plain array.

Both `$fieldname` and `$orderByField` must resolve to real model fields.

### `countAllWhere(string $SQLfragment = '', array $params = []): int`

Returns the number of rows matching the condition fragment.

### `existsWhere(string $SQLfragment = '', array $params = []): bool`

Returns whether at least one row matches the condition fragment.

## Dynamic static methods

The model supports a dynamic method family through `__callStatic()`.

Preferred forms:

- `findBy<Field>($match)`
- `findOneBy<Field>($match)`
- `firstBy<Field>($match)`
- `lastBy<Field>($match)`
- `countBy<Field>($match)`

Examples:

```php
Category::findByName('Fiction');
Category::findOneByUpdatedAt('2026-03-08 12:00:00');
Category::countByName(['Fiction', 'Fantasy']);
```

Rules:

- field names are resolved against real columns
- camelCase field names can map to snake_case columns
- scalar matches become `= ?`
- array matches become `IN (...)`
- empty arrays short-circuit without querying the database

Legacy snake_case dynamic methods remain supported temporarily:

- `find_by_name($match)`
- `findOne_by_name($match)`
- `first_by_name($match)`
- `last_by_name($match)`
- `count_by_name($match)`

Those methods emit `E_USER_DEPRECATED`.

### `fetchOneWhereMatchingSingleField(string $fieldname, mixed $match, string $order): ?static`

Returns one row for a single-column match.

### `fetchAllWhereMatchingSingleField(string $fieldname, mixed $match): array`

Returns all rows for a single-column match.

## Validation extension points

### `validate(): bool`

Legacy static validation hook. Returns `true` by default.

### `validateForSave(): void`

Shared instance-level validation hook for both insert and update. By default it calls `static::validate()`.

### `validateForInsert(): void`

Insert-specific validation hook. No-op by default.

### `validateForUpdate(): void`

Update-specific validation hook. No-op by default.

Preferred customisation is to override the instance methods, not the legacy static method.

## Strict field controls

### `useStrictFields(bool $strict = true): void`

Enables or disables strict field mode for the current model class.

### `strictFieldsEnabled(): bool`

Returns whether strict field mode is currently enabled.

## Exceptions raised by the ORM

The API can raise these ORM-specific exceptions:

- `Freshsauce\Model\Exception\ConfigurationException`
- `Freshsauce\Model\Exception\ConnectionException`
- `Freshsauce\Model\Exception\InvalidDynamicMethodException`
- `Freshsauce\Model\Exception\MissingDataException`
- `Freshsauce\Model\Exception\ModelException`
- `Freshsauce\Model\Exception\UnknownFieldException`

PDO exceptions still bubble up for database-level errors.
Loading