Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0bfba41
refactor: switches to static what can be
JasonTheAdams Mar 8, 2025
3bed92a
test: updates tests to use static properties/methods
JasonTheAdams Mar 8, 2025
4c05c26
refactor: switch more to static
JasonTheAdams Mar 8, 2025
24ba49b
test: updates remaining tests
JasonTheAdams Mar 8, 2025
b8e8831
feat: adds native model buildings from query data
JasonTheAdams Mar 8, 2025
6541267
chore: adds additional docs
JasonTheAdams Mar 8, 2025
2c2a2d4
chore: updates docs
JasonTheAdams Apr 9, 2025
0dcc15b
chore: updates outdated flows
JasonTheAdams Apr 9, 2025
dc52d77
Merge branch 'main' into refactor/static-methods-properties
JasonTheAdams Apr 9, 2025
7ff61d8
test: updates properties in tests
JasonTheAdams Apr 9, 2025
3bdedb7
chore: fixes syntax in readme
JasonTheAdams Apr 9, 2025
90b7a08
Merge branch 'refactor/static-methods-properties' into feature/from-q…
JasonTheAdams Apr 9, 2025
235d54e
fix: fetches relationship statically
JasonTheAdams Apr 9, 2025
cff40fb
Merge branch 'refactor/static-methods-properties' into feature/from-q…
JasonTheAdams Apr 9, 2025
99769e0
refactor: cleans up ModelQueryBuilder
JasonTheAdams Apr 9, 2025
254b408
fix: corrects static syntax
JasonTheAdams Apr 10, 2025
a7c7c3b
Merge branch 'refactor/static-methods-properties' into feature/from-q…
JasonTheAdams Apr 10, 2025
676dff6
feat: improves data casting
JasonTheAdams Apr 10, 2025
8a3bc18
refactor: corrects spacing
JasonTheAdams Apr 10, 2025
cf435ab
Merge branch 'release/2.0.0' into feature/from-query-data
JasonTheAdams Apr 12, 2025
8ec57b7
refactor: changes to fromData
JasonTheAdams Apr 12, 2025
dafd8ef
feat: skips casting if value is correct type
JasonTheAdams Apr 12, 2025
90a6def
fix: corrects changed interface
JasonTheAdams Apr 12, 2025
f433793
test: tries fixing syntax error
JasonTheAdams Apr 12, 2025
b99d67f
refactor: removes sttaic return type as it is not 7.4 compat
JasonTheAdams Apr 12, 2025
29359f3
chore: bumps min PHP version to 7.4
JasonTheAdams Apr 12, 2025
b838c79
test: adds fromData model test
JasonTheAdams Apr 12, 2025
77f50f2
test: adds fromModel exception test
JasonTheAdams Apr 12, 2025
8dd2f82
test: ignores extra data
JasonTheAdams Apr 12, 2025
7be8ba8
test: ignore missing
JasonTheAdams Apr 12, 2025
64bf370
test: marks tests as skipped for now
JasonTheAdams Apr 12, 2025
01c1151
refactor: removes number type in favor of unions
JasonTheAdams Apr 12, 2025
20a6d39
feat: adds model property and definition
JasonTheAdams Apr 12, 2025
4650acf
feat: adds model property collection
JasonTheAdams Apr 12, 2025
96e29f1
feat: starts integrated collection into model
JasonTheAdams Apr 12, 2025
8c138e4
feat: properly handles restoring value to unset state
JasonTheAdams Apr 14, 2025
175675a
feat: updates Model to use new property structure
JasonTheAdams Apr 15, 2025
09d5c34
feat: adds revertChange method
JasonTheAdams Apr 15, 2025
15580dc
refactor: add and switch to map method
JasonTheAdams Apr 15, 2025
d5e3e0e
fix: makes corrections to pass Model tests
JasonTheAdams Apr 15, 2025
34b0950
chore: ignores .vscode directory
JasonTheAdams Apr 15, 2025
a5d7d62
test: stops passing anonymous class
JasonTheAdams Apr 15, 2025
e606e0f
feat: makes shorthand definitions nullable by default
JasonTheAdams Apr 15, 2025
79979ed
feat: adds ability to unset model property
JasonTheAdams Apr 15, 2025
6bfb720
test: adds PropertyDefinition tests
JasonTheAdams Apr 15, 2025
8fcf39a
test: adds property and collection tests (with fixes)
JasonTheAdams Apr 15, 2025
b9e48e4
fix: skips missing properties when set to
JasonTheAdams Sep 26, 2025
b1fef45
Merge branch 'release/2.0.0' into feature/improved-properties
JasonTheAdams Sep 29, 2025
4ae8722
fix: corrects failing test
JasonTheAdams Sep 29, 2025
c767577
chore: updates readme
JasonTheAdams Sep 29, 2025
e04396f
chore: clarifies property definition docs
JasonTheAdams Sep 29, 2025
2b73b12
chore: improves property definition docs
JasonTheAdams Sep 29, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ files/
repo/
vendor/
.idea
.vscode
154 changes: 151 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A library for a simple model structure.
* [Configuration](#configuration)
* [Creating a model](#creating-a-model)
* [Interacting with a model](#interacting-with-a-model)
* [Attribute validation](#attribute-validation)
* [Data transfer objects](#data-transfer-objects)
* [Classes of note](#classes-of-note)
* [Model](#model)
Expand Down Expand Up @@ -57,7 +58,9 @@ Models are classes that hold data and provide some helper methods for interactin

### A simple model

This is an example of a model that just holds properties.
This is an example of a model that just holds properties. Properties can be defined in one or both of the following ways:

#### Using the shorthand syntax:

```php
namespace Boomshakalaka\Whatever;
Expand All @@ -70,14 +73,43 @@ class Breakfast_Model extends Model {
*/
protected static $properties = [
'id' => 'int',
'name' => 'string',
'name' => ['string', 'Default Name'], // With default value
'price' => 'float',
'num_eggs' => 'int',
'has_bacon' => 'bool',
];
}
```

#### Using property definitions for more control:

```php
namespace Boomshakalaka\Whatever;

use Boomshakalaka\StellarWP\Models\Model;
use Boomshakalaka\StellarWP\Models\ModelPropertyDefinition;

class Breakfast_Model extends Model {
/**
* @inheritDoc
*/
protected static function properties(): array {
return [
'id' => ModelPropertyDefinition::create()
->type('int')
->required(),
'name' => ModelPropertyDefinition::create()
->type('string')
->default('Default Name')
->nullable(),
'price' => ModelPropertyDefinition::create()
->type('float')
->requiredOnSave(),
];
}
}
```

### A ReadOnly model

This is a model whose intent is to only read and store data. The Read operations should - in most cases - be deferred to
Expand Down Expand Up @@ -183,6 +215,115 @@ class Breakfast_Model extends Model implements Contracts\ModelCrud {
}
```

## Interacting with a model

### Change tracking

Models track changes to their properties and provide methods to manage those changes:

```php
$breakfast = new Breakfast_Model([
'name' => 'Original Name',
'price' => 5.99,
]);

// Check if a property is dirty (changed)
$breakfast->setAttribute('name', 'New Name');
if ($breakfast->isDirty('name')) {
echo 'Name has changed!';
}

// Get all dirty values
$dirtyValues = $breakfast->getDirty(); // ['name' => 'New Name']

// Commit changes (makes current values the "original")
$breakfast->commitChanges();
// or use the alias:
$breakfast->syncOriginal();

// Revert a specific property change
$breakfast->setAttribute('price', 7.99);
$breakfast->revertChange('price'); // price is back to 5.99

// Revert all changes
$breakfast->setAttribute('name', 'Another Name');
$breakfast->setAttribute('price', 8.99);
$breakfast->revertChanges(); // All properties back to original

// Get original value
$originalName = $breakfast->getOriginal('name');
$allOriginal = $breakfast->getOriginal(); // Get all original values
```

### Checking if properties are set

The `isSet()` method checks if a property has been set. This is different from PHP's `isset()` because it considers `null` values and default values as "set":

```php
$breakfast = new Breakfast_Model();

// Properties with defaults are considered set
if ($breakfast->isSet('name')) { // true if 'name' has a default value
echo 'Name is set';
}

// Properties without defaults are not set until assigned
if (!$breakfast->isSet('price')) { // false - no default and not assigned
echo 'Price is not set';
}

// Setting a property to null still counts as set
$breakfast->setAttribute('price', null);
if ($breakfast->isSet('price')) { // true - explicitly set to null
echo 'Price is set (even though it\'s null)';
}

// PHP's isset() behaves differently with null
if (!isset($breakfast->price)) { // false - isset() returns false for null
echo 'PHP isset() returns false for null values';
}
```

**Key differences from PHP's `isset()`:**
- `isSet()` returns `true` for properties with default values
- `isSet()` returns `true` for properties explicitly set to `null`
- `isSet()` returns `false` only for properties that have no default and haven't been assigned

### Creating models from query data

Models can be created from database query results using the `fromData()` method:

```php
// From an object or array
$data = DB::get_row("SELECT * FROM breakfasts WHERE id = 1");
$breakfast = Breakfast_Model::fromData($data);

// With different build modes
$breakfast = Breakfast_Model::fromData($data, Breakfast_Model::BUILD_MODE_STRICT);
$breakfast = Breakfast_Model::fromData($data, Breakfast_Model::BUILD_MODE_IGNORE_MISSING);
$breakfast = Breakfast_Model::fromData($data, Breakfast_Model::BUILD_MODE_IGNORE_EXTRA);
```

Build modes:
- `BUILD_MODE_STRICT`: Throws exceptions for missing or extra properties
- `BUILD_MODE_IGNORE_MISSING`: Ignores properties missing from the data
- `BUILD_MODE_IGNORE_EXTRA`: Ignores extra properties in the data (default)

### Extending model construction

Models can perform custom initialization after construction by overriding the `afterConstruct()` method:

```php
class Breakfast_Model extends Model {
protected function afterConstruct() {
// Perform custom initialization
if ($this->has_bacon && $this->num_eggs > 2) {
$this->setAttribute('name', $this->name . ' (Hearty!)');
}
}
}
```

## Attribute validation

Sometimes it would be helpful to validate attributes that are set in the model. To do that, you can create `validate_*()`
Expand Down Expand Up @@ -442,6 +583,13 @@ $breakfast = Breakfast_Model::find( 1 );
$breakfast->delete();
```

### Unsetting properties

```php
$breakfast = Breakfast_Model::find( 1 );
unset($breakfast->price); // Unsets the price property
```

## Classes of note

### `Model`
Expand All @@ -455,7 +603,7 @@ This is an abstract class to extend for creating model factories.
### `ModelQueryBuilder`

This class extends the [`stellarwp/db`](https://github.com/stellarwp/db) `QueryBuilder` class so that it returns
model instances rather than arrays or `stdClass` instances. Using this requires models that implement the `ModelFromQueryBuilderObject`
model instances rather than arrays or `stdClass` instances. Using this requires models that implement the `ModelBuildsFromData`
interface.

### `DataTransferObject`
Expand Down
20 changes: 20 additions & 0 deletions src/Models/Contracts/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace StellarWP\Models\Contracts;

use RuntimeException;
use StellarWP\Models\ModelPropertyDefinition;

interface Model extends ModelBuildsFromData {
/**
Expand Down Expand Up @@ -58,6 +59,25 @@ public function getDirty() : array;
*/
public function getOriginal( ?string $key = null );

/**
* Returns the property definition for the given key.
*
* @since 2.0.0
*
* @param string $key Property name.
*
* @return ModelPropertyDefinition
*/
public static function getPropertyDefinition( string $key ) : ModelPropertyDefinition;

/**
* Returns the property definitions for the model.
*
* @since 2.0.0
* @return array<string,ModelPropertyDefinition>
*/
public static function getPropertyDefinitions() : array;

/**
* Determines if the model has the given property.
*
Expand Down
Loading