Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-842

spl_object_hash tries to get hash from array - fails

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Critical Critical
    • Resolution: Invalid
    • Affects Version/s: 2.0-BETA4
    • Fix Version/s: None
    • Component/s: ORM
    • Security Level: All
    • Labels:
      None

      Description

      I have a model which looks like this:

      Class A
      protected $collectionB = array(); // Holds many Class B instances
      protected $collectionC = array(); // Holds many Class C instances
      
      Class B
      protected $classC; // Holds one Class C instance
      
      Class C
      protected $attributes; // Holds many attributes which are stored in another entitty with SINGLE_TABLE discriminator
      

      When I try to persist my object to the database, Doctrine causes this error:

      Message: spl_object_hash() expects parameter 1 to be object, array given
      File: C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\UnitOfWork.php
      Line: 2043
      Trace:
      
      #0 [internal function]: PHPUnit_Util_ErrorHandler::handleError(2, 'spl_object_hash...', 'C:\Users\Sebast...', 2043, Array)
      #1 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\UnitOfWork.php(2043): spl_object_hash(Array)
      #2 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\Persisters\ManyToManyPersister.php(110): Doctrine\ORM\UnitOfWork->getEntityIdentifier(Array)
      #3 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\Persisters\ManyToManyPersister.php(92): Doctrine\ORM\Persisters\ManyToManyPersister->_collectJoinTableColumnParameters(Object(Doctrine\ORM\PersistentCollection), Array)
      #4 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\Persisters\AbstractCollectionPersister.php(124): Doctrine\ORM\Persisters\ManyToManyPersister->_getInsertRowSQLParameters(Object(Doctrine\ORM\PersistentCollection), Array)
      #5 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\Persisters\AbstractCollectionPersister.php(104): Doctrine\ORM\Persisters\AbstractCollectionPersister->insertRows(Object(Doctrine\ORM\PersistentCollection))
      #6 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\UnitOfWork.php(303): Doctrine\ORM\Persisters\AbstractCollectionPersister->update(Object(Doctrine\ORM\PersistentCollection))
      #7 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Doctrine\ORM\EntityManager.php(320): Doctrine\ORM\UnitOfWork->commit()
      #8 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_models\trunk\library\App\Model\Service\Dao\Doctrine.php(26): Doctrine\ORM\EntityManager->flush()
      #9 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_models\trunk\library\App\Model\Service\Abstract.php(54): App_Model_Service_Dao_Doctrine->save(Object(App_Model_Ticket))
      #10 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_models\trunk\library\App\Model\Abstract.php(264): App_Model_Service_Abstract->save(Object(App_Model_Ticket))
      #11 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_models\trunk\library\App\Model\Ticket.php(75): App_Model_Abstract->save()
      #12 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\application\controllers\TicketController.php(69): App_Model_Ticket->save()
      #13 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Zend\Controller\Action.php(513): TicketController->putAction()
      #14 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Zend\Controller\Dispatcher\Standard.php(295): Zend_Controller_Action->dispatch('putAction')
      #15 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Zend\Controller\Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_HttpTestCase), Object(Zend_Controller_Response_HttpTestCase))
      #16 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\library\Zend\Test\PHPUnit\ControllerTestCase.php(199): Zend_Controller_Front->dispatch()
      #17 C:\Users\Sebastian Hoitz\Documents\Entwicklung\kt_api\trunk\tests\application\controllers\TicketControllerTest.php(220): Zend_Test_PHPUnit_ControllerTestCase->dispatch('/ticket/1')
      #18 [internal function]: TicketControllerTest->testUpdateTicketWithoutInvolvedContactsDoesNotCauseException()
      #19 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\Framework\TestCase.php(769): ReflectionMethod->invokeArgs(Object(TicketControllerTest), Array)
      #20 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\Framework\TestCase.php(659): PHPUnit_Framework_TestCase->runTest()
      #21 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\Framework\TestResult.php(617): PHPUnit_Framework_TestCase->runBare()
      #22 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\Framework\TestCase.php(607): PHPUnit_Framework_TestResult->run(Object(TicketControllerTest))
      #23 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\Framework\TestSuite.php(751): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
      #24 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\Framework\TestSuite.php(727): PHPUnit_Framework_TestSuite->runTest(Object(TicketControllerTest), Object(PHPUnit_Framework_TestResult))
      #25 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\Framework\TestSuite.php(687): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult), false, Array, Array, false)
      #26 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\TextUI\TestRunner.php(305): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult), false, Array, Array, false)
      #27 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\TextUI\Command.php(188): PHPUnit_TextUI_TestRunner->doRun(Object(PHPUnit_Framework_TestSuite), Array)
      #28 C:\Program Files (x86)\PHP\PEAR\pear\PHPUnit\TextUI\Command.php(129): PHPUnit_TextUI_Command->run(Array, true)
      #29 C:\Program Files (x86)\PHP\PEAR\phpunit(53): PHPUnit_TextUI_Command::main()
      #30 {main}
      

      I assume that this might happen, because of the Class B holding one Class C, and Class A also holding some Class C instances.

      Those are, in some cases, the same. So when a new Class B is added, together with a new Class C, this new Class C is also added to Class A.
      And somehow internally the already persisted Class C (Class B is getting persisted first) transforms the Class C instance into an array, and Doctrine can't save it anymore.

      This is my guess of what happens. When I skip adding all the Class C instances to Class A, I don't get this error.

        Activity

        Hide
        Marc Hodgins added a comment -

        Do you get the same error if the collections are defined as ArrayCollections instead of PHP arrays ? Not sure if it is supported to use pure PHP arrays for collections.

        See http://www.doctrine-project.org/projects/orm/2.0/docs/reference/association-mapping/en#collections

        /** @Entity */
        class A {
           /* ... */
           public function __construct() {
                $this->collectionB = new \Doctrine\Common\Collections\ArrayCollection;
           }
        
           /** @OneToMany(targetEntity="B", mappedBy="a") */
           protected $collectionB;
        }
        
        /** @Entity */
        class B {
            /* ... */
            /** @ManyToOne(targetEntity="A", inversedBy="collectionB") */
            protected $a;
        }  
        
        Show
        Marc Hodgins added a comment - Do you get the same error if the collections are defined as ArrayCollections instead of PHP arrays ? Not sure if it is supported to use pure PHP arrays for collections. See http://www.doctrine-project.org/projects/orm/2.0/docs/reference/association-mapping/en#collections /** @Entity */ class A { /* ... */ public function __construct() { $ this ->collectionB = new \Doctrine\Common\Collections\ArrayCollection; } /** @OneToMany(targetEntity= "B" , mappedBy= "a" ) */ protected $collectionB; } /** @Entity */ class B { /* ... */ /** @ManyToOne(targetEntity= "A" , inversedBy= "collectionB" ) */ protected $a; }
        Hide
        Sebastian Hoitz added a comment -

        I change the definition inside of my constructor method to ArrayCollection.

        It's just that I write array() when I define the properties to that it's easier for the developers to tell which properties are collections or not.

        So they actually are ArrayCollections.

        Show
        Sebastian Hoitz added a comment - I change the definition inside of my constructor method to ArrayCollection. It's just that I write array() when I define the properties to that it's easier for the developers to tell which properties are collections or not. So they actually are ArrayCollections.
        Hide
        Christian Heinrich added a comment - - edited

        Please post your entity mappings and some code.

        It would be highly appreciated if you could also provide a test case so that we can confirm easily. Thank you!

        Show
        Christian Heinrich added a comment - - edited Please post your entity mappings and some code. It would be highly appreciated if you could also provide a test case so that we can confirm easily. Thank you!
        Hide
        Sebastian Hoitz added a comment -

        This was a very stupid issue on my side which I wasn't even thinking about:

        I have my own "ModelCollection" class in which I store my models. This class extends Doctrines ArrayCollection class and overwrites the toArray function. It changed its behavior so that the toArray was called recursively also on all child elements.
        This caused the PersistentCollection to return an array on the getInsertDiff method.

        Long story short: Maybe we should think about making the toArray method on the ArrayCollection class final, since Doctrines ORM relies on its behavior.

        Show
        Sebastian Hoitz added a comment - This was a very stupid issue on my side which I wasn't even thinking about: I have my own "ModelCollection" class in which I store my models. This class extends Doctrines ArrayCollection class and overwrites the toArray function. It changed its behavior so that the toArray was called recursively also on all child elements. This caused the PersistentCollection to return an array on the getInsertDiff method. Long story short: Maybe we should think about making the toArray method on the ArrayCollection class final, since Doctrines ORM relies on its behavior.

          People

          • Assignee:
            Roman S. Borschel
            Reporter:
            Sebastian Hoitz
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: