diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 4520af0..23300b5 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -19,6 +19,7 @@ Yii Framework 2 Change Log - Enh #12592: Optimized `yii\filters\AccessController` on processing accessrules (dynasource) - Enh #12938: Allow to pass additional parameters to `yii\base\View::renderDynamic()` (mikehaertl) - Enh #13006: Added a `/` to the `yii\captcha\Captcha::$captchaAction` string to work correctly in a module also (boehsermoe) +- Enh #15410: Added serialization abstraction layer under `yii\serialize\*` namespace (klimov-paul) - Removed methods marked as deprecated in 2.0.x (samdark) - Chg #14784: Signature of `yii\web\RequestParserInterface::parse()` changed to accept `yii\web\Request` instance as a sole argument (klimov-paul) - Chg #10771: Consistent behavior of `run()` method in all framework widgets. All return the result now for better extensibility (pkirill99, cebe) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 872029d..8af546a 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -106,6 +106,9 @@ Upgrade from Yii 2.0.x be configured there. Creating your own cache implementation you should implement `\Psr\SimpleCache\CacheInterface` or extend `yii\caching\SimpleCache` abstract class. Use `yii\caching\CacheInterface` only if you wish to replace `yii\caching\Cache` component providing your own solution for cache dependency handling. +* `yii\caching\SimpleCache::$serializer` now should be `yii\serialize\SerializerInterface` instance or its DI compatible configuration. + Thus it does no longer accept pair of serialize/unserialize functions as an array. Use `yii\serialize\CallbackSerializer` or + other predefined serializer class from `yii\serialize\*` namespace instead. * Console command used to clear cache now calls related actions "clear" instead of "flush". * Yii autoloader was removed in favor of Composer-generated one. You should remove explicit inclusion of `Yii.php` from your entry `index.php` scripts. In case you have relied on class map, use `composer.json` instead of configuring it diff --git a/framework/caching/SimpleCache.php b/framework/caching/SimpleCache.php index 60ab521..5ae3117 100644 --- a/framework/caching/SimpleCache.php +++ b/framework/caching/SimpleCache.php @@ -9,7 +9,10 @@ namespace yii\caching; use Psr\SimpleCache\CacheInterface; use yii\base\Component; +use yii\di\Instance; use yii\helpers\StringHelper; +use yii\serialize\PhpSerializer; +use yii\serialize\SerializerInterface; /** * SimpleCache is the base class for cache classes implementing pure PSR-16 [[CacheInterface]]. @@ -46,32 +49,49 @@ abstract class SimpleCache extends Component implements CacheInterface */ public $keyPrefix = ''; /** - * @var null|array|false the functions used to serialize and unserialize cached data. Defaults to null, meaning - * using the default PHP `serialize()` and `unserialize()` functions. If you want to use some more efficient - * serializer (e.g. [igbinary](http://pecl.php.net/package/igbinary)), you may configure this property with - * a two-element array. The first element specifies the serialization function, and the second the deserialization - * function. If this property is set false, data will be directly sent to and retrieved from the underlying + * @var SerializerInterface|array|false the serializer to be used for serializing and unserializing of the cached data. + * Serializer should be an instance of [[SerializerInterface]] or its DI compatible configuration. For example: + * + * ```php + * [ + * 'class' => \yii\serialize\IgbinarySerializer::class + * ] + * ``` + * + * Default is [[PhpSerializer]], meaning using the default PHP `serialize()` and `unserialize()` functions. + * + * If this property is set `false`, data will be directly sent to and retrieved from the underlying * cache component without any serialization or deserialization. You should not turn off serialization if * you are using [[Dependency|cache dependency]], because it relies on data serialization. Also, some * implementations of the cache can not correctly save and retrieve data different from a string type. */ - public $serializer; + public $serializer = PhpSerializer::class; /** * {@inheritdoc} */ + public function init() + { + parent::init(); + + if ($this->serializer !== false) { + $this->serializer = Instance::ensure($this->serializer instanceof \Closure ? call_user_func($this->serializer) : $this->serializer, SerializerInterface::class); + } + } + + /** + * {@inheritdoc} + */ public function get($key, $default = null) { $key = $this->normalizeKey($key); $value = $this->getValue($key); if ($value === false || $this->serializer === false) { return $default; - } elseif ($this->serializer === null) { - return unserialize($value); } - return call_user_func($this->serializer[1], $value); + return $this->serializer->unserialize($value); } /** @@ -91,8 +111,7 @@ abstract class SimpleCache extends Component implements CacheInterface if ($this->serializer === false) { $results[$key] = $values[$newKey]; } else { - $results[$key] = $this->serializer === null ? unserialize($values[$newKey]) - : call_user_func($this->serializer[1], $values[$newKey]); + $results[$key] = $this->serializer->unserialize($values[$newKey]); } } } @@ -114,10 +133,8 @@ abstract class SimpleCache extends Component implements CacheInterface */ public function set($key, $value, $ttl = null) { - if ($this->serializer === null) { - $value = serialize($value); - } elseif ($this->serializer !== false) { - $value = call_user_func($this->serializer[0], $value); + if ($this->serializer !== false) { + $value = $this->serializer->serialize($value); } $key = $this->normalizeKey($key); $ttl = $this->normalizeTtl($ttl); @@ -131,10 +148,8 @@ abstract class SimpleCache extends Component implements CacheInterface { $data = []; foreach ($values as $key => $value) { - if ($this->serializer === null) { - $value = serialize($value); - } elseif ($this->serializer !== false) { - $value = call_user_func($this->serializer[0], $value); + if ($this->serializer !== false) { + $value = $this->serializer->serialize($value); } $key = $this->normalizeKey($key); $data[$key] = $value; diff --git a/framework/serialize/CallbackSerializer.php b/framework/serialize/CallbackSerializer.php new file mode 100644 index 0000000..87a2276 --- /dev/null +++ b/framework/serialize/CallbackSerializer.php @@ -0,0 +1,45 @@ + + * @since 2.1.0 + */ +class CallbackSerializer extends BaseObject implements SerializerInterface +{ + /** + * @var callable PHP callback, which should be used to serialize value. + */ + public $serialize; + /** + * @var callable PHP callback, which should be used to unserialize value. + */ + public $unserialize; + + + /** + * {@inheritdoc} + */ + public function serialize($value) + { + return call_user_func($this->serialize, $value); + } + + /** + * {@inheritdoc} + */ + public function unserialize($value) + { + return call_user_func($this->unserialize, $value); + } +} \ No newline at end of file diff --git a/framework/serialize/IgbinarySerializer.php b/framework/serialize/IgbinarySerializer.php new file mode 100644 index 0000000..4c46935 --- /dev/null +++ b/framework/serialize/IgbinarySerializer.php @@ -0,0 +1,36 @@ + + * @since 2.1.0 + */ +class IgbinarySerializer extends BaseObject implements SerializerInterface +{ + /** + * {@inheritdoc} + */ + public function serialize($value) + { + return igbinary_serialize($value); + } + + /** + * {@inheritdoc} + */ + public function unserialize($value) + { + return igbinary_unserialize($value); + } +} \ No newline at end of file diff --git a/framework/serialize/JsonSerializer.php b/framework/serialize/JsonSerializer.php new file mode 100644 index 0000000..20bd5d7 --- /dev/null +++ b/framework/serialize/JsonSerializer.php @@ -0,0 +1,44 @@ + + * @since 2.1.0 + */ +class JsonSerializer extends BaseObject implements SerializerInterface +{ + /** + * @var integer the encoding options. For more details please refer to + * . + * Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`. + */ + public $options = 320; + + + /** + * {@inheritdoc} + */ + public function serialize($value) + { + return Json::encode($value, $this->options); + } + + /** + * {@inheritdoc} + */ + public function unserialize($value) + { + return Json::decode($value); + } +} \ No newline at end of file diff --git a/framework/serialize/PhpSerializer.php b/framework/serialize/PhpSerializer.php new file mode 100644 index 0000000..81734cd --- /dev/null +++ b/framework/serialize/PhpSerializer.php @@ -0,0 +1,35 @@ + + * @since 2.1.0 + */ +class PhpSerializer extends BaseObject implements SerializerInterface +{ + /** + * {@inheritdoc} + */ + public function serialize($value) + { + return serialize($value); + } + + /** + * {@inheritdoc} + */ + public function unserialize($value) + { + return unserialize($value); + } +} \ No newline at end of file diff --git a/framework/serialize/SerializerInterface.php b/framework/serialize/SerializerInterface.php new file mode 100644 index 0000000..9b79878 --- /dev/null +++ b/framework/serialize/SerializerInterface.php @@ -0,0 +1,31 @@ + + * @since 2.1.0 + */ +interface SerializerInterface +{ + /** + * Serializes given value. + * @param mixed $value value to be serialized + * @return string serialized value. + */ + public function serialize($value); + + /** + * Restores value from its serialized representations + * @param string $value serialized string. + * @return mixed restored value + */ + public function unserialize($value); +} \ No newline at end of file diff --git a/tests/framework/serialize/CallbackSerializerTest.php b/tests/framework/serialize/CallbackSerializerTest.php new file mode 100644 index 0000000..b0cf9f0 --- /dev/null +++ b/tests/framework/serialize/CallbackSerializerTest.php @@ -0,0 +1,27 @@ + 'serialize', + 'unserialize' => 'unserialize', + ]); + } +} \ No newline at end of file diff --git a/tests/framework/serialize/IgbinarySerializerTest.php b/tests/framework/serialize/IgbinarySerializerTest.php new file mode 100644 index 0000000..9bc5755 --- /dev/null +++ b/tests/framework/serialize/IgbinarySerializerTest.php @@ -0,0 +1,37 @@ +markTestSkipped('igbinary extension is required.'); + return; + } + + parent::setUp(); + } + + /** + * {@inheritdoc} + */ + protected function createSerializer() + { + return new IgbinarySerializer(); + } +} \ No newline at end of file diff --git a/tests/framework/serialize/JsonSerializerTest.php b/tests/framework/serialize/JsonSerializerTest.php new file mode 100644 index 0000000..549e044 --- /dev/null +++ b/tests/framework/serialize/JsonSerializerTest.php @@ -0,0 +1,24 @@ + 'array']], + ]; + } + + /** + * @dataProvider dataProviderSerialize + * + * @param mixed $value + */ + public function testSerialize($value) + { + $serializer = $this->createSerializer(); + + $serialized = $serializer->serialize($value); + $this->assertTrue(is_string($serialized)); + + $this->assertEquals($value, $serializer->unserialize($serialized)); + } +} \ No newline at end of file