Skip to content

Commit 5011f49

Browse files
JasonTheAdamsjonwaldsteinborkweb
authored
2.0 — Feature: Improved Properties (#22)
Co-authored-by: Jon Waldstein <Jpwaldstein@gmail.com> Co-authored-by: Matthew Batchelder <borkweb@gmail.com>
1 parent a4c3af9 commit 5011f49

12 files changed

+2158
-194
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ files/
33
repo/
44
vendor/
55
.idea
6+
.vscode

README.md

Lines changed: 151 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ A library for a simple model structure.
88
* [Configuration](#configuration)
99
* [Creating a model](#creating-a-model)
1010
* [Interacting with a model](#interacting-with-a-model)
11+
* [Attribute validation](#attribute-validation)
1112
* [Data transfer objects](#data-transfer-objects)
1213
* [Classes of note](#classes-of-note)
1314
* [Model](#model)
@@ -57,7 +58,9 @@ Models are classes that hold data and provide some helper methods for interactin
5758

5859
### A simple model
5960

60-
This is an example of a model that just holds properties.
61+
This is an example of a model that just holds properties. Properties can be defined in one or both of the following ways:
62+
63+
#### Using the shorthand syntax:
6164

6265
```php
6366
namespace Boomshakalaka\Whatever;
@@ -70,14 +73,43 @@ class Breakfast_Model extends Model {
7073
*/
7174
protected static $properties = [
7275
'id' => 'int',
73-
'name' => 'string',
76+
'name' => ['string', 'Default Name'], // With default value
7477
'price' => 'float',
7578
'num_eggs' => 'int',
7679
'has_bacon' => 'bool',
7780
];
7881
}
7982
```
8083

84+
#### Using property definitions for more control:
85+
86+
```php
87+
namespace Boomshakalaka\Whatever;
88+
89+
use Boomshakalaka\StellarWP\Models\Model;
90+
use Boomshakalaka\StellarWP\Models\ModelPropertyDefinition;
91+
92+
class Breakfast_Model extends Model {
93+
/**
94+
* @inheritDoc
95+
*/
96+
protected static function properties(): array {
97+
return [
98+
'id' => ModelPropertyDefinition::create()
99+
->type('int')
100+
->required(),
101+
'name' => ModelPropertyDefinition::create()
102+
->type('string')
103+
->default('Default Name')
104+
->nullable(),
105+
'price' => ModelPropertyDefinition::create()
106+
->type('float')
107+
->requiredOnSave(),
108+
];
109+
}
110+
}
111+
```
112+
81113
### A ReadOnly model
82114

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

218+
## Interacting with a model
219+
220+
### Change tracking
221+
222+
Models track changes to their properties and provide methods to manage those changes:
223+
224+
```php
225+
$breakfast = new Breakfast_Model([
226+
'name' => 'Original Name',
227+
'price' => 5.99,
228+
]);
229+
230+
// Check if a property is dirty (changed)
231+
$breakfast->setAttribute('name', 'New Name');
232+
if ($breakfast->isDirty('name')) {
233+
echo 'Name has changed!';
234+
}
235+
236+
// Get all dirty values
237+
$dirtyValues = $breakfast->getDirty(); // ['name' => 'New Name']
238+
239+
// Commit changes (makes current values the "original")
240+
$breakfast->commitChanges();
241+
// or use the alias:
242+
$breakfast->syncOriginal();
243+
244+
// Revert a specific property change
245+
$breakfast->setAttribute('price', 7.99);
246+
$breakfast->revertChange('price'); // price is back to 5.99
247+
248+
// Revert all changes
249+
$breakfast->setAttribute('name', 'Another Name');
250+
$breakfast->setAttribute('price', 8.99);
251+
$breakfast->revertChanges(); // All properties back to original
252+
253+
// Get original value
254+
$originalName = $breakfast->getOriginal('name');
255+
$allOriginal = $breakfast->getOriginal(); // Get all original values
256+
```
257+
258+
### Checking if properties are set
259+
260+
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":
261+
262+
```php
263+
$breakfast = new Breakfast_Model();
264+
265+
// Properties with defaults are considered set
266+
if ($breakfast->isSet('name')) { // true if 'name' has a default value
267+
echo 'Name is set';
268+
}
269+
270+
// Properties without defaults are not set until assigned
271+
if (!$breakfast->isSet('price')) { // false - no default and not assigned
272+
echo 'Price is not set';
273+
}
274+
275+
// Setting a property to null still counts as set
276+
$breakfast->setAttribute('price', null);
277+
if ($breakfast->isSet('price')) { // true - explicitly set to null
278+
echo 'Price is set (even though it\'s null)';
279+
}
280+
281+
// PHP's isset() behaves differently with null
282+
if (!isset($breakfast->price)) { // false - isset() returns false for null
283+
echo 'PHP isset() returns false for null values';
284+
}
285+
```
286+
287+
**Key differences from PHP's `isset()`:**
288+
- `isSet()` returns `true` for properties with default values
289+
- `isSet()` returns `true` for properties explicitly set to `null`
290+
- `isSet()` returns `false` only for properties that have no default and haven't been assigned
291+
292+
### Creating models from query data
293+
294+
Models can be created from database query results using the `fromData()` method:
295+
296+
```php
297+
// From an object or array
298+
$data = DB::get_row("SELECT * FROM breakfasts WHERE id = 1");
299+
$breakfast = Breakfast_Model::fromData($data);
300+
301+
// With different build modes
302+
$breakfast = Breakfast_Model::fromData($data, Breakfast_Model::BUILD_MODE_STRICT);
303+
$breakfast = Breakfast_Model::fromData($data, Breakfast_Model::BUILD_MODE_IGNORE_MISSING);
304+
$breakfast = Breakfast_Model::fromData($data, Breakfast_Model::BUILD_MODE_IGNORE_EXTRA);
305+
```
306+
307+
Build modes:
308+
- `BUILD_MODE_STRICT`: Throws exceptions for missing or extra properties
309+
- `BUILD_MODE_IGNORE_MISSING`: Ignores properties missing from the data
310+
- `BUILD_MODE_IGNORE_EXTRA`: Ignores extra properties in the data (default)
311+
312+
### Extending model construction
313+
314+
Models can perform custom initialization after construction by overriding the `afterConstruct()` method:
315+
316+
```php
317+
class Breakfast_Model extends Model {
318+
protected function afterConstruct() {
319+
// Perform custom initialization
320+
if ($this->has_bacon && $this->num_eggs > 2) {
321+
$this->setAttribute('name', $this->name . ' (Hearty!)');
322+
}
323+
}
324+
}
325+
```
326+
186327
## Attribute validation
187328

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

586+
### Unsetting properties
587+
588+
```php
589+
$breakfast = Breakfast_Model::find( 1 );
590+
unset($breakfast->price); // Unsets the price property
591+
```
592+
445593
## Classes of note
446594

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

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

461609
### `DataTransferObject`

src/Models/Contracts/Model.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace StellarWP\Models\Contracts;
44

55
use RuntimeException;
6+
use StellarWP\Models\ModelPropertyDefinition;
67

78
interface Model extends ModelBuildsFromData {
89
/**
@@ -58,6 +59,25 @@ public function getDirty() : array;
5859
*/
5960
public function getOriginal( ?string $key = null );
6061

62+
/**
63+
* Returns the property definition for the given key.
64+
*
65+
* @since 2.0.0
66+
*
67+
* @param string $key Property name.
68+
*
69+
* @return ModelPropertyDefinition
70+
*/
71+
public static function getPropertyDefinition( string $key ) : ModelPropertyDefinition;
72+
73+
/**
74+
* Returns the property definitions for the model.
75+
*
76+
* @since 2.0.0
77+
* @return array<string,ModelPropertyDefinition>
78+
*/
79+
public static function getPropertyDefinitions() : array;
80+
6181
/**
6282
* Determines if the model has the given property.
6383
*

0 commit comments

Comments
 (0)