8000 [Validator] Callback constraint with Closure cannot be serialized · Issue #10083 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Validator] Callback constraint with Closure cannot be serialized #10083

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

Closed
Koc opened this issue Jan 20, 2014 · 12 comments
Closed

[Validator] Callback constraint with Closure cannot be serialized #10083

Koc opened this issue Jan 20, 2014 · 12 comments

Comments

@Koc
Copy link
Contributor
Koc commented Jan 20, 2014

This is very strange issue. There is edit form for Country entities and on one of entity this occurs.

I don't know how it possible to reproduce it on small test case. The problem is PDO instance and closures stores in the Apc cache when parsing metadata.

[1] PDOException: You cannot serialize or unserialize PDO instances
    at n/a
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/Mapping/Cache/ApcCache.php line 44

    at PDO->__wakeup()
        in  line 

    at apc_fetch('validator_66707dc10d9ce0f6680a042bb025bfd833eed70a6da1a1c506598b89de13182dProxies\__CG__\Metal\TerritorialBundle\Entity\Country')
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/Mapping/Cache/ApcCache.php line 44

    at Symfony\Component\Validator\Mapping\Cache\ApcCache->read('Proxies\__CG__\Metal\TerritorialBundle\Entity\Country')
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php line 61

    at Symfony\Component\Validator\Mapping\ClassMetadataFactory->getMetadataFor(object(Country))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/ValidationVisitor.php line 162

    at Symfony\Component\Validator\ValidationVisitor->validate(object(Country), 'Default', 'data', true, false)
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/ExecutionContext.php line 227

    at Symfony\Component\Validator\ExecutionContext->validate(object(Country), 'data', 'Default', true)
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php line 59

    at Symfony\Component\Form\Extension\Validator\Constraints\FormValidator->validate(object(Form), object(Form))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/ExecutionContext.php line 276

    at Symfony\Component\Validator\ExecutionContext->executeConstraintValidators(object(Form), array(object(Form)))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/ExecutionContext.php line 241

    at Symfony\Component\Validator\ExecutionContext->validateValue(object(Form), array(object(Form)))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/ValidationVisitor.php line 110

    at Symfony\Component\Validator\ValidationVisitor->visit(object(ClassMetadata), object(Form), 'Default', '')
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/Mapping/ClassMetadata.php line 107

    at Symfony\Component\Validator\Mapping\ClassMetadata->accept(object(ValidationVisitor), object(Form), 'Default', '')
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/ValidationVisitor.php line 162

    at Symfony\Component\Validator\ValidationVisitor->validate(object(Form), 'Default', '', false, false)
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Validator/Validator.php line 90

    at Symfony\Component\Validator\Validator->validate(object(Form))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php line 55

    at Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener->validateForm(object(FormEvent), 'form.post_bind', object(EventDispatcher))
        in  line 

    at call_user_func(array(object(ValidationListener), 'validateForm'), object(FormEvent), 'form.post_bind', object(EventDispatcher))
        in /vhosts/ololo.com/new/app/cache/metalloprokat/prod/classes.php line 1747

    at Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(array(array(object(ValidationListener), 'validateForm')), 'form.post_bind', object(FormEvent))
        in /vhosts/ololo.com/new/app/cache/metalloprokat/prod/classes.php line 1680

    at Symfony\Component\EventDispatcher\EventDispatcher->dispatch('form.post_bind', object(FormEvent))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php line 42

    at Symfony\Component\EventDispatcher\ImmutableEventDispatcher->dispatch('form.post_bind', object(FormEvent))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php line 653

    at Symfony\Component\Form\Form->submit(object(Request))
        in /vhosts/ololo.com/new/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php line 667

    at Symfony\Component\Form\Form->bind(object(Request))
        in /vhosts/ololo.com/new/vendor/sonata-project/admin-bundle/Sonata/AdminBundle/Controller/CRUDController.php line 301

    at Sonata\AdminBundle\Controller\CRUDController->editAction('209')
        in  line 

    at call_user_func_array(array(object(CRUDController), 'editAction'), array('209'))
        in /vhosts/ololo.com/new/app/bootstrap.php.cache line 2911

    at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), '1')
        in /vhosts/ololo.com/new/app/bootstrap.php.cache line 2883

    at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), '1', true)
        in /vhosts/ololo.com/new/app/bootstrap.php.cache line 3022

    at Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle(object(Request), '1', true)
        in /vhosts/ololo.com/new/app/bootstrap.php.cache line 2303

    at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
        in /vhosts/ololo.com/new/web/app.php line 49

    at require_once('/vhosts/ololo.com/new/web/app.php')
        in /vhosts/ololo.com/app.php line 11
@cordoval
Copy link
Contributor

since it is data dependent or at least it seems, could you please tell us details about which country or try to reproduce it? It seems this site is russian ... would need a SE fork reprod 8000 uction

@Koc
Copy link
Contributor Author
Koc commented Jan 20, 2014

I will try make fork but this issue is very hard reproducible. Maybe somewho guess where the problem could be...

@4rthem
Copy link
Contributor
4rthem commented Jul 22, 2014

It seems to be a Sonata Admin issue.
Sonata\AdminBundle\Admin\Admin::attachInlineValidator adds a constraint with a Closure which APC tries to serialize.

As @Koc, this problem occurs only on one of my entities.

@Koc
Copy link
Contributor Author
Koc commented Jul 22, 2014

But Symfony has Symfony\Component\Validator\Constraints\Callback which also accepts closure http://symfony.com/doc/current/reference/constraints/Callback.html#callback-option . How it works?

@Koc
Copy link
Contributor Author
8000 Koc commented Jul 22, 2014

// cc @webmozart

@4rthem
Copy link
Contributor
4rthem commented Jul 23, 2014

Yes but a Closure cannot be set in configuration (YAML, annotation or XML) but only in PHP code so it won't be cached.

I saw that the problem could come from the recursion in Symfony\Component\Validator\Mapping\ClassMetadataFactory::getMetadataFor:

  • getMetadataFor looks for the constraints of my entity Entity\Item and recursively merge its inherited constraints (from the parent classes)
  • Metadata for Entity\Item and its parents are cached
  • attachInlineValidator adds a constraint containing a Closure using $metadata->addConstraint(...)

For a reason I don't know, getMetadataFor is called for Proxies\__CG__\Entity\Item so:

  • getMetadataFor(Proxies\__CG__\Entity\Item) tries to merge parent constraints including Entity\Item ones. And here we have this Closure!

I think this problem occurs because my entity has a ManyToOne association which is referencing back this entity.

Item -> RelatedEntity -> Item (here a Proxy)

So this issue is not a SonataAdminBundle one but concerns the cache mechanism. Maybe the metadata should not cache constraints directly added in PHP code?

I hope to be clear.

@uwej711
Copy link
Contributor
uwej711 commented Aug 7, 2014

The following change for Symfony 2.3 fixes this:

diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/
index 77eb8b5..4e4e0b2 100644
--- a/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php
+++ b/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php
@@ -68,6 +68,14 @@ class ClassMetadataFactory implements MetadataFactoryInterface

         $metadata = new ClassMetadata($class);

+        if (null !== $this->loader) {
+            $this->loader->loadClassMetadata($metadata);
+        }
+
+        if (null !== $this->cache) {
+            $this->cache->write($metadata);
+        }
+
         // Include constraints from the parent class
         if ($parent = $metadata->getReflectionClass()->getParentClass()) {
             $metadata->mergeConstraints($this->getMetadataFor($parent->name));
@@ -81,13 +89,6 @@ class ClassMetadataFactory implements MetadataFactoryInterface
             $metadata->mergeConstraints($this->getMetadataFor($interface->name));
         }

-        if (null !== $this->loader) {
-            $this->loader->loadClassMetadata($metadata);
-        }
-
-        if (null !== $this->cache) {
-            $this->cache->write($metadata);
-        }

         return $this->loadedClasses[$class] = $metadata;
     }

@uwej711
Copy link
Contributor
uwej711 commented Aug 7, 2014

i.e. cache directly after loading and look for parents later. not sure if this breaks anything substantial

@stof
Copy link
Member
stof commented Aug 7, 2014

@uwej711 your proposal breaks the caching: given that you don't merge the parent constraint before caching, you need to update the code reading the cache so that merging parents is done after loading the cache too.

@uwej711
Copy link
Contributor
uwej711 commented Aug 7, 2014

Yes, you are right.

@uwej711
Copy link
Contributor
uwej711 commented Aug 7, 2014

So rather this way:

diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Cla
index 77eb8b5..f90977e 100644
--- a/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php
+++ b/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php
@@ -59,6 +59,7 @@ class ClassMetadataFactory implements MetadataFactoryInterface
         }

         if (null !== $this->cache && false !== ($this->loadedClasses[$class] = $this->cache->read($class))) {
+            $this->mergeParent($this->loadedClasses[$class]);
             return $this->loadedClasses[$class];
         }

@@ -68,6 +69,21 @@ class ClassMetadataFactory implements MetadataFactoryInterface

         $metadata = new ClassMetadata($class);

+        if (null !== $this->loader) {
+            $this->loader->loadClassMetadata($metadata);
+        }
+
+        if (null !== $this->cache) {
+            $this->cache->write($metadata);
+        }
+
+        $this->mergeParent($metadata);
+
+        return $this->loadedClasses[$class] = $metadata;
+    }
+
+    protected function mergeParent(ClassMetadata $metadata)
+    {
         // Include constraints from the parent class
         if ($parent = $metadata->getReflectionClass()->getParentClass()) {
             $metadata->mergeConstraints($this->getMetadataFor($parent->name));
@@ -80,16 +96,6 @@ class ClassMetadataFactory implements MetadataFactoryInterface
             }
             $metadata->mergeConstraints($this->getMetadataFor($interface->name));
         }
-
-        if (null !== $this->loader) {
-            $this->loader->loadClassMetadata($metadata);
-        }
-
-        if (null !== $this->cache) {
-            $this->cache->write($metadata);
-        }
-
-        return $this->loadedClasses[$class] = $metadata;
     }

     /**

Note: the order of the constraints change:

diff --git a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataFactoryTest.php b/src/Symfony/Component/Validator/T
index bee4025..19689d3 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataFactoryTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataFactoryTest.php
@@ -42,16 +42,16 @@ class ClassMetadataFactoryTest extends \PHPUnit_Framework_TestCase
         $constraints = array(
             new ConstraintA(array('groups' => array(
                 'Default',
-                'EntityParent',
                 'Entity',
             ))),
             new ConstraintA(array('groups' => array(
                 'Default',
-                'EntityInterface',
+                'EntityParent',
                 'Entity',
             ))),
             new ConstraintA(array('groups' => array(
                 'Default',
+                'EntityInterface',
                 'Entity',
             ))),
         );

@webmozart webmozart changed the title [Validator] You cannot serialize or unserialize PDO instances [Validator] Callback constraint with Closure cannot be serialized Oct 23, 2014
@webmozart webmozart added the Bug label Oct 23, 2014
@webmozart
Copy link
Contributor

Replaced by #12302.

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

No branches or pull requests

6 participants
0