Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

$finder = PhpCsFixer\Finder::create()
->in([
__DIR__ . '/src',
Expand Down
68 changes: 52 additions & 16 deletions EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ echo $category->created_at;
echo $category->updated_at;
```

`save()` inserts when the primary key is empty and updates when the primary key is present.
`save()` inserts when the primary key is `null` and updates when the primary key is present.

## Load a record by primary key

Expand Down Expand Up @@ -93,17 +93,7 @@ $count = Category::count();

## Dynamic finders

Snake_case methods:

```php
$all = Category::find_by_name('Fiction');
$one = Category::findOne_by_name('Fiction');
$first = Category::first_by_name(['Fiction', 'Fantasy']);
$last = Category::last_by_name(['Fiction', 'Fantasy']);
$count = Category::count_by_name('Fiction');
```

CamelCase alternatives:
Preferred camelCase methods:

```php
$all = Category::findByName('Fiction');
Expand All @@ -113,6 +103,30 @@ $last = Category::lastByName(['Fiction', 'Fantasy']);
$count = Category::countByName('Fiction');
```

Legacy snake_case dynamic methods still work during the transition, but they are deprecated and should be migrated.

## Focused query helpers

Check existence:

```php
$hasCategories = Category::exists();
$hasFiction = Category::existsWhere('name = ?', ['Fiction']);
```

Fetch ordered results:

```php
$alphabetical = Category::fetchAllWhereOrderedBy('name', 'ASC');
$latest = Category::fetchOneWhereOrderedBy('id', 'DESC');
```

Pluck one column:

```php
$names = Category::pluck('name', '', [], 'name', 'ASC', 10);
```

## Custom WHERE clauses

Fetch a single matching record:
Expand Down Expand Up @@ -181,21 +195,43 @@ $rows = $statement->fetchAll(PDO::FETCH_ASSOC);

## Validation

Override `validate()` to enforce model rules before insert and update:
Use instance-aware hooks to enforce model rules before insert and update:

```php
class Category extends Freshsauce\Model\Model
{
protected static $_tableName = 'categories';

public static function validate()
protected function validateForSave(): void
{
return true;
if (trim((string) $this->name) === '') {
throw new RuntimeException('Name is required');
}
}
}
```

Throw an exception from `validate()` whenever your application-specific rule fails, and the write will be aborted before insert or update.
Override `validateForInsert()` or `validateForUpdate()` when the rules are operation-specific.

The legacy static `validate()` method remains supported for backward compatibility.

## Strict field mode

Enable strict field mode when you want unknown assignments to fail immediately instead of being silently ignored on persistence:

```php
class Category extends Freshsauce\Model\Model
{
protected static $_tableName = 'categories';
protected static bool $_strict_fields = true;
}
```

Or enable it temporarily:

```php
Category::useStrictFields(true);
```

## MySQL example connection

Expand Down
52 changes: 48 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ Model ORM for PHP

If you want database-backed PHP models without pulling in a heavyweight stack, this library is built for that job.

## When it fits

Use this library when you want:

- Active-record style models without adopting a full framework ORM
- Direct access to SQL and PDO when convenience helpers stop helping
- A small API surface that stays easy to understand in legacy or custom PHP apps

Skip it if you need relationship graphs, migrations, or a chainable query builder comparable to framework ORMs.

## Why teams pick it

- Lightweight by design: point a model at a table and start reading and writing records.
Expand Down Expand Up @@ -110,6 +120,22 @@ Category::countByName('Science Fiction');

Legacy snake_case dynamic methods still work during the transition, but they are deprecated and emit `E_USER_DEPRECATED` notices.

If you still have legacy calls such as `find_by_name()`, treat them as migration work rather than the preferred API.

### Focused query helpers

For common read patterns that do not justify raw SQL:

```php
Category::exists();
Category::existsWhere('name = ?', ['Science Fiction']);

$ordered = Category::fetchAllWhereOrderedBy('name', 'ASC');
$latest = Category::fetchOneWhereOrderedBy('id', 'DESC');

$names = Category::pluck('name', '', [], 'name', 'ASC', 10);
```

### Flexible SQL when convenience methods stop helping

Use targeted where clauses:
Expand All @@ -133,21 +159,39 @@ $rows = $statement->fetchAll(PDO::FETCH_ASSOC);

### Validation hooks

Override `validate()` in your model when writes need application rules:
Use instance-aware hooks when writes need application rules:

```php
class Category extends Freshsauce\Model\Model
{
protected static $_tableName = 'categories';

public static function validate()
protected function validateForSave(): void
{
return true;
if (trim((string) $this->name) === '') {
throw new RuntimeException('Name is required');
}
}
}
```

Throw an exception from `validate()` to block invalid inserts or updates.
Use `validateForInsert()` or `validateForUpdate()` when the rules differ by operation.

The legacy static `validate()` method still works for backward compatibility, but new code should prefer the instance hooks.

### Strict field mode

Unknown properties are permissive by default for compatibility. If you want typo-safe writes, enable strict field mode:

```php
class Category extends Freshsauce\Model\Model
{
protected static $_tableName = 'categories';
protected static bool $_strict_fields = true;
}
```

You can also opt in at runtime with `Category::useStrictFields(true)`.

### Predictable exceptions

Expand Down
2 changes: 2 additions & 0 deletions src/Model/Exception/ConfigurationException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Freshsauce\Model\Exception;

class ConfigurationException extends ModelException
Expand Down
2 changes: 2 additions & 0 deletions src/Model/Exception/ConnectionException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Freshsauce\Model\Exception;

class ConnectionException extends ModelException
Expand Down
2 changes: 2 additions & 0 deletions src/Model/Exception/InvalidDynamicMethodException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Freshsauce\Model\Exception;

class InvalidDynamicMethodException extends ModelException
Expand Down
2 changes: 2 additions & 0 deletions src/Model/Exception/MissingDataException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Freshsauce\Model\Exception;

class MissingDataException extends ModelException
Expand Down
2 changes: 2 additions & 0 deletions src/Model/Exception/ModelException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Freshsauce\Model\Exception;

class ModelException extends \RuntimeException
Expand Down
2 changes: 2 additions & 0 deletions src/Model/Exception/UnknownFieldException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Freshsauce\Model\Exception;

class UnknownFieldException extends ModelException
Expand Down
Loading