1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Licensed under The GPL-3.0 License |
4
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
5
|
|
|
* Redistributions of files must retain the above copyright notice. |
6
|
|
|
* |
7
|
|
|
* @since 2.0.0 |
8
|
|
|
* @author Christopher Castro <[email protected]> |
9
|
|
|
* @link http://www.quickappscms.org |
10
|
|
|
* @license http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License |
11
|
|
|
*/ |
12
|
|
|
namespace Eav\Model\Behavior; |
13
|
|
|
|
14
|
|
|
use Cake\Cache\Cache; |
15
|
|
|
use Cake\Collection\CollectionInterface; |
16
|
|
|
use Cake\Database\Type; |
17
|
|
|
use Cake\Datasource\EntityInterface; |
18
|
|
|
use Cake\ORM\Query; |
19
|
|
|
use Cake\ORM\Table; |
20
|
|
|
use Cake\ORM\TableRegistry; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Support class for EAV behavior. |
24
|
|
|
*/ |
25
|
|
|
class EavToolbox |
26
|
|
|
{ |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* List of accepted value types. |
30
|
|
|
* |
31
|
|
|
* @var array |
32
|
|
|
*/ |
33
|
|
|
public static $types = [ |
34
|
|
|
'biginteger', |
35
|
|
|
'binary', |
36
|
|
|
'date', |
37
|
|
|
'float', |
38
|
|
|
'decimal', |
39
|
|
|
'integer', |
40
|
|
|
'time', |
41
|
|
|
'datetime', |
42
|
|
|
'timestamp', |
43
|
|
|
'uuid', |
44
|
|
|
'string', |
45
|
|
|
'text', |
46
|
|
|
'boolean', |
47
|
|
|
]; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* The table being managed. |
51
|
|
|
* |
52
|
|
|
* @var \Cake\ORM\Table |
53
|
|
|
*/ |
54
|
|
|
protected $_table = null; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Attributes index by bundle, and by name within each bundle. |
58
|
|
|
* |
59
|
|
|
* ```php |
60
|
|
|
* [ |
61
|
|
|
* 'administrator' => [ |
62
|
|
|
* 'admin-address' => [ |
63
|
|
|
* 'type' => 'varchar', |
64
|
|
|
* 'searchable' => false |
65
|
|
|
* ], |
66
|
|
|
* 'admin-phone' => [ |
67
|
|
|
* 'type' => 'varchar', |
68
|
|
|
* 'searchable' => true |
69
|
|
|
* ] |
70
|
|
|
* ], |
71
|
|
|
* 'editor' => [ |
72
|
|
|
* 'editor-last-login' => [ |
73
|
|
|
* 'type' => 'datetime', |
74
|
|
|
* 'searchable' => false, |
75
|
|
|
* ] |
76
|
|
|
* ] |
77
|
|
|
* ] |
78
|
|
|
* ``` |
79
|
|
|
* |
80
|
|
|
* @var array |
81
|
|
|
*/ |
82
|
|
|
protected $_attributes = []; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Constructor. |
86
|
|
|
* |
87
|
|
|
* @param \Cake\ORM\Table $table The table being handled |
88
|
|
|
*/ |
89
|
|
|
public function __construct(Table $table) |
90
|
|
|
{ |
91
|
|
|
$this->_table = $table; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Gets a clean column name from query expression. |
96
|
|
|
* |
97
|
|
|
* ### Example: |
98
|
|
|
* |
99
|
|
|
* ```php |
100
|
|
|
* EavToolbox::columnName('Tablename.some_column'); |
101
|
|
|
* // returns "some_column" |
102
|
|
|
* |
103
|
|
|
* EavToolbox::columnName('my_column'); |
104
|
|
|
* // returns "my_column" |
105
|
|
|
* ``` |
106
|
|
|
* |
107
|
|
|
* @param string $column Column name from query |
108
|
|
|
* @return string |
109
|
|
|
*/ |
110
|
|
|
public static function columnName($column) |
111
|
|
|
{ |
112
|
|
|
list($tableName, $fieldName) = pluginSplit((string)$column); |
113
|
|
|
if (!$fieldName) { |
114
|
|
|
$fieldName = $tableName; |
115
|
|
|
} |
116
|
|
|
$fieldName = preg_replace('/\s{2,}/', ' ', $fieldName); |
117
|
|
|
list($fieldName, ) = explode(' ', trim($fieldName)); |
118
|
|
|
|
119
|
|
|
return $fieldName; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Checks if the provided entity has defined certain $property, regardless of |
124
|
|
|
* its value. |
125
|
|
|
* |
126
|
|
|
* @param \Cake\Datasource\EntityInterface $entity The entity to check |
127
|
|
|
* @param string $property The property name |
128
|
|
|
* @return bool True if exists |
129
|
|
|
*/ |
130
|
|
|
public function propertyExists(EntityInterface $entity, $property) |
131
|
|
|
{ |
132
|
|
|
$visibleProperties = $entity->visibleProperties(); |
133
|
|
|
|
134
|
|
|
return in_array($property, $visibleProperties); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Marshalls flat data into PHP objects. |
139
|
|
|
* |
140
|
|
|
* @param mixed $value The value to convert |
141
|
|
|
* @param string $type Type identifier, `integer`, `float`, etc |
142
|
|
|
* @return mixed Converted value |
143
|
|
|
*/ |
144
|
|
|
public function marshal($value, $type) |
145
|
|
|
{ |
146
|
|
|
return Type::build($type)->marshal($value); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Gets all attributes added to this table. |
151
|
|
|
* |
152
|
|
|
* @param string|null $bundle Get attributes within given bundle, or all of them |
153
|
|
|
* regardless of the bundle if not provided |
154
|
|
|
* @return array List of attributes indexed by name (virtual column name) |
155
|
|
|
*/ |
156
|
|
|
public function attributes($bundle = null) |
157
|
|
|
{ |
158
|
|
|
$key = empty($bundle) ? '@all' : $bundle; |
159
|
|
|
if (isset($this->_attributes[$key])) { |
160
|
|
|
return $this->_attributes[$key]; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
$this->_attributes[$key] = []; |
164
|
|
|
$cacheKey = $this->_table->table() . '_' . $key; |
|
|
|
|
165
|
|
|
$attrs = Cache::read($cacheKey, 'eav_table_attrs'); |
166
|
|
|
|
167
|
|
|
if (empty($attrs)) { |
168
|
|
|
$conditions = ['EavAttributes.table_alias' => $this->_table->table()]; |
|
|
|
|
169
|
|
|
if (!empty($bundle)) { |
170
|
|
|
$conditions['EavAttributes.bundle'] = $bundle; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
$attrs = TableRegistry::get('Eav.EavAttributes') |
174
|
|
|
->find() |
175
|
|
|
->where($conditions) |
176
|
|
|
->all() |
177
|
|
|
->toArray(); |
178
|
|
|
|
179
|
|
|
Cache::write($cacheKey, $attrs, 'eav_table_attrs'); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
foreach ($attrs as $attr) { |
183
|
|
|
$this->_attributes[$key][$attr->get('name')] = $attr; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
return $this->attributes($bundle); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Gets a list of attribute names. |
191
|
|
|
* |
192
|
|
|
* @param string $bundle Filter by bundle name |
193
|
|
|
* @return array |
194
|
|
|
*/ |
195
|
|
|
public function getAttributeNames($bundle = null) |
196
|
|
|
{ |
197
|
|
|
$attributes = $this->attributes($bundle); |
198
|
|
|
|
199
|
|
|
return array_keys($attributes); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Gets a list of attribute IDs. |
204
|
|
|
* |
205
|
|
|
* @param string $bundle Filter by bundle name |
206
|
|
|
* @return array |
207
|
|
|
*/ |
208
|
|
|
public function getAttributeIds($bundle = null) |
209
|
|
|
{ |
210
|
|
|
$attributes = $this->attributes($bundle); |
211
|
|
|
$ids = []; |
212
|
|
|
|
213
|
|
|
foreach ($attributes as $name => $info) { |
214
|
|
|
$ids[] = $info['id']; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return $ids; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Given a collection of entities gets the ID of all of them. |
222
|
|
|
* |
223
|
|
|
* This method iterates the given set and invokes `getEntityId()` for every |
224
|
|
|
* entity in the set. |
225
|
|
|
* |
226
|
|
|
* @param \Cake\Collection\CollectionInterface $results Set of entities |
227
|
|
|
* @return array List of entity ids suitable for EAV logic |
228
|
|
|
*/ |
229
|
|
|
public function extractEntityIds(CollectionInterface $results) |
230
|
|
|
{ |
231
|
|
|
$entityIds = []; |
232
|
|
|
$results->each(function ($entity) use (&$entityIds) { |
233
|
|
|
if ($entity instanceof EntityInterface) { |
234
|
|
|
$entityIds[] = $this->getEntityId($entity); |
235
|
|
|
} |
236
|
|
|
}); |
237
|
|
|
|
238
|
|
|
return $entityIds; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Calculates entity's primary key. |
243
|
|
|
* |
244
|
|
|
* If PK is composed of multiple columns they will be merged with `:` symbol. |
245
|
|
|
* For example, consider `Users` table with composed PK <nick, email>, then for |
246
|
|
|
* certain User entity this method could return: |
247
|
|
|
* |
248
|
|
|
* john-locke:[email protected] |
249
|
|
|
* |
250
|
|
|
* @param \Cake\Datasource\EntityInterface $entity The entity |
251
|
|
|
* @return string |
252
|
|
|
*/ |
253
|
|
|
public function getEntityId(EntityInterface $entity) |
254
|
|
|
{ |
255
|
|
|
$pk = []; |
256
|
|
|
$keys = $this->_table->primaryKey(); |
|
|
|
|
257
|
|
|
$keys = !is_array($keys) ? [$keys] : $keys; |
258
|
|
|
foreach ($keys as $key) { |
259
|
|
|
$pk[] = $entity->get($key); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
return implode(':', $pk); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Gets attribute's EAV type. |
267
|
|
|
* |
268
|
|
|
* @param string $attrName Attribute name |
269
|
|
|
* @return string Attribute's EAV type |
270
|
|
|
* @see \Eav\Model\Behavior\EavBehavior::_mapType() |
271
|
|
|
*/ |
272
|
|
|
public function getType($attrName) |
273
|
|
|
{ |
274
|
|
|
return $this->mapType($this->attributes()[$attrName]->get('type')); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Gets attribute's bundle. |
279
|
|
|
* |
280
|
|
|
* @param string $attrName Attribute name |
281
|
|
|
* @return string|null |
282
|
|
|
*/ |
283
|
|
|
public function getBundle($attrName) |
284
|
|
|
{ |
285
|
|
|
return $this->attributes()[$attrName]->get('bundle'); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Whether the given attribute can be used in WHERE clauses. |
290
|
|
|
* |
291
|
|
|
* @param string $attrName Attribute name |
292
|
|
|
* @return bool |
293
|
|
|
*/ |
294
|
|
|
public function isSearchable($attrName) |
295
|
|
|
{ |
296
|
|
|
return (bool)$this->attributes()[$attrName]->get('searchable'); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Maps schema data types to EAV's supported types. |
301
|
|
|
* |
302
|
|
|
* @param string $type A schema type. e.g. "string", "integer" |
303
|
|
|
* @return string A EAV type. Possible values are `datetime`, `binary`, `time`, |
304
|
|
|
* `date`, `float`, `intreger`, `biginteger`, `text`, `string`, `boolean` or |
305
|
|
|
* `uuid` |
306
|
|
|
*/ |
307
|
|
|
public function mapType($type) |
308
|
|
|
{ |
309
|
|
|
switch ($type) { |
310
|
|
|
case 'float': |
311
|
|
|
case 'decimal': |
312
|
|
|
return 'float'; |
313
|
|
|
case 'timestamp': |
314
|
|
|
return 'datetime'; |
315
|
|
|
default: |
316
|
|
|
return $type; |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Gets the name of the class driver used by the given $query to access the DB. |
322
|
|
|
* |
323
|
|
|
* @param \Cake\ORM\Query $query The query to inspect |
324
|
|
|
* @return string Lowercased drive name. e.g. `mysql` |
325
|
|
|
*/ |
326
|
|
|
public function driver(Query $query) |
327
|
|
|
{ |
328
|
|
|
$conn = $query->connection(null); |
|
|
|
|
329
|
|
|
list(, $driver) = namespaceSplit(strtolower(get_class($conn->driver()))); |
330
|
|
|
|
331
|
|
|
return $driver; |
332
|
|
|
} |
333
|
|
|
} |
334
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.