├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
└── src
├── CallbackSerializer.php
├── DynamicAttributeBehavior.php
├── JsonExpressionSerializer.php
├── JsonSerializer.php
├── PhpSerializer.php
└── SerializerInterface.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Yii 2 ActiveRecord Dynamic Attribute extension Change Log
2 | =========================================================
3 |
4 | 1.0.2, September 19, 2018
5 | -------------------------
6 |
7 | - Enh: Usage of deprecated `yii\base\InvalidParamException` changed to `yii\base\InvalidArgumentException` one (klimov-paul)
8 | - Enh #1: `JsonExpressionSerializer` created providing support for `yii\db\JsonExpression` usage (klimov-paul)
9 |
10 |
11 | 1.0.1, November 3, 2017
12 | -----------------------
13 |
14 | - Bug: Usage of deprecated `yii\base\Object` changed to `yii\base\BaseObject` allowing compatibility with PHP 7.2 (klimov-paul)
15 |
16 |
17 | 1.0.0, August 26, 2016
18 | ----------------------
19 |
20 | - Initial release.
21 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The Yii framework is free software. It is released under the terms of
2 | the following BSD License.
3 |
4 | Copyright © 2015 by Yii2tech (https://github.com/yii2tech)
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions
9 | are met:
10 |
11 | * Redistributions of source code must retain the above copyright
12 | notice, this list of conditions and the following disclaimer.
13 | * Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in
15 | the documentation and/or other materials provided with the
16 | distribution.
17 | * Neither the name of Yii2tech nor the names of its
18 | contributors may be used to endorse or promote products derived
19 | from this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
ActiveRecord Dynamic Attribute Extension for Yii2
6 |
7 |
8 |
9 | This extension provides dynamic ActiveRecord attributes stored into the single field in serialized state.
10 |
11 | For license information check the [LICENSE](LICENSE.md)-file.
12 |
13 | [](https://packagist.org/packages/yii2tech/ar-dynattribute)
14 | [](https://packagist.org/packages/yii2tech/ar-dynattribute)
15 | [](https://travis-ci.org/yii2tech/ar-dynattribute)
16 |
17 |
18 | Installation
19 | ------------
20 |
21 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
22 |
23 | Either run
24 |
25 | ```
26 | php composer.phar require --prefer-dist yii2tech/ar-dynattribute
27 | ```
28 |
29 | or add
30 |
31 | ```json
32 | "yii2tech/ar-dynattribute": "*"
33 | ```
34 |
35 | to the require section of your composer.json.
36 |
37 |
38 | Usage
39 | -----
40 |
41 | This extension provides dynamic ActiveRecord attributes stored into the single field in serialized state.
42 | For example: imagine we create a web site, where logged in user may customize its appearance, like changing
43 | color schema or enable/disable sidebar and so on. In order to make this customization persistent all user's
44 | choices should be stored into the database. In general each view setting should have its own column in the
45 | 'user' table. However, this is not very practical in case your application is under development and new
46 | settings appear rapidly. Thus it make sense to use single text field, which will store all chosen view
47 | parameters in the serialized string. If new option introduced there will no necessity to change 'user' table
48 | schema.
49 | Migration for the 'user' table creation may look like following:
50 |
51 | ```php
52 | class m??????_??????_create_user extends \yii\db\Migration
53 | {
54 | public function up()
55 | {
56 | $this->createTable('User', [
57 | 'id' => $this->primaryKey(),
58 | 'username' => $this->string()->notNull(),
59 | 'email' => $this->string()->notNull(),
60 | 'passwordHash' => $this->string()->notNull(),
61 | // ...
62 | 'viewParams' => $this->text(), // field, which stores view parameters in serialized state
63 | ]);
64 | }
65 |
66 | public function down()
67 | {
68 | $this->dropTable('User');
69 | }
70 | }
71 | ```
72 |
73 | **Heads up!** In general such data storage approach is a **bad** practice and is not recommended to be used.
74 | Its main drawback is inability to use dynamic attributes in condition for the search query.
75 | It is acceptable only for the attributes, which are directly set and read for single record only, and never
76 | used for the filter queries.
77 |
78 | > Tip: you may store dynamic attributes into 'JSON' type column instead of plain text, in case you are using modern DBMS
79 | with built-in JSON support (e.g. MySQL >= 5.5 or PostgreSQL), however, you will have to deal with possible search
80 | condition composition on your own - this extension does not provide explicit support for it.
81 |
82 | This extension provides [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior]] ActiveRecord behavior for
83 | the dynamic attributes support.
84 | For example:
85 |
86 | ```php
87 | use yii\db\ActiveRecord;
88 | use yii2tech\ar\dynattribute\DynamicAttributeBehavior;
89 |
90 | class User extends ActiveRecord
91 | {
92 | public function behaviors()
93 | {
94 | return [
95 | 'dynamicAttribute' => [
96 | 'class' => DynamicAttributeBehavior::className(),
97 | 'storageAttribute' => 'viewParams', // field to store serialized attributes
98 | 'dynamicAttributeDefaults' => [ // default values for the dynamic attributes
99 | 'bgColor' => 'green',
100 | 'showSidebar' => true,
101 | ],
102 | ],
103 | ];
104 | }
105 |
106 | public static function tableName()
107 | {
108 | return 'User';
109 | }
110 |
111 | // ...
112 | }
113 | ```
114 |
115 | Once being attached [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior]] allows its owner to operate
116 | dynamic attributes just as regular one. On model save they will be serialized and stored into the holding
117 | field. After record is fetched from database the first attempt to read the dynamic attributes will unserialize
118 | them and prepare for the usage.
119 | For example:
120 |
121 | ```php
122 | $model = new User();
123 | // ...
124 | $model->bgColor = 'red';
125 | $model->showSidebar = false;
126 | $model->save(); // 'bgColor' and 'showSidebar' are serialized and stored at 'viewParams'
127 | echo $model->viewParams; // outputs: '{"bgColor": "red", "showSidebar": false}'
128 |
129 | $refreshedModel = User::findOne($model->getPrimaryKey());
130 | echo $refreshedModel->bgColor; // outputs 'red'
131 | echo $refreshedModel->showSidebar; // outputs 'false'
132 | ```
133 |
134 | You may use dynamic attributes as the regular ActiveRecord attributes. For example: you may
135 | specify the validation rules for them and obtain their values via web form.
136 |
137 | > Note: keep in mind that dynamic attributes do not correspond to ActiveRecord entity fields, thus
138 | some particular ActiveRecord methods like `updateAttributes()` will not work for them.
139 |
140 |
141 | ## Default values setup
142 |
143 | As you may note from above example, you can provide a default values for the dynamic attributes
144 | via [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior::$dynamicAttributeDefaults]].
145 | Thus once you need extra dynamic attribute for your model you can just update the `dynamicAttributeDefaults`
146 | list with corresponding value, without necessity to perform any updates on your database.
147 |
148 | ```php
149 | class User extends ActiveRecord
150 | {
151 | public function behaviors()
152 | {
153 | return [
154 | 'dynamicAttribute' => [
155 | 'class' => DynamicAttributeBehavior::className(),
156 | 'storageAttribute' => 'viewParams',
157 | 'dynamicAttributeDefaults' => [
158 | 'bgColor' => 'green',
159 | 'showSidebar' => true,
160 | 'fontColor' => 'black', // newly added attribute
161 | ],
162 | ],
163 | ];
164 | }
165 |
166 | // ...
167 | }
168 |
169 | $newModel = new User();
170 | echo $newModel->bgColor; // outputs 'green'
171 | echo $newModel->showSidebar; // outputs 'true'
172 |
173 | $oldModel = User::find()->orderBy(['id' => SORT_ASC])->limit(1)->one();
174 | echo $oldModel->viewParams; // outputs: '{"bgColor": "red", "showSidebar": false}'
175 | echo $oldModel->fontColor; // outputs: 'black'
176 | ```
177 |
178 | > Note: you may exclude dynamic attribute, which value equals the default one, from saving disabling
179 | [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior::$saveDynamicAttributeDefaults]] option.
180 |
181 |
182 | ## Restrict dynamic attribute list
183 |
184 | Setup of the dynamic attribute default values not only useful, but in general is necessary.
185 | This list puts a restriction on the possible dynamic attribute names. Only attributes, which
186 | have default value specified can be set or read from the model. This prevents the possible mistakes
187 | caused by typos in the code.
188 | For example:
189 |
190 | ```php
191 | $newModel = new User();
192 | $newModel->bgColor = 'blue'; // works fine
193 | $newModel->unExistingAttribute = 10; // throws an exception!
194 | ```
195 |
196 | However sometimes there is necessity of storage list of attributes, which can not be predicted.
197 | For example, saving response fields from some external service.
198 | In this case you can disable check performed on attribute setter using
199 | [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior::$allowRandomDynamicAttribute]].
200 | If it is set to `true` you will be able to setup any dynamic attribute no matter declared or not
201 | at `dynamicAttributeDefaults`.
202 |
203 | > Note: you can also use [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior::setDynamicAttributes()]] method
204 | to bypass naming restriction. This method will set all provided attributes without any checks.
205 |
206 | You can as well control the dynamic attributes list to be actually saved using [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior::$dynamicAttributeSaveFilter]].
207 | If set to `true` it will exclude any attribute, which is not listed at `dynamicAttributeDefaults` option. You may as
208 | well specify it as a PHP callback, which will perform some custom filtering. This option allows you to remove obsolete
209 | dynamic attributes, which existed in the past, but no longer actual.
210 |
211 |
212 | ## Serializer setup
213 |
214 | By default [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior]] saves the dynamic attribute in JSON
215 | format. However, you may setup another serializer for them via [[\yii2tech\ar\dynattribute\DynamicAttributeBehavior::$serializer]].
216 | The following serializers are available withing this extension:
217 |
218 | - [[\yii2tech\ar\dynattribute\JsonSerializer]] - stores data in JSON format
219 | - [[\yii2tech\ar\dynattribute\PhpSerializer]] - stores data using PHP `serialize()`/`unserialize()` functions
220 | - [[\yii2tech\ar\dynattribute\CallbackSerializer]] - stores data via custom serialize PHP callback.
221 | - [[\yii2tech\ar\dynattribute\JsonExpressionSerializer]] - handles [[yii\db\JsonExpression]] instances, supporting usage of 'JSON' DB column types.
222 |
223 | Please refer to the particular serializer class for more details.
224 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yii2tech/ar-dynattribute",
3 | "description": "Provides dynamic ActiveRecord attributes stored into the single field in serialized state",
4 | "keywords": ["yii2", "active", "record", "dynamic", "attribute"],
5 | "type": "yii2-extension",
6 | "license": "BSD-3-Clause",
7 | "support": {
8 | "issues": "https://github.com/yii2tech/ar-dynattribute/issues",
9 | "forum": "http://www.yiiframework.com/forum/",
10 | "wiki": "https://github.com/yii2tech/ar-dynattribute/wiki",
11 | "source": "https://github.com/yii2tech/ar-dynattribute"
12 | },
13 | "authors": [
14 | {
15 | "name": "Paul Klimov",
16 | "email": "klimov.paul@gmail.com"
17 | }
18 | ],
19 | "require": {
20 | "yiisoft/yii2": "~2.0.14"
21 | },
22 | "repositories": [
23 | {
24 | "type": "composer",
25 | "url": "https://asset-packagist.org"
26 | }
27 | ],
28 | "autoload": {
29 | "psr-4": {"yii2tech\\ar\\dynattribute\\": "src"}
30 | },
31 | "extra": {
32 | "branch-alias": {
33 | "dev-master": "1.0.x-dev"
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/CallbackSerializer.php:
--------------------------------------------------------------------------------
1 |
16 | * @since 1.0
17 | */
18 | class CallbackSerializer extends BaseObject implements SerializerInterface
19 | {
20 | /**
21 | * @var callable PHP callback, which should be used to serialize value.
22 | */
23 | public $serialize;
24 | /**
25 | * @var callable PHP callback, which should be used to unserialize value.
26 | */
27 | public $unserialize;
28 |
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public function serialize($value)
34 | {
35 | return call_user_func($this->serialize, $value);
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function unserialize($value)
42 | {
43 | return call_user_func($this->unserialize, $value);
44 | }
45 | }
--------------------------------------------------------------------------------
/src/DynamicAttributeBehavior.php:
--------------------------------------------------------------------------------
1 | [
33 | * 'class' => DynamicAttributeBehavior::className(),
34 | * 'storageAttribute' => 'viewParams', // field to store serialized attributes
35 | * 'dynamicAttributeDefaults' => [ // default values for the dynamic attributes
36 | * 'bgColor' => 'green',
37 | * 'showSidebar' => true,
38 | * ],
39 | * ],
40 | * ];
41 | * }
42 | *
43 | * // ...
44 | * }
45 | *
46 | * $model = new User();
47 | * $model->bgColor = 'red';
48 | * $model->showSidebar = false;
49 | * $model->save(); // 'bgColor' and 'showSidebar' are serialized and stored at 'viewParams'
50 | * echo $model->viewParams; // outputs: '{"bgColor": "red", "showSidebar": false}'
51 | * ```
52 | *
53 | * @property BaseActiveRecord $owner
54 | * @property string|array|SerializerInterface $serializer serializer instance or its configuration.
55 | * @property array $dynamicAttributes dynamic attributes in format: name => value.
56 | * @property bool $isDynamicAttributeInitialized whether the dynamic attributes have been initialized or not.
57 | *
58 | * @author Paul Klimov
59 | * @since 1.0
60 | */
61 | class DynamicAttributeBehavior extends Behavior
62 | {
63 | /**
64 | * @var string name of the owner attribute, which stores serialized dynamic attribute values.
65 | */
66 | public $storageAttribute = 'data';
67 | /**
68 | * @var array list of dynamic attribute default values.
69 | *
70 | * For example:
71 | *
72 | * ```php
73 | * [
74 | * 'hasComment' => false,
75 | * 'commentCount' => 0,
76 | * 'gender' => null,
77 | * ]
78 | * ```
79 | */
80 | public $dynamicAttributeDefaults = [];
81 | /**
82 | * @var bool whether to save dynamic attribute values, which are equals to the ones, specified via [[dynamicAttributeDefaults]].
83 | * By default `true`, which means default values will be saved st [[storageAttribute]].
84 | * If set to `false`, which means dynamic attribute, which value exactly matches (`===`) the one specified at [[dynamicAttributeDefaults]],
85 | * thus its value will pick up new default value if it is changed.
86 | */
87 | public $saveDynamicAttributeDefaults = true;
88 | /**
89 | * @var bool whether set of the attribute with the name, which is not exist neither at current [[dynamicAttributes]]
90 | * nor at [[$dynamicAttributeDefaults]], is allowed or not.
91 | * By default this option is disabled, providing the limitation of the dynamic attribute names, which can
92 | * be set via virtual property access or [[setDynamicAttribute()]] method.
93 | * If enabled dynamic attribute with any name will be allowed to be set.
94 | */
95 | public $allowRandomDynamicAttribute = false;
96 | /**
97 | * @var bool|callable whether to filter [[dynamicAttributes]] value before save.
98 | * Being `null` or `false` means no filtering is performed.
99 | * If set to `true` any attribute, which is not present at [[dynamicAttributeDefaults]] will be removed
100 | * before saving.
101 | * You may setup this option with PHP callback, which accept raw attribute list and should return filtered list:
102 | *
103 | * ```php
104 | * function (array $rawAttributes) {
105 | * return array
106 | * }
107 | * ```
108 | */
109 | public $dynamicAttributeSaveFilter;
110 |
111 | /**
112 | * @var array dynamic attributes in format: name => value.
113 | */
114 | private $_dynamicAttributes;
115 | /**
116 | * @var string|array|SerializerInterface serializer instance or its configuration.
117 | * Following shortcuts are supported:
118 | *
119 | * - 'php' - use [[PhpSerializer]]
120 | * - 'json' - use [[JsonSerializer]]
121 | *
122 | * Using array configuration, you may omit 'class' parameter, in this case [[CallbackSerializer]] will be used.
123 | * For example:
124 | *
125 | * ```php
126 | * [
127 | * 'serialize' => function ($value) { return serialize($value); },
128 | * 'unserialize' => function ($value) { return unserialize($value); },
129 | * ]
130 | * ```
131 | */
132 | private $_serializer = 'json';
133 |
134 |
135 | /**
136 | * Returns dynamic attribute values.
137 | * @return array dynamic attribute values in format: name => value.
138 | */
139 | public function getDynamicAttributes()
140 | {
141 | if ($this->_dynamicAttributes === null) {
142 | $this->_dynamicAttributes = $this->unserializeAttributes($this->owner->{$this->storageAttribute});
143 | if (!empty($this->dynamicAttributeDefaults)) {
144 | $this->_dynamicAttributes = array_merge($this->dynamicAttributeDefaults, $this->_dynamicAttributes);
145 | }
146 | }
147 | return $this->_dynamicAttributes;
148 | }
149 |
150 | /**
151 | * Sets dynamic attribute values.
152 | * Note that this method ignores [[allowRandomDynamicAttribute]] option.
153 | * @param array $dynamicAttributes dynamic attribute values in format: name => value.
154 | */
155 | public function setDynamicAttributes($dynamicAttributes)
156 | {
157 | $this->_dynamicAttributes = $dynamicAttributes;
158 | }
159 |
160 | /**
161 | * Returns the value of specified dynamic attribute.
162 | * @param string $name attribute name.
163 | * @return mixed attribute value.
164 | */
165 | public function getDynamicAttribute($name)
166 | {
167 | $attributes = $this->getDynamicAttributes();
168 | if (!array_key_exists($name, $attributes)) {
169 | throw new InvalidArgumentException('Getting unknown dynamic attribute: ' . get_class($this->owner) . '::$' . $name);
170 | }
171 | return $attributes[$name];
172 | }
173 |
174 | /**
175 | * Sets the value of the specified dynamic attribute.
176 | * @param string $name attribute name.
177 | * @param mixed $value attribute value.
178 | */
179 | public function setDynamicAttribute($name, $value)
180 | {
181 | $attributes = $this->getDynamicAttributes();
182 | if (!$this->allowRandomDynamicAttribute && !array_key_exists($name, $attributes)) {
183 | throw new InvalidArgumentException('Setting unknown dynamic attribute: ' . get_class($this->owner) . '::$' . $name);
184 | }
185 | $attributes[$name] = $value;
186 | $this->setDynamicAttributes($attributes);
187 | }
188 |
189 | /**
190 | * @return SerializerInterface serializer instance
191 | */
192 | public function getSerializer()
193 | {
194 | if (!is_object($this->_serializer)) {
195 | $this->_serializer = $this->createSerializer($this->_serializer);
196 | }
197 | return $this->_serializer;
198 | }
199 |
200 | /**
201 | * @param SerializerInterface|array|string $serializer serializer to be used
202 | */
203 | public function setSerializer($serializer)
204 | {
205 | $this->_serializer = $serializer;
206 | }
207 |
208 | /**
209 | * Creates serializer from given configuration.
210 | * @param string|array $config serializer configuration.
211 | * @return SerializerInterface serializer instance
212 | */
213 | protected function createSerializer($config)
214 | {
215 | if (is_string($config)) {
216 | switch ($config) {
217 | case 'php':
218 | $config = [
219 | 'class' => PhpSerializer::className()
220 | ];
221 | break;
222 | case 'json':
223 | $config = [
224 | 'class' => JsonSerializer::className()
225 | ];
226 | break;
227 | }
228 | } elseif (is_array($config)) {
229 | if (!isset($config['class'])) {
230 | $config['class'] = CallbackSerializer::className();
231 | }
232 | }
233 | return Instance::ensure($config, 'yii2tech\ar\dynattribute\SerializerInterface');
234 | }
235 |
236 | /**
237 | * @return bool whether the dynamic attributes have been initialized or not.
238 | */
239 | public function getIsDynamicAttributeInitialized()
240 | {
241 | return ($this->_dynamicAttributes !== null);
242 | }
243 |
244 | /**
245 | * Serializes given attributes into a string.
246 | * @param array $attributes attributes to be serialized in format: name => value
247 | * @return string serialized attributes.
248 | */
249 | protected function serializeAttributes($attributes)
250 | {
251 | ksort($attributes); // sort the data to facilitate 'dirty-attributes' AR feature
252 | return $this->getSerializer()->serialize($attributes);
253 | }
254 |
255 | /**
256 | * Restores attribute values from string.
257 | * @param string $source serialized data string.
258 | * @return array restored attributes.
259 | */
260 | protected function unserializeAttributes($source)
261 | {
262 | if (empty($source)) {
263 | return [];
264 | }
265 | return (array)$this->getSerializer()->unserialize($source);
266 | }
267 |
268 | // Property Access Extension:
269 |
270 | /**
271 | * PHP getter magic method.
272 | * This method is overridden so that dynamic attribute can be accessed like property.
273 | *
274 | * @param string $name property name
275 | * @throws UnknownPropertyException if the property is not defined
276 | * @return mixed property value
277 | */
278 | public function __get($name)
279 | {
280 | try {
281 | return parent::__get($name);
282 | } catch (UnknownPropertyException $exception) {
283 | $attributes = $this->getDynamicAttributes();
284 | if (array_key_exists($name, $attributes)) {
285 | return $attributes[$name];
286 | }
287 | throw $exception;
288 | }
289 | }
290 |
291 | /**
292 | * PHP setter magic method.
293 | * This method is overridden so that dynamic attribute can be accessed like property.
294 | * @param string $name property name
295 | * @param mixed $value property value
296 | * @throws UnknownPropertyException if the property is not defined
297 | */
298 | public function __set($name, $value)
299 | {
300 | try {
301 | parent::__set($name, $value);
302 | } catch (UnknownPropertyException $exception) {
303 | $attributes = $this->getDynamicAttributes();
304 | if (!$this->allowRandomDynamicAttribute && !array_key_exists($name, $attributes)) {
305 | throw $exception;
306 | }
307 | $attributes[$name] = $value;
308 | $this->setDynamicAttributes($attributes);
309 | }
310 | }
311 |
312 | /**
313 | * Checks if a property is set, i.e. defined and not null.
314 | *
315 | * Do not call this method directly as it is a PHP magic method that
316 | * will be implicitly called when executing `isset($object->property)`.
317 | *
318 | * Note that if the property is not defined, false will be returned.
319 | * @param string $name the property name or the event name
320 | * @return bool whether the named property is set (not null).
321 | * @see http://php.net/manual/en/function.isset.php
322 | */
323 | public function __isset($name)
324 | {
325 | if (parent::__isset($name)) {
326 | return true;
327 | }
328 | $attributes = $this->getDynamicAttributes();
329 | return isset($attributes[$name]);
330 | }
331 |
332 | /**
333 | * Sets an object property to null.
334 | *
335 | * Do not call this method directly as it is a PHP magic method that
336 | * will be implicitly called when executing `unset($object->property)`.
337 | *
338 | * Note that if the property is not defined, this method will do nothing.
339 | * If the property is read-only, it will throw an exception.
340 | * @param string $name the property name
341 | * @see http://php.net/manual/en/function.unset.php
342 | */
343 | public function __unset($name)
344 | {
345 | $attributes = $this->getDynamicAttributes();
346 | if (array_key_exists($name, $attributes)) {
347 | unset($attributes[$name]);
348 | $this->setDynamicAttributes($attributes);
349 | } else {
350 | parent::__unset($name);
351 | }
352 | }
353 |
354 | /**
355 | * {@inheritdoc}
356 | */
357 | public function canGetProperty($name, $checkVars = true)
358 | {
359 | if (parent::canGetProperty($name, $checkVars)) {
360 | return true;
361 | }
362 | $attributes = $this->getDynamicAttributes();
363 | return array_key_exists($name, $attributes);
364 | }
365 |
366 | /**
367 | * {@inheritdoc}
368 | */
369 | public function canSetProperty($name, $checkVars = true)
370 | {
371 | if (parent::canSetProperty($name, $checkVars)) {
372 | return true;
373 | }
374 | $attributes = $this->getDynamicAttributes();
375 | return $this->allowRandomDynamicAttribute || array_key_exists($name, $attributes);
376 | }
377 |
378 | // Events :
379 |
380 | /**
381 | * {@inheritdoc}
382 | */
383 | public function events()
384 | {
385 | return [
386 | BaseActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
387 | BaseActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
388 | ];
389 | }
390 |
391 | /**
392 | * Handles owner 'beforeInsert' and 'beforeUpdate' events, ensuring dynamic attributes are saved.
393 | * @param \yii\base\Event $event event instance.
394 | */
395 | public function beforeSave($event)
396 | {
397 | if (!$this->getIsDynamicAttributeInitialized()) {
398 | return;
399 | }
400 |
401 | $attributes = $this->getDynamicAttributes();
402 |
403 | if (!$this->saveDynamicAttributeDefaults) {
404 | foreach ($this->dynamicAttributeDefaults as $name => $value) {
405 | if (array_key_exists($name, $attributes) && $attributes[$name] === $value) {
406 | unset($attributes[$name]);
407 | }
408 | }
409 | }
410 |
411 | if ($this->dynamicAttributeSaveFilter !== null) {
412 | if ($this->dynamicAttributeSaveFilter === true) {
413 | $attributes = array_intersect_key($attributes, $this->dynamicAttributeDefaults);
414 | } else {
415 | $attributes = call_user_func($this->dynamicAttributeSaveFilter, $attributes);
416 | }
417 | }
418 |
419 | $data = $this->serializeAttributes($attributes);
420 |
421 | $this->owner->{$this->storageAttribute} = $data;
422 | }
423 | }
--------------------------------------------------------------------------------
/src/JsonExpressionSerializer.php:
--------------------------------------------------------------------------------
1 |
20 | * @since 1.0.2
21 | */
22 | class JsonExpressionSerializer extends BaseObject implements SerializerInterface
23 | {
24 | /**
25 | * @var string|null Type of JSON, expression should be casted to. Defaults to `null`, meaning
26 | * no explicit casting will be performed.
27 | * @see JsonExpression::$type
28 | */
29 | public $type;
30 |
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public function serialize($value)
36 | {
37 | return new JsonExpression($value, $this->type);
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function unserialize($value)
44 | {
45 | if ($value instanceof JsonExpression) {
46 | return $value->getValue();
47 | }
48 |
49 | return $value;
50 | }
51 | }
--------------------------------------------------------------------------------
/src/JsonSerializer.php:
--------------------------------------------------------------------------------
1 |
17 | * @since 1.0
18 | */
19 | class JsonSerializer extends BaseObject implements SerializerInterface
20 | {
21 | /**
22 | * @var int the encoding options. For more details please refer to
23 | * .
24 | * Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`.
25 | */
26 | public $options = 320;
27 |
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function serialize($value)
33 | {
34 | return Json::encode($value, $this->options);
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function unserialize($value)
41 | {
42 | return Json::decode($value);
43 | }
44 | }
--------------------------------------------------------------------------------
/src/PhpSerializer.php:
--------------------------------------------------------------------------------
1 |
16 | * @since 1.0
17 | */
18 | class PhpSerializer extends BaseObject implements SerializerInterface
19 | {
20 | /**
21 | * {@inheritdoc}
22 | */
23 | public function serialize($value)
24 | {
25 | return serialize($value);
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | public function unserialize($value)
32 | {
33 | return unserialize($value);
34 | }
35 | }
--------------------------------------------------------------------------------
/src/SerializerInterface.php:
--------------------------------------------------------------------------------
1 |
14 | * @since 1.0
15 | */
16 | interface SerializerInterface
17 | {
18 | /**
19 | * Serializes given value.
20 | * @param mixed $value value to be serialized
21 | * @return string serialized value.
22 | */
23 | public function serialize($value);
24 |
25 | /**
26 | * Restores value from its serialized representations
27 | * @param string $value serialized string.
28 | * @return mixed restored value
29 | */
30 | public function unserialize($value);
31 | }
--------------------------------------------------------------------------------