Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src/serializers/JsonSerializer.php doesn't work if a class has parameters in the constructor #455

Open
TheBlueAssasin opened this issue Sep 12, 2022 · 2 comments

Comments

@TheBlueAssasin
Copy link

TheBlueAssasin commented Sep 12, 2022

What steps will reproduce the problem?

Try to serialize an active record which has a datetime column with its default value

What's expected?

Serialize the object and then successfully unserializing it.

What do you get instead?

When you try to unserialize, you get an error because yii\db\Expression is missing required parameters in the constructor

Additional info

I have an idea to use reflection to get around the issue. Of course it will only work if the parameters are public properties. I will post here if it works.

<?php
class JsonSerializer extends \yii\queue\serializers\JsonSerializer
{
	public $constructorParamsKey = 'constructorParams';

	/**
	 * @param mixed $data
	 * @return array|mixed
	 * @throws InvalidConfigException
	 */
	protected function toArray($data)
	{
		if (is_object($data)) {
			$result = [$this->classKey => get_class($data)];
			foreach (get_object_vars($data) as $property => $value) {
				if ($property === $this->classKey) {
					throw new InvalidConfigException("Object cannot contain $this->classKey property.");
				}
				if ($property === $this->constructorParamsKey) {
					throw new InvalidConfigException("Object cannot contain $this->constructorParamsKey property.");
				}

				$result[$property] = $this->toArray($value);
			}

			$constructorParams = $this->getConstructorParams($data);

			if(!empty($constructorParams)){
				$result['constructorParams'] = $constructorParams;
			}

			return $result;
		}

		if (is_array($data)) {
			$result = [];
			foreach ($data as $key => $value) {
				if ($key === $this->classKey) {
					throw new InvalidConfigException("Array cannot contain $this->classKey key.");
				}
				$result[$key] = $this->toArray($value);
			}

			return $result;
		}

		return $data;
	}

	/**
	 * @param array $data
	 * @return mixed
	 */
	protected function fromArray($data)
	{
		if (!is_array($data)) {
			return $data;
		}

		if (!isset($data[$this->classKey])) {
			$result = [];
			foreach ($data as $key => $value) {
				$result[$key] = $this->fromArray($value);
			}

			return $result;
		}

		$config = ['class' => $data[$this->classKey]];
		unset($data[$this->classKey]);

		$constructorParams = [];
		if(isset($data['constructorParams'])){
			$constructorParams = $data['constructorParams'];
			unset($data['constructorParams']);
		}

		foreach ($data as $property => $value) {
			$config[$property] = $this->fromArray($value);
		}

		return Yii::createObject($config, $constructorParams);
	}

	/**
	 * Returns the constructor params and their values
	 * @param $object
	 * @return array
	 */
	protected function getConstructorParams($object)
	{
		$ref = new ReflectionClass($object);
		if (!$ref->isInstantiable()) {
			return [];
		}

		$parameters = [];
		$constructor = $ref->getConstructor();
		$params = $constructor->getParameters();

		foreach ($params as $param) {
			$name = $param->getName();
			if(property_exists($object, $name)) {
				$parameters[] = $object->{$name};
			}
		}

		return $parameters;
	}
}
Q A
Yii version 2.0.15.1
PHP version 7.2.34
Operating system Linux
@rob006
Copy link

rob006 commented Sep 12, 2022

Why you're using JsonSerializer? It is intended to be used for serializing simple jobs, and serializing ActiveRecord is way beyond "simple".

@TheBlueAssasin
Copy link
Author

Why you're using JsonSerializer? It is intended to be used for serializing simple jobs, and serializing ActiveRecord is way beyond "simple".

I'm not syncing the whole active record. Of course this would not be optimal. I use the queue to log a history of the changes of attributes. I only want to save a json field in the db with the changed attributes. And I have a column which is a datetime field, and I got this error, so I decided to report it.

I could use the PHP serializer, but JSON is better, because it is reusable with other languages and storage engines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants