Skip to content

Commit 10b17a1

Browse files
jharderjared
and
jared
authored
Allow Model.beforeSave to execute closure to satisfy injection requirements (#90)
Allow Model.beforeFind to execute closure to satisfy injection requirement --------- Co-authored-by: jared <[email protected]>
1 parent fadfed9 commit 10b17a1

File tree

5 files changed

+168
-24
lines changed

5 files changed

+168
-24
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,23 @@ fields when creating a record, on the `modified_by` field again when updating
7575
the record and it will use the associated user record's company `id` in the
7676
`company_id` field when creating a record.
7777

78+
You can also provide a closure that accepts an EntityInterface and returns a bool:
79+
80+
```php
81+
$this->addBehavior('Muffin/Footprint.Footprint', [
82+
'events' => [
83+
'Model.beforeSave' => [
84+
'user_id' => 'new',
85+
'company_id' => 'new',
86+
'modified_by' => 'always',
87+
'deleted_by' => function ($entity): bool {
88+
return $entity->deleted !== null;
89+
},
90+
]
91+
],
92+
]);
93+
```
94+
7895
### Adding middleware via event
7996

8097
In some cases you don't have direct access to the place where the `AuthenticationMiddleware` is added. Then you will have to add this to your `src/Application.php`

src/Model/Behavior/FootprintBehavior.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Cake\ORM\Behavior;
1212
use Cake\ORM\Query\SelectQuery;
1313
use Cake\Utility\Hash;
14+
use Closure;
1415
use UnexpectedValueException;
1516

1617
class FootprintBehavior extends Behavior
@@ -163,9 +164,11 @@ protected function _injectEntity(EntityInterface $entity, ArrayObject $options,
163164
$new = $entity->isNew() !== false;
164165

165166
foreach ($fields as $field => $when) {
166-
if (!in_array($when, ['always', 'new', 'existing'])) {
167+
if (!in_array($when, ['always', 'new', 'existing']) && !($when instanceof Closure)) {
167168
throw new UnexpectedValueException(sprintf(
168-
'When should be one of "always", "new" or "existing". The passed value "%s" is invalid',
169+
'When should be one of "always", "new" or "existing", ' .
170+
'or a closure that takes an EntityInterface and returns a bool. ' .
171+
'The passed value "%s" is invalid.',
169172
$when
170173
));
171174
}
@@ -177,7 +180,8 @@ protected function _injectEntity(EntityInterface $entity, ArrayObject $options,
177180
if (
178181
$when === 'always' ||
179182
($when === 'new' && $new) ||
180-
($when === 'existing' && !$new)
183+
($when === 'existing' && !$new) ||
184+
($when instanceof Closure && $when($entity))
181185
) {
182186
$entity->set(
183187
$field,

tests/Fixture/ArticlesFixture.php

+25
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,41 @@ class ArticlesFixture extends TestFixture
1212
'title' => 'article 1',
1313
'created_by' => 1,
1414
'modified_by' => 1,
15+
'manager_id' => 10,
1516
],
1617
[
1718
'title' => 'article 2',
1819
'created_by' => 1,
1920
'modified_by' => 2,
21+
'manager_id' => null,
2022
],
2123
[
2224
'title' => 'article 3',
2325
'created_by' => 2,
2426
'modified_by' => 1,
27+
'company_id' => 2,
28+
'manager_id' => null,
29+
],
30+
[
31+
'title' => 'find article',
32+
'created_by' => 4,
33+
'modified_by' => 4,
34+
'company_id' => 2,
35+
'manager_id' => null,
36+
],
37+
[
38+
'title' => 'final article',
39+
'created_by' => 3,
40+
'modified_by' => 4,
41+
'company_id' => 4,
42+
'manager_id' => null,
43+
],
44+
[
45+
'title' => 'penultimate article',
46+
'created_by' => 4,
47+
'modified_by' => 4,
48+
'company_id' => 1,
49+
'manager_id' => null,
2550
],
2651
];
2752
}

tests/TestCase/Model/Behavior/FootprintBehaviorTest.php

+117-21
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,22 @@ public function setUp(): void
2828
'created_by' => 'new',
2929
'modified_by' => 'always',
3030
'company_id' => 'always',
31+
'manager_id' => function ($entity): bool {
32+
return $entity->company_id == 1;
33+
},
34+
],
35+
'Model.beforeFind' => [
36+
'created_by',
37+
'company_id',
3138
],
32-
'Model.beforeFind' => 'created_by',
3339
],
3440
'propertiesMap' => [
3541
'company_id' => '_footprint.company.id',
42+
'manager_id' => '_footprint.manager.id',
3643
],
3744
]);
3845

3946
$this->Table = $table;
40-
$this->footprint = new Entity([
41-
'id' => 2,
42-
'company' => new Entity(['id' => 5]),
43-
]);
4447
}
4548

4649
public function tearDown(): void
@@ -51,58 +54,151 @@ public function tearDown(): void
5154

5255
public function testSave()
5356
{
54-
$entity = new Entity(['title' => 'new article']);
55-
$entity = $this->Table->save($entity, ['_footprint' => $this->footprint]);
57+
// Properties may still be assigned even if
58+
// closure would be satisfied.
59+
$entity = new Entity(['title' => 'new article', 'manager_id' => 7]);
60+
$footprint = new Entity([
61+
'id' => 2,
62+
'company' => new Entity(['id' => 1]),
63+
'manager' => new Entity(['id' => 10]),
64+
]);
65+
66+
$entity = $this->Table->save($entity, ['_footprint' => $footprint]);
5667
$expected = [
5768
'id' => $entity->id,
5869
'title' => 'new article',
5970
'created_by' => 2,
6071
'modified_by' => 2,
61-
'company_id' => 5,
72+
'company_id' => 1,
73+
'manager_id' => 7,
6274
];
75+
6376
$this->assertSame(
6477
$expected,
65-
$entity->extract(['id', 'title', 'created_by', 'modified_by', 'company_id'])
78+
$entity->extract(['id', 'title', 'created_by', 'modified_by', 'company_id', 'manager_id'])
6679
);
6780

81+
// Closure fields won't set if disallowed
82+
// even if provided.
83+
$entity = new Entity();
84+
$entity->title = 'new title';
6885
$footprint = new Entity([
6986
'id' => 3,
87+
'company' => new Entity(['id' => 5]),
88+
'manager' => new Entity(['id' => 4]),
7089
]);
71-
$entity->title = 'new title';
90+
7291
$entity = $this->Table->save($entity, ['_footprint' => $footprint]);
73-
$expected = ['id' => $entity->id, 'title' => 'new title', 'created_by' => 2, 'modified_by' => 3];
74-
$this->assertSame($expected, $entity->extract(['id', 'title', 'created_by', 'modified_by']));
92+
$expected = [
93+
'id' => $entity->id,
94+
'title' => 'new title',
95+
'created_by' => 3,
96+
'modified_by' => 3,
97+
'company_id' => 5,
98+
'manager_id' => null,
99+
];
75100

101+
$this->assertSame($expected, $entity->extract(['id', 'title', 'created_by', 'modified_by', 'company_id', 'manager_id']));
102+
103+
// Fields won't set if a footprint isn't provided
76104
$entity = new Entity(['title' => 'without footprint']);
105+
77106
$entity = $this->Table->save($entity);
78-
$expected = ['id' => $entity->id, 'title' => 'without footprint', 'created_by' => null, 'modified_by' => null];
79-
$this->assertSame($expected, $entity->extract(['id', 'title', 'created_by', 'modified_by']));
107+
$expected = [
108+
'id' => $entity->id,
109+
'title' => 'without footprint',
110+
'created_by' => null,
111+
'modified_by' => null,
112+
'manager_id' => null,
113+
];
114+
115+
$this->assertSame($expected, $entity->extract(['id', 'title', 'created_by', 'modified_by', 'manager_id']));
116+
117+
// Satisfying closure manually still permits
118+
// explicit field assignments
119+
$entity = new Entity(['title' => 'different manager', 'company_id' => 1]);
120+
$footprint = new Entity([
121+
'id' => 3,
122+
'company' => new Entity(['id' => 5]),
123+
'manager' => new Entity(['id' => 4]),
124+
]);
125+
126+
$entity = $this->Table->save($entity, ['_footprint' => $footprint]);
127+
$expected = [
128+
'id' => $entity->id,
129+
'title' => 'different manager',
130+
'created_by' => 3,
131+
'modified_by' => 3,
132+
'company_id' => 1,
133+
'manager_id' => 4,
134+
];
135+
136+
$this->assertSame($expected, $entity->extract(['id', 'title', 'created_by', 'modified_by', 'company_id', 'manager_id']));
80137
}
81138

82139
public function testFind()
83140
{
84-
$result = $this->Table->find('all', _footprint: $this->footprint)
141+
$footprint = new Entity(['id' => 4]);
142+
143+
$result = $this->Table->find('all', _footprint: $footprint)
85144
->enableHydration(false)
86145
->first();
87146

88-
$expected = ['id' => 3, 'title' => 'article 3', 'created_by' => 2, 'modified_by' => 1];
147+
$expected = [
148+
'id' => 4,
149+
'title' => 'find article',
150+
'created_by' => 4,
151+
'modified_by' => 4,
152+
'company_id' => 2,
153+
'manager_id' => null,
154+
];
89155
$this->assertSame($expected, $result);
90156

91157
// Test to show value of "id" is not used from footprint if
92158
// "Articles.created_by" is already set in condition.
93-
$result = $this->Table->find('all', _footprint: $this->footprint)
94-
->where(['Articles.created_by' => 1])
159+
$result = $this->Table->find('all', _footprint: $footprint)
160+
->where(['Articles.created_by' => 3])
95161
->enableHydration(false)
96162
->first();
97163

98-
$expected = ['id' => 1, 'title' => 'article 1', 'created_by' => 1, 'modified_by' => 1];
164+
$expected = [
165+
'id' => 5,
166+
'title' => 'final article',
167+
'created_by' => 3,
168+
'modified_by' => 4,
169+
'company_id' => 4,
170+
'manager_id' => null,
171+
];
172+
$this->assertSame($expected, $result);
173+
174+
// Test to show value of "id" is not used from footprint even
175+
// "Articles.manager_id" validates the Model.beforeSave closure
176+
$result = $this->Table->find('all', _footprint: $footprint)
177+
->where(['Articles.company_id' => 1])
178+
->enableHydration(false)
179+
->first();
180+
181+
$expected = [
182+
'id' => 6,
183+
'title' => 'penultimate article',
184+
'created_by' => 4,
185+
'modified_by' => 4,
186+
'company_id' => 1,
187+
'manager_id' => null,
188+
];
99189
$this->assertSame($expected, $result);
100190
}
101191

102192
public function testInjectEntityException()
103193
{
104194
$this->expectException('UnexpectedValueException');
105-
$this->expectExceptionMessage('When should be one of "always", "new" or "existing". The passed value "invalid" is invalid');
195+
$this->expectExceptionMessage('When should be one of "always", "new" or "existing", ' .
196+
'or a closure that takes an EntityInterface and returns a bool. ' .
197+
'The passed value "invalid" is invalid.');
198+
199+
$footprint = new Entity([
200+
'id' => 2,
201+
]);
106202

107203
$this->Table->behaviors()->Footprint->setConfig(
108204
'events',
@@ -113,6 +209,6 @@ public function testInjectEntityException()
113209
]
114210
);
115211
$entity = new Entity(['title' => 'new article']);
116-
$entity = $this->Table->save($entity, ['_footprint' => $this->footprint]);
212+
$entity = $this->Table->save($entity, ['_footprint' => $footprint]);
117213
}
118214
}

tests/schema.php

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
'title' => ['type' => 'string', 'length' => 255],
1010
'created_by' => ['type' => 'integer'],
1111
'modified_by' => ['type' => 'integer'],
12+
'company_id' => ['type' => 'integer'],
13+
'manager_id' => ['type' => 'integer'],
1214
],
1315
'constraints' => [
1416
'primary' => [

0 commit comments

Comments
 (0)