[DDC-544] Extract Interface on Doctrine\ORM\Repository Created: 27/Apr/10  Updated: 09/Apr/13  Resolved: 09/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-ALPHA4
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Improvement Priority: Minor
Reporter: Benjamin Eberlei Assignee: Roman S. Borschel
Resolution: Won't Fix Votes: 0
Labels: None


 Description   

There should be an interface composed of all the methods on the Repository, so that in userland you can do something like:

interface IMyRepository extends Doctrine\ORM\IEntityRepository
{

}

class MyRepository extends Doctrine\ORM\EntityRepository implements IMyRepository
{

}

That way in your code you could type-hint for IMyRepository, or for the EntityRepository Interface even and it would be indefinately easier to mock or replace the implementation.



 Comments   
Comment by Roman S. Borschel [ 28/Apr/10 ]

To match our naming conventions that should rather be: interface EntityRepository and the old EntityRepository => BasicEntityRepository or similar.

Comment by Christian Heinrich [ 04/May/10 ]

Roman, do you mean that we should rename the file (and class) EntityRepository to BasicEntityRepository and create a new EntityRepository that contains an interface EntityRepository?

If you want me to, I could do this.

Regards
Christian

Comment by Roman S. Borschel [ 19/May/10 ]

@Christian: Yes, thats what I meant. I will schedule this for BETA3 though. I want BETA2 to be a smooth upgrade without any BC issues, if possible.

Comment by Roman S. Borschel [ 13/Jun/10 ]

I am wondering whether this is really necessary? There are not many methods on EntityRepository and its easy to create your own interface(s) for your repositories that include these few methods. To be consistent with our naming standards there would have to be a bc break which seems unnecessary to me, unless we invent some other name for the interface, i.e. "ObjectRepository".

Comment by Benjamin Eberlei [ 09/Jul/10 ]

Wont fix

Comment by Saem Ghani [ 08/Apr/13 ]

It's unfortunate this wasn't implemented, it's actually a significant issue for us. I have to say I completely disagree with Roman's reasoning in his last comment.

We have a very large database and commensurately many repositories. We're now at a point that we need to fire our own application level events (as an example, we end up having to compose in services a lot), some of them originating within repositories (doctrine events are insufficient, and we need application specific ones). We're using Symfony2 for DIC and build repositories not through doctrine but via services. We consider using setter injection to be a significant anti-pattern, an object should be valid/fully-initialized post construction so we'd rather not do that – if you were wondering. Now for all our repositories that we would like to build in extra functionality we create our own repository class, compose in the doctrine repository and implement the interface by convention (we could write our own, but the mileage sucks). Now where we could have passed in an instance of anything obeying that interface we're stuck (there goes type hinting). We can avoid some drudgery through traits but it's still an unfortunate solution.

If this is at all possible (even if it has a less than ideal name), we'd very much appreciate this, thank you.

PS. A BC break in beta would have been very easy and now it sucks even more.

Comment by Marco Pivetta [ 08/Apr/13 ]

Saem Ghani there is such an interface in doctrine common. Check Doctrine\Common\Persistence\ObjectRepository at https://github.com/doctrine/common/blob/2.3.2/lib/Doctrine/Common/Persistence/ObjectRepository.php

Comment by Saem Ghani [ 08/Apr/13 ]

Marco: I'm afraid that is incomplete. EntityRepository has a number of methods that fall outside of the scope of those prototyped in ObjectRepository and Selectable interfaces that it implements.

Comment by Benjamin Eberlei [ 08/Apr/13 ]

Why don't you ship your own layer of repositories and disregard the Doctrine ones completly? I do that all the time, and then constructor injection is very simple as well.

Comment by Saem Ghani [ 08/Apr/13 ]

Because the repositories provide features we use. Reusing/staying close to Symfony/Doctrine makes it easier to learn/train, and gives us herd immunity benefits around shared knowledge and most importantly testing. We're trying to reduce maintenance burden, this interface would not only help us do that but anyone else in a similar situation. Additionally, we have many projects most of them large, going across them we've got something like 400+ tables.

Comment by Marco Pivetta [ 08/Apr/13 ]

Saem Ghani your last comment does not really provide any rationale behind extraction of the remaining methods into an interface. There's no advantage in extracting those methods to an own interface. createQueryBuilder, createResultSetMappingBuilder, createNamedQuery, createNativeNamedQuery, clear, __call, getEntityManager, getClassMetadata are all utility methods that are not good candidates for an interface. You can easily re-implement those on an existing entity manager.

Just use the Doctrine\Common\Persistence\ObjectRepository API: you should actually stick to that to increase portability across the various object managers in doctrine project.

If you need all those methods too, you are looking for inheritance, not composition (and probably are delegating too much responsibility to the repository).

Comment by Saem Ghani [ 09/Apr/13 ]

Marco, rather than looking at each comment in isolation (which is what seems to be happening), please take things in aggregate. I've provided the rationale in previous comments. If you require more information I can clarify further.

Extracting as an interface would:

  • Make it easier to make mocks during testing (right now a mock EntityManager and ClassMetadata instance must also be mocked out), this is significant friction/noise during testing
  • Allow people to compose as opposed to inherit, especially important when you have features that are specific to relational stores
  • We have Entities that are represented by data in the database AND on the file system. All of a sudden inheriting from a doctrine EntityRepository makes just as much sense as inheriting from a FileSystemRepository (of our own making). All the while the responsibility is the same, load/store of Entities, the finders/query methods still make sense.
  • At the same time the finder API is very useful (duplicating the code would suck, as was suggested earlier), but having to manually maintain that contract with each doctrine release is an unnecessary burden, not just for us, but anyone else in our predicament
  • Creating our own interface means we can't abstract over 3rd party repositories provided by other bundles.




[DDC-627] Unexpected Duplicate Field Mapping Exception Created: 07/Jun/10  Updated: 02/Jun/11  Resolved: 13/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: Mapping Drivers
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Alexandre Brina Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

mysql Ver 14.14 Distrib 5.1.43, for Win32 (ia32)

PHP 5.3.1 (cli) (built: Feb 8 2010 22:11:49)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2009 Zend Technologies

  • with Zend Debugger v5.3, Copyright (c) 1999-2010, by Zend Technologies [loaded] [licensed] [enabled]

Issue Links:
Reference
is referenced by DDC-616 Reverse engineering with Oracle Resolved

 Description   

Trying to generate entities with annotation mappings using the CLI orm:convert-mapping command will throw an exception.

– Schema to reproduce
CREATE DATABASE IF NOT EXISTS `doctrine_issue`;
USE `doctrine_issue`;
CREATE TABLE `activity` (
`idact` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`idact`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `activity_log` (
`idacl` int(10) unsigned NOT NULL AUTO_INCREMENT,
`idact` int(10) unsigned NOT NULL,
PRIMARY KEY (`idacl`),
CONSTRAINT `fk_activity_data_activity` FOREIGN KEY (`idact`) REFERENCES `activity` (`idact`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

// configure a simple doctrine.php file to connect and run the following CLI command:
C:\>php doctrine.php orm:convert-mapping --from-database annotation .\Entities

Will throw an MappingException::duplicateFieldMapping on ActivityLog::idact, at line 1064 of class Doctrine\ORM\Mapping\ClassMetadataInfo



 Comments   
Comment by Benjamin Eberlei [ 13/Jun/10 ]

Fixed and scheduled for BETA 3

Comment by Cosmo [ 02/Jun/11 ]

Hi, I've got this exact issue in 2.1.0BETA1. Any idea how to work around it?





[DDC-577] Change default allocationSize from 10 to 1 Created: 07/May/10  Updated: 12/Aug/10  Resolved: 13/Jun/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Improvement Priority: Major
Reporter: Jan Tichý Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

I propose to change the default value of allocationSize for sequence columns from current 10 to 1. It's defined in $definition['allocationSize'] in Doctrine/ORM/Mapping/ClassMetaFactory.php.

The improvement request is based on detailed discussion on http://www.doctrine-project.org/jira/browse/DDC-569?focusedCommentId=12855&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#action_12855

Current default value for allocationSize=10 is CONFUSING and I am sure it will cause problems and misunderstoods for many people in future.

The core of the problem is that lot of people define table structure manually, separately from entity annotations. And while the default allocationSize is 10, they have to remember (and they have to know they should remember) each time they write new table and new entity, that:

a) They have to define each the sequence in database manually with INCREMENT BY 10
b) OR they have to define in each entity manually the annotation for @SequenceGenerator - allocationSize=1
c) OR they have to manually use strategy="IDENTITY", but it's primarily related to primary keys, not to all sequences at general.

Shortly, in an intuitive way everybody assumes the behaviour as allocationSize would be 1. Current default value 10 is not in accord with this intuitive perception and will cause many questions and misunderstoods.

The "preloading" of 10 values at once is a kind of "advanced optimalization" - it's great it is available and implemented - but it should not be applied automatically, but only after explicit setup.



 Comments   
Comment by Roman S. Borschel [ 05/Jun/10 ]

This should be changed for BETA3.

Comment by Benjamin Eberlei [ 13/Jun/10 ]

Fixed and scheduled for 2.0 BETA 3

Comment by Guilherme Blanco [ 12/Aug/10 ]

Actually patch was only committed now.

http://github.com/doctrine/doctrine2/commit/5719f8523bbb3c8a6fd11c749e5317b238d3d2b3





[DDC-719] Error in SQL subquery for a ManyToMany selfreferencing enitity when using the SIZE() or IS EMPTY dql function Created: 25/Jul/10  Updated: 07/Aug/10  Resolved: 07/Aug/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: DQL, Mapping Drivers, ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Steffen Vogel Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP 5.3, Ubuntu Lucid, MySQL 5, Apache 2.2


Attachments: File DDC719-patch.diff    

 Description   

I have an entity (Group) with a self-referencing ManyToMany association (parents, children).
Now im just trying to query all groups without a parent.

My DQL:

SELECT g, c, d FROM Volkszaehler\Model\Group g LEFT JOIN g.children c LEFT JOIN g.channels d  WHERE g.parents IS EMPTY

throws this PDOException:

object(PDOException)#31 (8) {
  ["message":protected]=>
  string(89) "SQLSTATE[42S02]: Base table or view not found: 1146 Table 'volkszaehler.5_' doesn't exist"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  string(5) "42S02"
  ["file":protected]=>
  string(90) "/home/steffen/workspace/doctrine/lib/vendor/doctrine-dbal/lib/Doctrine/DBAL/Connection.php"
  ["line":protected]=>
  int(568)
  ["trace":"Exception":private]=>
  array(9) {
    [0]=>
    array(6) {
      ["file"]=>
      string(90) "/home/steffen/workspace/doctrine/lib/vendor/doctrine-dbal/lib/Doctrine/DBAL/Connection.php"
      ["line"]=>
      int(568)
      ["function"]=>
      string(5) "query"
      ["class"]=>
      string(3) "PDO"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(1) {
        [0]=>
        string(604) "SELECT g0_.name AS name0, g0_.description AS description1, g0_.id AS id2, g0_.uuid AS uuid3, g1_.name AS name4, g1_.description AS description5, g1_.id AS id6, g1_.uuid AS uuid7, c2_.name AS name8, c2_.description AS description9, c2_.indicator AS indicator10, c2_.resolution AS resolution11, c2_.cost AS cost12, c2_.id AS id13, c2_.uuid AS uuid14 FROM groups g0_ LEFT JOIN groups_groups g3_ ON g0_.id = g3_.parent_id LEFT JOIN groups g1_ ON g1_.id = g3_.child_id LEFT JOIN groups_channel g4_ ON g0_.id = g4_.group_id LEFT JOIN channels c2_ ON c2_.id = g4_.channel_id WHERE (SELECT COUNT(*) FROM  5_) = 0"
      }
    }
    [1]=>
    array(6) {
      ["file"]=>
      string(85) "/home/steffen/workspace/doctrine/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php"
      ["line"]=>
      int(46)
      ["function"]=>
      string(12) "executeQuery"
      ["class"]=>
      string(24) "Doctrine\DBAL\Connection"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(3) {
        [0]=>
        string(604) "SELECT g0_.name AS name0, g0_.description AS description1, g0_.id AS id2, g0_.uuid AS uuid3, g1_.name AS name4, g1_.description AS description5, g1_.id AS id6, g1_.uuid AS uuid7, c2_.name AS name8, c2_.description AS description9, c2_.indicator AS indicator10, c2_.resolution AS resolution11, c2_.cost AS cost12, c2_.id AS id13, c2_.uuid AS uuid14 FROM groups g0_ LEFT JOIN groups_groups g3_ ON g0_.id = g3_.parent_id LEFT JOIN groups g1_ ON g1_.id = g3_.child_id LEFT JOIN groups_channel g4_ ON g0_.id = g4_.group_id LEFT JOIN channels c2_ ON c2_.id = g4_.channel_id WHERE (SELECT COUNT(*) FROM  5_) = 0"
        [1]=>
        array(0) {
        }
        [2]=>
        array(0) {
        }
      }
    }
    [2]=>
    array(6) {
      ["file"]=>
      string(59) "/home/steffen/workspace/doctrine/lib/Doctrine/ORM/Query.php"
      ["line"]=>
      int(265)
      ["function"]=>
      string(7) "execute"
      ["class"]=>
      string(44) "Doctrine\ORM\Query\Exec\SingleSelectExecutor"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(3) {
        [0]=>
        object(Doctrine\DBAL\Connection)#16 (11) {
          ["_conn":protected]=>
          object(Doctrine\DBAL\Driver\PDOConnection)#29 (0) {
          }
          ["_config":protected]=>
          object(Doctrine\ORM\Configuration)#7 (1) {
            ["_attributes":protected]=>
            array(7) {
              ["metadataCacheImpl"]=>
              object(Doctrine\Common\Cache\ApcCache)#8 (2) {
                ["_cacheIdsIndexId":"Doctrine\Common\Cache\AbstractCache":private]=>
                string(18) "doctrine_cache_ids"
                ["_namespace":"Doctrine\Common\Cache\AbstractCache":private]=>
                NULL
              }
              ["queryCacheImpl"]=>
              object(Doctrine\Common\Cache\ApcCache)#8 (2) {
                ["_cacheIdsIndexId":"Doctrine\Common\Cache\AbstractCache":private]=>
                string(18) "doctrine_cache_ids"
                ["_namespace":"Doctrine\Common\Cache\AbstractCache":private]=>
                NULL
              }
              ["metadataDriverImpl"]=>
              object(Doctrine\ORM\Mapping\Driver\AnnotationDriver)#13 (4) {
                ["_reader":"Doctrine\ORM\Mapping\Driver\AnnotationDriver":private]=>
                object(Doctrine\Common\Annotations\AnnotationReader)#9 (2) {
                  ["parser":"Doctrine\Common\Annotations\AnnotationReader":private]=>
                  object(Doctrine\Common\Annotations\Parser)#10 (6) {
                    ["lexer":"Doctrine\Common\Annotations\Parser":private]=>
                    object(Doctrine\Common\Annotations\Lexer)#11 (5) {
                      ["tokens":"Doctrine\Common\Lexer":private]=>
                      array(0) {
                      }
                      ["position":"Doctrine\Common\Lexer":private]=>
                      int(0)
                      ["peek":"Doctrine\Common\Lexer":private]=>
                      int(0)
                      ["lookahead"]=>
                      NULL
                      ["token"]=>
                      NULL
                    }
                    ["isNestedAnnotation":"Doctrine\Common\Annotations\Parser":private]=>
                    bool(false)
                    ["defaultAnnotationNamespace":"Doctrine\Common\Annotations\Parser":private]=>
                    string(21) "Doctrine\ORM\Mapping\"
                    ["namespaceAliases":"Doctrine\Common\Annotations\Parser":private]=>
                    array(0) {
                    }
                    ["context":"Doctrine\Common\Annotations\Parser":private]=>
                    string(0) ""
                    ["autoloadAnnotations":"Doctrine\Common\Annotations\Parser":private]=>
                    bool(false)
                  }
                  ["cache":"Doctrine\Common\Annotations\AnnotationReader":private]=>
                  object(Doctrine\Common\Cache\ArrayCache)#12 (3) {
                    ["data":"Doctrine\Common\Cache\ArrayCache":private]=>
                    array(0) {
                    }
                    ["_cacheIdsIndexId":"Doctrine\Common\Cache\AbstractCache":private]=>
                    string(18) "doctrine_cache_ids"
                    ["_namespace":"Doctrine\Common\Cache\AbstractCache":private]=>
                    NULL
                  }
                }
                ["_paths":protected]=>
                array(1) {
                  [0]=>
                  string(58) "/home/steffen/workspace/volkszaehler.org/backend/lib/Model"
                }
                ["_fileExtension":protected]=>
                string(4) ".php"
                ["_classNames":protected]=>
                NULL
              }
              ["proxyDir"]=>
              string(66) "/home/steffen/workspace/volkszaehler.org/backend/lib/Model/Proxies"
              ["proxyNamespace"]=>
              string(26) "Volkszaehler\Model\Proxies"
              ["autoGenerateProxyClasses"]=>
              bool(true)
              ["sqlLogger"]=>
              object(Volkszaehler\Util\Debug)#22 (5) {
                ["queries":protected]=>
                array(1) {
                  [0]=>
                  array(2) {
                    ["sql"]=>
                    string(604) "SELECT g0_.name AS name0, g0_.description AS description1, g0_.id AS id2, g0_.uuid AS uuid3, g1_.name AS name4, g1_.description AS description5, g1_.id AS id6, g1_.uuid AS uuid7, c2_.name AS name8, c2_.description AS description9, c2_.indicator AS indicator10, c2_.resolution AS resolution11, c2_.cost AS cost12, c2_.id AS id13, c2_.uuid AS uuid14 FROM groups g0_ LEFT JOIN groups_groups g3_ ON g0_.id = g3_.parent_id LEFT JOIN groups g1_ ON g1_.id = g3_.child_id LEFT JOIN groups_channel g4_ ON g0_.id = g4_.group_id LEFT JOIN channels c2_ ON c2_.id = g4_.channel_id WHERE (SELECT COUNT(*) FROM  5_) = 0"
                    ["parameters"]=>
                    array(0) {
                    }
                  }
                }
                ["messages":protected]=>
                array(0) {
                }
                ["started":protected]=>
                NULL
                ["level":protected]=>
                string(1) "1"
                ["created"]=>
                float(1280063214.6367)
              }
            }
          }
          ["_eventManager":protected]=>
          object(Doctrine\Common\EventManager)#14 (1) {
            ["_listeners":"Doctrine\Common\EventManager":private]=>
            array(0) {
            }
          }
          ["_isConnected":"Doctrine\DBAL\Connection":private]=>
          bool(true)
          ["_transactionNestingLevel":"Doctrine\DBAL\Connection":private]=>
          int(0)
          ["_transactionIsolationLevel":"Doctrine\DBAL\Connection":private]=>
          int(2)
          ["_params":"Doctrine\DBAL\Connection":private]=>
          array(5) {
            ["driver"]=>
            string(9) "pdo_mysql"
            ["host"]=>
            string(9) "localhost"
            ["user"]=>
            string(2) "vz"
            ["password"]=>
            string(4) "demo"
            ["dbname"]=>
            string(12) "volkszaehler"
          }
          ["_platform":protected]=>
          object(Doctrine\DBAL\Platforms\MySqlPlatform)#17 (1) {
            ["doctrineTypeMapping":protected]=>
            NULL
          }
          ["_schemaManager":protected]=>
          NULL
          ["_driver":protected]=>
          object(Doctrine\DBAL\Driver\PDOMySql\Driver)#15 (0) {
          }
          ["_isRollbackOnly":"Doctrine\DBAL\Connection":private]=>
          bool(false)
        }
        [1]=>
        array(0) {
        }
        [2]=>
        array(0) {
        }
      }
    }
    [3]=>
    array(6) {
      ["file"]=>
      string(67) "/home/steffen/workspace/doctrine/lib/Doctrine/ORM/AbstractQuery.php"
      ["line"]=>
      int(522)
      ["function"]=>
      string(10) "_doExecute"
      ["class"]=>
      string(18) "Doctrine\ORM\Query"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(0) {
      }
    }
    [4]=>
    array(6) {
      ["file"]=>
      string(67) "/home/steffen/workspace/doctrine/lib/Doctrine/ORM/AbstractQuery.php"
      ["line"]=>
      int(360)
      ["function"]=>
      string(7) "execute"
      ["class"]=>
      string(26) "Doctrine\ORM\AbstractQuery"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(2) {
        [0]=>
        array(0) {
        }
        [1]=>
        int(1)
      }
    }
    [5]=>
    array(6) {
      ["file"]=>
      string(83) "/home/steffen/workspace/volkszaehler.org/backend/lib/Controller/GroupController.php"
      ["line"]=>
      int(57)
      ["function"]=>
      string(9) "getResult"
      ["class"]=>
      string(26) "Doctrine\ORM\AbstractQuery"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(0) {
      }
    }
    [6]=>
    array(6) {
      ["file"]=>
      string(78) "/home/steffen/workspace/volkszaehler.org/backend/lib/Controller/Controller.php"
      ["line"]=>
      int(54)
      ["function"]=>
      string(3) "get"
      ["class"]=>
      string(39) "Volkszaehler\Controller\GroupController"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(0) {
      }
    }
    [7]=>
    array(6) {
      ["file"]=>
      string(67) "/home/steffen/workspace/volkszaehler.org/backend/lib/Dispatcher.php"
      ["line"]=>
      int(149)
      ["function"]=>
      string(3) "run"
      ["class"]=>
      string(34) "Volkszaehler\Controller\Controller"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(1) {
        [0]=>
        string(3) "get"
      }
    }
    [8]=>
    array(6) {
      ["file"]=>
      string(58) "/home/steffen/workspace/volkszaehler.org/backend/index.php"
      ["line"]=>
      int(55)
      ["function"]=>
      string(3) "run"
      ["class"]=>
      string(23) "Volkszaehler\Dispatcher"
      ["type"]=>
      string(2) "->"
      ["args"]=>
      array(0) {
      }
    }
  }
  ["previous":"Exception":private]=>
  NULL
  ["errorInfo"]=>
  array(3) {
    [0]=>
    string(5) "42S02"
    [1]=>
    int(1146)
    [2]=>
    string(37) "Table 'volkszaehler.5_' doesn't exist"
  }
}

Here is my Group Entitiy:

namespace Volkszaehler\Model;

use Doctrine\Common\Collections;

use Doctrine\Common\Collections\ArrayCollection;

/**
 * Group entity
 *
 * @author Steffen Vogel <info@steffenvogel.de>
 * @package default
 *
 * @Entity
 * @Table(name="groups")
 */
class Group extends Entity {
	/** @Column(type="string", nullable=false) */
	protected $name;

	/** @Column(type="string", nullable=true) */
	protected $description;

	/**
	 * @ManyToMany(targetEntity="Channel", inversedBy="groups")
	 * @JoinTable(name="groups_channel",
	 * 		joinColumns={@JoinColumn(name="group_id", referencedColumnName="id")},
	 * 		inverseJoinColumns={@JoinColumn(name="channel_id", referencedColumnName="id")}
	 * )
	 */
	protected $channels = NULL;

	/**
	 * @ManyToMany(targetEntity="Group", inversedBy="parents")
	 * @JoinTable(name="groups_groups",
	 * 		joinColumns={@JoinColumn(name="parent_id", referencedColumnName="id")},
	 * 		inverseJoinColumns={@JoinColumn(name="child_id", referencedColumnName="id")}
	 * )
	 */
	protected $children = NULL;

	/**
	 * @ManyToMany(targetEntity="Group", mappedBy="children")
	 */
	protected $parents = NULL;

	/**
	 * construct
	 */
	public function __construct() {
		parent::__construct();

		$this->channels = new ArrayCollection();
		$this->children = new ArrayCollection();
		$this->parents = new ArrayCollection();
	}

	/**
	 * adds group as new child
	 *
	 * @param Group $child
	 * @todo check against endless recursion
	 * @todo check if the group is already member of the group
	 */
	public function addGroup(Group $child) {
		$this->children->add($child);
	}

	/**
	 * adds channel as new child
	 *
	 * @param Channel $child
	 * @todo check if the channel is already member of the group
	 */
	public function addChannel(Channel $child) {
		$this->channels->add($child);
	}

	/**
	 * getter & setter
	 */
	public function getName() { return $this->name; }
	public function setName($name) { $this->name = $name; }
	public function getDescription() { return $this->description; }
	public function setDescription($description) { $this->description = $description; }
	public function getChildren() { return $this->children; }
	public function getParents() { return $this->parents; }
	public function getChannels() { return $this->channels; }
}


 Comments   
Comment by Benjamin Eberlei [ 25/Jul/10 ]

Looks like abug in the SQL Walker.

btw, you can use $e->getTraceAsString() to get a nice looking output for an exception. Its not as verbose as var_dump on the exception

Comment by Guilherme Blanco [ 03/Aug/10 ]

No, it is a bug on ClassMetadata.

The var_dump on Association of parents refers to a NULL on joinTable.
Problem seems to be deeper. I am creating a test case, will figure it out soon.

Comment by Guilherme Blanco [ 03/Aug/10 ]

Ok, it seems that on Mapping drivers we don't map jointables on opposite side.

So, if you map something on inversedBy and you grab the association from mappedBy side, you'll never have the jointable definition, because it is not exported to us.
This seems like a bug flaw we have on our code... not at the point of a critical, but we need to fix it asap.

Comment by Roman S. Borschel [ 03/Aug/10 ]

That the jointable info is only on the owning side is by design, not a flaw.

Comment by Guilherme Blanco [ 03/Aug/10 ]

Path to DDC-719 aswell as a possible issue with collection member implementation.

Comment by Guilherme Blanco [ 06/Aug/10 ]

In http://github.com/doctrine/doctrine2/commit/35af98260a525a841c05be15f52f8df455000066 I committed a fix to this issue.
Should be working now =)

Comment by Roman S. Borschel [ 07/Aug/10 ]

Reopening in order to correct the fixed version.

Comment by Roman S. Borschel [ 07/Aug/10 ]

Closing with correct version.





[DDC-518] Merging an entity with a one to one association to a MANAGED entity with no id throws 'The given entity has no identity.' Created: 13/Apr/10  Updated: 30/Jul/10  Resolved: 30/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: Dave Keen Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File ddc518.patch    

 Description   

Calling merge($entity) where $entity has a one to one association to another entity that has been persisted but not yet flushed (when using auto-generated ids) throws 'The given entity has no identity.'

It looks like it does this because _doMerge in UnitOfWork assumes for one to one associations only that the associated entity has an id and calls registerManaged, which then calls addToIdentityMap)on it.

I think that registeredManaged should only be called if !isset($this->_entityInsertions[spl_object_hash($other)])

Reproduce.php
// This is a new element
$doctor = new \vo\Doctor(); $d1->name = "New doctor";

// This is a detached element which is in the database
$patient = new \vo\Patient(); $p1->name = "Existing patient"; $p1->id = 1;

$doctor->patients->add($patient);
$patient ->doctor = $doctor;

$em->persist($doctor);

// This throws InvalidArgumentException: The given entity has no identity. in D:\Projects\ORM\flextrine2\flextrine\web\lib\Doctrine\ORM\UnitOfWork.php on line 1014
$em->merge($patient);
Doctor.php
class Doctor {

    /** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */
    public $id;
	
    /** @Column(length=100, type="string") */
    public $name;
	
	/**
     * @OneToMany(targetEntity="Patient", mappedBy="doctor", fetch="EAGER")
     */
	public $patients;
	
	public function __construct() {
		$this->patients = new ArrayCollection();
	}
	
}
Patient.php
class Patient {
	
	var $_explicitType = "vo/Patient";
	
    /** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */
    public $id;
	
    /** @Column(length=100, type="string") */
    public $name;

	/**
     * @OneToOne(targetEntity="Doctor", inversedBy="patients")
	 * @JoinColumn(name="doctor_id", referencedColumnName="id")
     */
	public $doctor;
	
}


 Comments   
Comment by Roman S. Borschel [ 08/May/10 ]

I think the order of operations in your example is not correct even though the error is misleading.

You are associating a "new doctor" to a "detached patient". That does not seem right, remember that merge() returns a managed copy, thus when merging the patient later, the "new doctor" is still associated with the "detached patient", not with the managed one.

The following should work:

// Merge detached patient
$managedPatient = $em->merge($patient);

// Associate new doctor with patient
$doctor->patients->add($managedPatient);
$managedPatient->doctor = $doctor;

// Persist new doctor and flush
$em->persist($doctor);
$em->flush();
Comment by Dave Keen [ 16/May/10 ]

You are quite right - that does work.

However, I am now trying to implement my use case (turning a tree of detached objects into a tree of managed objects) and implementing the correct order you describe above seems to cause another problem. I am not sure if this should be another ticket, but I'll put it here for the moment.

Note that the part within the stars that creates the unmanaged doctor and the detached patient simulates exactly what is received by Doctrine in my application so I can't put any merges in here.

/********************************************************************/
// This is a new element
$doctor = new \vo\Doctor(); $doctor->name = "New doctor";

// This is a detached element which is in the database
$patient = new \vo\Patient(); $patient->name = "Existing patient"; $patient->id = 1;

$doctor->patients->add($patient); $patient->doctor = $doctor;
/********************************************************************/

// Now replace $patient with its managed version
$managedPatient = $em->merge($patient);
$doctor->patients->set(0, $managedPatient);

// Persist the doctor
$em->persist($doctor);

$em->flush();

This works up to the flush, which throws an error of the form:

Notice: Undefined index: 000000007dd346c3000000005d0908d2 in \Doctrine\ORM\UnitOfWork.php on line 1955

In the database this results in a new doctor being created, but doctor_id in the patients table is set to NULL for the patient that is supposed to be linking to it.

Comment by Benjamin Eberlei [ 06/Jun/10 ]

I think this can't work, because obviously $doctor->patients still points to the unmanaged $patient, not the managed one.

Can you elaborate on why the star part is done by Doctrine? Where is doctrine doing that? what are you doing with the public API?

All the detached entities should be merged before doing anything regarding a NEW entity in my opinion.

Comment by Benjamin Eberlei [ 06/Jun/10 ]

Moved to beta3 for now

Comment by Benjamin Eberlei [ 06/Jun/10 ]

Patch with test-case for this merging scenario

Comment by Roman S. Borschel [ 30/Jul/10 ]

Fixed in http://github.com/doctrine/doctrine2/commit/69073c4b37ee28f988306db4965f512b70f45181





[DDC-634] Merging an entity with a single valued association that has been set to null has no effect Created: 12/Jun/10  Updated: 30/Jul/10  Resolved: 30/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Dave Keen Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None


 Description   

When merging an owning entity which has NULL as one of its associated properties, merge doesn't realize that it needs to disassociate the entity.

class Doctor {
	
    /** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */
    public $id;
	
	/**
     * @OneToMany(targetEntity="Patient", mappedBy="doctor", fetch="EAGER")
     */
	public $patients;
	
	public function __construct() {
		$this->patients = new ArrayCollection();
	}
	
}
class Patient {
	
    /** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */
    public $id;

	/**
     * @OneToOne(targetEntity="Doctor", inversedBy="patients")
	 * @JoinColumn(name="doctor_id", referencedColumnName="id")
     */
	public $doctor;
	
	public function __construct() {
	}
	
}

Assume that in the database there exists a doctor id=1 and a patient id=1. The patient belongs to the doctor, so the patient table has doctor_id = 1;

$p1 = new \vo\Patient();
$p1->id = 1;
$p1->doctor = null;
$em->merge($p1);
$em->flush();

This does not set doctor_id to null as expected.

It can be fixed by changing the block at line 1373 in UnitOfWork.php as follow. Apologies for not providing a patch file, I haven't quite got the hang of git yet

                           if ($other !== null) {
                                $targetClass = $this->_em->getClassMetadata($assoc2->targetEntityName);
                                $id = $targetClass->getIdentifierValues($other);
                                $proxy = $this->_em->getProxyFactory()->getProxy($assoc2->targetEntityName, $id);
                                $prop->setValue($managedCopy, $proxy);
                                $this->registerManaged($proxy, $id, array());
                            } else {
								$prop->setValue($managedCopy, null);
			   }


 Comments   
Comment by Roman S. Borschel [ 30/Jul/10 ]

Fixed in http://github.com/doctrine/doctrine2/commit/69073c4b37ee28f988306db4965f512b70f45181





[DDC-644] [ORACLE] setting query maxResults together with firstResult leads to an error Created: 17/Jun/10  Updated: 28/Jul/10  Resolved: 28/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: Martin Ivičič Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP 5.3.2


Attachments: Zip Archive ReflectionException.zip    

 Description   

I'm not very skilled in doctrine yet, but I found something that might be worth reporting.

The code below produces "Class does not exist" error, which is quite misleading and just a side effect of a Notice that shows up in AbstractHydrator.php (lines 190 and 191).

		$q = DBConnection::Common()->createQueryBuilder()->select('c')->from('COMPANY', 'c')->getQuery();
		$q->setMaxResults(15);
		$q->setFirstResult(1);
		var_dump($query->getResult());

The query becomes:

SELECT b.* FROM (SELECT a.*, ROWNUM AS doctrine_rownum FROM (SELECT c0_.ID_COMPANY AS ID_COMPANY0, c0_.CONTACT AS CONTACT1, c0_.CONTRACT AS CONTRACT2, c0_.DESCRIPTION AS DESCRIPTION3, c0_.ID_DISPLAY_CASE AS ID_DISPLAY_CASE4, c0_.MAX_OPEN_WIN AS MAX_OPEN_WIN5, c0_.NAME AS NAME6, c0_.REFRESH_TIME AS REFRESH_TIME7, c0_.STATUS AS STATUS8, c0_.TABLESPACE AS TABLESPACE9 FROM COMPANY c0_) a ) b WHERE doctrine_rownum BETWEEN 2 AND 16

The problem occurs when iterating over the list of internal column aliases (ID_COMPANY0,DESCRIPTION3,ID_DISPLAY_CASE4,MAX_OPEN_WIN5,NAME6,REFRESH_TIME7,STATUS8,TABLESPACE9 and
DOCTRINE_ROWNUM). The last one "DOCTRINE_ROWNUM" is missing in both $this->_rsm->metaMappings and $this->_rsm->columnOwnerMap and it's not a matter of case sensitivity, it's missing completely.

Result:

$cache[$key]['dqlAlias'] becomes NULL
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;

(line 203) creates a new key (named as empty string) in $rowData and things get screwed up since then in further iterations over $rowData array.

Unfortunately I'm not familiar with the internal code of doctrine at all, and it seems too complex for me to be able to provide a fix (except for an ugly hack).

I've attached the Exception trace (might help).

BTW: Doctrine 2 is the cleanest piece of complex code I've ever seen. You guys rock !!!



 Comments   
Comment by Benjamin Eberlei [ 28/Jun/10 ]

fixed formating

Comment by Benjamin Eberlei [ 28/Jul/10 ]

Fixed





[DDC-723] Upgrade ORM to depend on DBAL Beta3 Created: 28/Jul/10  Updated: 28/Jul/10  Resolved: 28/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Improvement Priority: Major
Reporter: Benjamin Eberlei Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

We need to raise the dependency of ORM to DBAL BETA 3 before the ORM Beta 3 release.



 Comments   
Comment by Benjamin Eberlei [ 28/Jul/10 ]

Done!





[DDC-714] Fix of DDC-167 creates FatalError when persisting a new entity Created: 22/Jul/10  Updated: 25/Jul/10  Resolved: 25/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA3, 2.0-BETA4
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Blocker
Reporter: Michael Zach Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

Debian 5 (64bit), Postgresql 8.3, ZendServer 5.0.2, PHP 5.3.2, Doctrine-HEAD


Attachments: File uow.diff    

 Description   

The resolution of DDC-167 introduced a new problem in UnitOfWork on line 612 (function persistNew()) as well - when I try to save an entity, I get an Exception from my error handler (who captues php errors):

ErrorException with Argument 2 passed to Doctrine\ORM\Mapping\ClassMetadata::setIdentifierValues() must be an array, integer given, called in /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php on line 612 and defined
Backtrace: #0: Doctrine\ORM\Mapping\ClassMetadata->setIdentifierValues at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php:612
#1: Doctrine\ORM\UnitOfWork->persistNew at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php:1247
#2: Doctrine\ORM\UnitOfWork->doPersist at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php:1210
#3: Doctrine\ORM\UnitOfWork->persist at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/EntityManager.php:438

The relevant code in UoW is:

            $idValue = $idGen->generate($this->em, $entity);
            if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) {
                $this->entityIdentifiers[$oid] = array($class->identifier[0] => $idValue);
                $class->setIdentifierValues($entity, $idValue);

We're using the SequenceGenerator

@SequenceGenerator(allocationSize=1,sequenceName="address_id_seq")

which doesn't return an array, so the array typehint fails and generates an error.

The fix, which worked for me, is attached.



 Comments   
Comment by Benjamin Eberlei [ 25/Jul/10 ]

This bug also leads to about 400 test failures in the Postgres ORM Testsuite

Comment by Benjamin Eberlei [ 25/Jul/10 ]

Fixed! Thanks for reporting.





[DDC-716] Proxy autogeneration fails with concurrent requests Created: 22/Jul/10  Updated: 24/Jul/10  Resolved: 24/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Jaka Jancar Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

Debian 5.0.5, Apache 2.2.9, PHP 5.3.2



 Description   

When doing concurrent requests with autogeneration of proxies enabled, the proxy file does not exist when ProxyFactory tries to use it:

Doctrine/ORM/Proxy/ProxyFactory.php(92): spl_autoload_call('MyClassProxy'))

I think this is because file_put_contents is not atomic.



 Comments   
Comment by Jaka Jancar [ 22/Jul/10 ]

The following fixes it on Linux, but I think will not work in Windows (iirc, rename() on Windows won't work if the dest file already exists, much less atomically):


--- Proxy/ProxyFactory.php	(revision 2)
+++ Proxy/ProxyFactory.php	(working copy)
@@ -144,7 +144,9 @@
 
         $file = str_replace($placeholders, $replacements, $file);
 
-        file_put_contents($fileName, $file);
+        $tmpFileName = $fileName.'-'.uniqid('', true);;
+        file_put_contents($tmpFileName, $file);
+        rename($tmpFileName, $fileName);
     }
 
     /**
Comment by Jaka Jancar [ 22/Jul/10 ]

I don't even know why this is needed. Can't the file just be returned as string and eval()'d, instead of being written to a file and then require()'d?

Comment by Benjamin Eberlei [ 22/Jul/10 ]

we should just change file_put_Contents into:

file_put_contents($fileName, $file, LOCK_EX);
Comment by Benjamin Eberlei [ 22/Jul/10 ]

Setting the LOCK_EX constant now, this should solve the issue.

However in high concurrency scenarios the "autoGenerateProxyClasses" flag should always be FALSE and the proxies be generated during build-time.

Comment by Jaka Jancar [ 22/Jul/10 ]

LOCK_EX doesn't fix it for me. Apparently it's still possible that the file doesn't exist:

E_WARNING (2): include(MyProxies/MyClassProxy.php) [<a href='function.include'>function.include</a>]: failed to open stream: No such file or directory

Please note that my above uniqid+rename suggestion only works if more_entropy is true, if you decide to add it.

However, I'd much prefer having an option of just not using these files at all. I've created an Improvement ticket DDC-717 for this.

Comment by Benjamin Eberlei [ 23/Jul/10 ]

i now underestand what you are doing wrong.

if you set autogenerate = false you have to call doctrine orm:generate-proxies

Comment by Jaka Jancar [ 23/Jul/10 ]

I'm not setting it to false!

> When doing concurrent requests with autogeneration of proxies enabled

Comment by Benjamin Eberlei [ 23/Jul/10 ]

ah ok, eval is just a workaround.

Comment by Benjamin Eberlei [ 24/Jul/10 ]

Closed again, See DDC-717 for a timetable of a fix using auto-generate = true in production.





[DDC-600] Persisting Entities with unmanaged related associations produces ugly notices Created: 18/May/10  Updated: 24/Jul/10  Resolved: 07/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Improvement Priority: Major
Reporter: Benjamin Eberlei Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None

Issue Links:
Reference
is referenced by DDC-718 Bottleneck in computeAssociationChang... Open

 Description   

It often happens that you forget to persist related entities during development, producing ugly notices about $object hash not being part of certain arrays, for example in "getEntityIdentifier".

Maybe this can be gracefully intercepted without cluttering the code?






[DDC-167] New method: EntityManager#getPartialReference Created: 20/Nov/09  Updated: 22/Jul/10  Resolved: 20/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Improvement Priority: Major
Reporter: Roman S. Borschel Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None


 Description   

Currently, EntityManager#getReference can be used to retrieve cheap references to "persistent" objects without actually loading them. The returned proxies only initialize themselves automatically when one of their methods is invoked. This can lead to unnecessary/unwanted initialization in cases of bidirectional associations where the "association management methods" (setFoo) properly set the other side of the association. Example (one-one, User-Address, User owning side):

class User {
    private $address;
    public function setAddress(Address $address) {
        if ($address !== $this->address) {
            $this->address = $address;
            $address->setUser($this);
        }
    }
}

class Address {
    private $user;
    public function setUser(User $user) {
        if ($user !== $this->user) {
            $this->user = $user;
            $user->setAddress($this);
        }
    }
}

Now, assuming the following code that makes use of getReference to associate a user to an address but actually wants to avoid loading the address.

// assuming $user is already loaded and now we want to associate it to an existing address
// but without actually loading the address

$addressId = 42; // from request, $_POST/$_GET/...
$address = $em->getReference('Address', $addressId);

$user->setAddress($address); // calls $address->setUser which initializes the proxy!
$em->flush();

A possible solution could be to allow obtaining single references to partial objects through the EntityManager. Partial objects can already be fetched through querying with the Query::HINT_FORCE_PARTIAL_LOAD query hint so the possibility to get a single partial object by ID seems to make sense.

A proposed method name is: getPartialReference($className, $id). It would return a managed (partial) instance of $className where only the identifier is populated and that will not initialize itself (hence "partial"). In the example above, usage of a partial reference object would avoid the extra initialization.



 Comments   
Comment by Benjamin Eberlei [ 20/Nov/09 ]

Sounds good to me, the method name is also very clear about that the retrieved object is partial, the only negative is that its really the only use-case for this method.

Comment by Roman S. Borschel [ 21/Nov/09 ]

I think this has some more use-cases. For example if you only want a single object and you have its ID and all you really need is the ID during that request. Proxy objects are initialized even if you call getId() because we (the proxy) cant know what happens inside that method.

Of course, partial objects are always dangerous and I think this is made pretty clear in the manual but the choice is up to the user in the end.

Comment by Roman S. Borschel [ 12/Mar/10 ]

Another interesting use-case: updating an object without loading it (as an alternative to a DQL bulk UPDATE):

$user = $em->getPartialReference('User', $userId);
$user->setName('newname');
$em->flush();

Of course one needs to be aware that the original entity data in such an update is not "correct", i.e. when using event listeners.

Comment by Roman S. Borschel [ 19/May/10 ]

Rescheduling for beta3.

Comment by Michael Zach [ 21/Jul/10 ]

Hello,

this fix needs to be implemented in UnitOfWork on line 612 (function persistNew()) as well - when I try to save an entity, I get an Exception from my error handler:

ErrorException with Argument 2 passed to Doctrine\ORM\Mapping\ClassMetadata::setIdentifierValues() must be an array, integer given, called in /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php on line 612 and defined
Backtrace: #0: Doctrine\ORM\Mapping\ClassMetadata->setIdentifierValues at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php:612
#1: Doctrine\ORM\UnitOfWork->persistNew at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php:1247
#2: Doctrine\ORM\UnitOfWork->doPersist at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/UnitOfWork.php:1210
#3: Doctrine\ORM\UnitOfWork->persist at /var/www/svn/cWorld_ZF/branches/devel-trunk/library/Doctrine/ORM/EntityManager.php:438

The relevant code in UoW is:

            $idValue = $idGen->generate($this->em, $entity);
            if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) {
                $this->entityIdentifiers[$oid] = array($class->identifier[0] => $idValue);
                $class->setIdentifierValues($entity, $idValue);

We're using the SequenceGenerator

@SequenceGenerator(allocationSize=1,sequenceName="address_id_seq")

which doesn't return an array, so the array typehint fails and generates an error.

Comment by Michael Zach [ 21/Jul/10 ]

Possible fix in UoW, line 612:

$class->setIdentifierValues($entity, $this->entityIdentifiers[$oid]);
Comment by Benjamin Eberlei [ 21/Jul/10 ]

please open a new issue, or reopen this one. posting into closed issues does more harm than good

Comment by Michael Zach [ 22/Jul/10 ]

Hello Benjamin,

since I've no rights to re-open this entry I had to create a new one: http://www.doctrine-project.org/jira/browse/DDC-714

Comment by Benjamin Eberlei [ 22/Jul/10 ]

hm, maybe our user-rirghts arre strange. I look into it later





[DDC-697] Support for DateTime in query parameters Created: 19/Jul/10  Updated: 21/Jul/10  Resolved: 21/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: DQL
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: New Feature Priority: Major
Reporter: Patrik Votoček Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

Supports native DateTime in query parameters. (for more universal using)

Column definition
/**
 * @var DateTime
 *
 * @Column(type="datetime")
 */
private $published;
Query
$qb = $em->getRepository('Entities\Foo')->createQueryBuilder('f');
$qb->where("f.published <= ?1");
$qb->setParameter(1, new \DateTime('2012-12-21 23:59:59'));

Now display "Object of class DateTime could not be converted to string" error



 Comments   
Comment by Benjamin Eberlei [ 19/Jul/10 ]

This is another case for possible optimizations in my opinion, is it possible to access the parameter needles from the ResultSetMapping? If so then we should add a convertToParam method to each Doctrine\DBAL\Types\Type and allow conversions to take place or just do nothing. This woulld help with this issue, aswell as with other more complex types to be bound.

Comment by Benjamin Eberlei [ 19/Jul/10 ]

This affects the following loop inside Doctrine\ORM\Query:

http://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Query.php#L234

Comment by Benjamin Eberlei [ 20/Jul/10 ]

According to Roman this should already be possible with:

$qb->setParameter(1, new \DateTime('2012-12-21 23:59:59'), \Doctrine\DBAL\Types\Type::DATETIME);

can you verify this?

Comment by Patrik Votoček [ 20/Jul/10 ]
$qb->setParameter(1, new \DateTime('2012-12-21 23:59:59'), \Doctrine\DBAL\Types\Type::DATETIME);

No this featrue not work for me. (Same error message) I'm tested at latest BETA release & latest code from master branch (GitHub).

Comment by Benjamin Eberlei [ 21/Jul/10 ]

Fixed, QueryBuilder::setParameter() and QueryBuilder::setParameters() did not yet support setting parameter types.





[DDC-710] DecimalType - NULL in database should be NULL not 0 Created: 21/Jul/10  Updated: 21/Jul/10  Resolved: 21/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA3
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Patrick Schwisow Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

DecimalType::convertToPHPValue should return NULL if the value in the database is NULL. This is similar to the issue with IntegerType that was fixed with #DDC-571.

The fix would be to replace

return (double) $value;

with

return is_null($value) ? null : (double) $value;



 Comments   
Comment by Benjamin Eberlei [ 21/Jul/10 ]

Fixed





[DDC-693] NULL values from Postgres boolean columns are loaded as FALSE instead of NULL Created: 18/Jul/10  Updated: 21/Jul/10  Resolved: 21/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Jan Tichý Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

PostgreSQL 8.4.3


Attachments: Zip Archive NullBoolean.zip    

 Description   

I have column in PostgreSQL defined as "BOOLEAN NULL". If I store NULL value to database and then load it back, I get FALSE instead of NULL.

Example entity and test attached.

I am not sure if this issue applies to ORM or DBAL, so posting here.



 Comments   
Comment by Benjamin Eberlei [ 21/Jul/10 ]

Fixed





[DDC-706] DriverChain::isTransient should return false not throw exception Created: 20/Jul/10  Updated: 21/Jul/10  Resolved: 21/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: Mapping Drivers
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Benjamin Eberlei Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

The isTransient() method has to return false, not throw an exception if the given class is not an entity or mapped superclass. The current behaviour violates the Mapping\Driver contract.



 Comments   
Comment by Benjamin Eberlei [ 21/Jul/10 ]

Fixed in master





[DDC-620] Add support to multiple FROM clauses Created: 31/May/10  Updated: 20/Jul/10  Resolved: 20/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: New Feature Priority: Major
Reporter: Guilherme Blanco Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None

Issue Links:
Duplicate
duplicates DDC-614 Multiple Entities in FROM clause thro... Closed

 Description   

We support multiple FROM in EBNF, and also in Hydrators.
But it still misses the support of SqlWalker. We need to support that



 Comments   
Comment by Christian Heinrich [ 21/Jun/10 ]

I think this issue is a duplicate.

Comment by Benjamin Eberlei [ 20/Jul/10 ]

Was fixed by guilherme, see DDC-614





[DDC-614] Multiple Entities in FROM clause throws exception Created: 27/May/10  Updated: 20/Jul/10  Resolved: 20/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: DQL
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Andy Aja deh Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None
Environment:

PostgreSql 8.4


Issue Links:
Duplicate
is duplicated by DDC-620 Add support to multiple FROM clauses Resolved

 Description   

When I query,

SELECT k, d FROM OneMind\Domain\Sales\Kendaraan k, OneMind\Domain\Sales\DeliveryOrder d

it raises exception:

SQLSTATE[42P01]: Undefined table: 7 ERROR: missing FROM-clause entry for table "d1_" 
LINE 1: ...gan AS pelanggan8, k0_.keterangan AS keterangan9, d1_.id AS ... ^ 

then I try to var_dump the sql:

SELECT k0_.id AS id0, k0_.tipe_kendaraan AS tipe_kendaraan1, k0_.warna AS warna2, k0_.no_rangka AS no_rangka3,
 k0_.no_mesin AS no_mesin4, k0_.tahun AS tahun5, k0_.rrn AS rrn6, k0_.salesman AS salesman7, k0_.pelanggan AS 
pelanggan8, k0_.keterangan AS keterangan9, d1_.id AS id10, d1_.nomor AS nomor11, d1_.tanggal AS tanggal12, 
d1_.kode_supplier AS kode_supplier13, d1_.tipe_kendaraan AS tipe_kendaraan14, d1_.warna AS warna15, 
d1_.no_rangka AS no_rangka16, d1_.no_mesin AS no_mesin17, d1_.tahun AS tahun18, d1_.harga_beli AS harga_beli19, 
d1_.no_sap AS no_sap20, d1_.tgl_sap AS tgl_sap21, d1_.dpp AS dpp22, d1_.ppn_masuk AS ppn_masuk23, d1_.bunga 
AS bunga24, d1_.jatuh_tempo AS jatuh_tempo25, d1_.rrn AS rrn26 FROM kendaraan k0_

Only the first entity appears in SQL FROM clause. The second one is missing. It is likely affect JOIN as well.



 Comments   
Comment by Felix-Johannes Jendrusch [ 09/Jul/10 ]

PDOException

SQLSTATE[42P01]: Undefined table: 7 ERROR: missing FROM-clause entry for table "t2_" LINE 1: ...N terminal_file t1_ ON f0_.id = t1_.file_ref AND (t2_.id IN ... ^'

Join

                        $qb->innerJoin('f.terminals', 'cts', Expr\Join::WITH,
                                $qb->expr()->in('cts.terminalRef.id', array_map(function($value) {
                                    return (integer) $value;
                                }, (array) $value)));

DQL

SELECT f FROM [...]\File f INNER JOIN f.terminals cts WITH cts.terminalRef.id IN(5) [...]

SQL

SELECT [...] FROM file f0_ INNER JOIN terminal_file t1_ ON f0_.id = t1_.file_ref AND (t2_.id IN (5)) INNER JOIN terminal t2_ ON t1_.terminal_ref = t2_.id [...]
Comment by Guilherme Blanco [ 20/Jul/10 ]

On http://github.com/doctrine/doctrine2/commit/2c28872af820a27b36e4ff3ca28ef92ea8c1f0f3 this issue was fixed.





[DDC-119] Collection change tracking broken with NOTIFY policy Created: 06/Nov/09  Updated: 16/Jul/10  Resolved: 15/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: 2.0-ALPHA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: Roman S. Borschel Assignee: Roman S. Borschel
Resolution: Fixed Votes: 1
Labels: None


 Description   

Looks like change tracking of collections together with the NOTIFY policy doesnt work well as collection updates are detected in _computeAssociationChanges. Perhaps the collection itself should inform the UnitOfWork directly?



 Comments   
Comment by Kawsar Saiyeed [ 16/Mar/10 ]

Not sure if the issue is identical but seems at least related. Using NOTIFY change tracking with many-to-many bidirectional associations does not work. Objects added to the associations do not get persisted when calling EntityManager#flush.

Tested on r7404.

Comment by Michael Zach [ 16/Jul/10 ]

Dear Roman,

the line # 456 in UnitOfWork.php seems wrong to me:

            $isChangeTrackingNotify = isset($this->entityChangeSets[$oid]);

Shouldn't this only be set if the entity has

 @ChangeTrackingPolicy("NOTIFY") *

set in his class docBlock? The current behaviour now is to assign $changeset if changes exists, leaving the NOTIFY tracking policy out:

             $changeSet = $isChangeTrackingNotify ? $this->entityChangeSets[$oid] : array();

Because of this change, all our unit tests involving saving of entites break (basically, the whole application), which implement @postUpdate for logging purposes logging an own computed changeset.

Comment by Roman S. Borschel [ 16/Jul/10 ]

Hi,

you're right, I did that naivly because I thought the only case where an entity would already have a changeset on flush is with the NOTIFY policy. I did not think of custom use cases like yours. It is fixed now in master. My apologies. However, this still means your approach would be broken if you would use the NOTIFY policy right? That sounds like maybe there is potential to improve the approach you're currently using. If you're missing anything in the API or implementation that forbids a different approach feel free to raise some new JIRA issues so we can possibly improve the situation.

Comment by Michael Zach [ 16/Jul/10 ]

Thank you for fixing this real quick! Our current approach is to compute the changeset on @preUpdate and after a successful save write the changeset to the database in @postUpdate. This conflicted with the changes made, I will however look into it and see if it's feasible for us to implement another approach.

Once again, thanks for your reply and fix.





[DDC-13] Enhance DQL chapter Created: 15/Sep/09  Updated: 15/Jul/10  Resolved: 15/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: Documentation
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Task Priority: Major
Reporter: Roman S. Borschel Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

The first half of the chapter "DQL - Doctrine Query Language" needs more examples and detailed explanations about DQL. The explanations need to be understandable and friendly for newcomers with no Doctrine 1.x experience.



 Comments   
Comment by Roman S. Borschel [ 16/Mar/10 ]

Benjaming already made quite some progress on this chapter. More to come for BETA2.

Comment by Roman S. Borschel [ 15/Jul/10 ]

Feel free to close this if you think the DQL docs are already good enough

Comment by Benjamin Eberlei [ 15/Jul/10 ]

Cover all the important topics now, refactoring and extension could be another ticket.





[DDC-681] PATCH: UnitOfWork#lock locks by column names instead of field names Created: 10/Jul/10  Updated: 10/Jul/10  Resolved: 10/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Dennis Verspuij Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None

Issue Links:
Reference
is referenced by DDC-683 EntityManager#lock() on unitialized p... Open

 Description   

On line 1700 of UnitOfWork#lock column names are used instead of field names, I think the line should read:

array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),

A related question: when I load an instance by relation, e.g. $author = $book->getAuthor(), I cannot specify a lock type at that point, so I have to call $em->lock($author, LockType::PESSIMISTIC_WRITE) afterwards, which results in two database queries for the same record. Is it possible to do this at once, without setting a transaction isolation level?



 Comments   
Comment by Benjamin Eberlei [ 10/Jul/10 ]

Fixed the UoW issue. Thanks!

As for the second issue, there is really only one way to skip the second database call:

Use a Fetch Join DQL to get both book and author and apply the pessimistic lock to that DQL. (Locking both the book and author)

All the other way already created the Author proxy object and you won't be able to specify a locking hint on those lazy loading select statements, requiring you to do the second locking call.

Comment by Dennis Verspuij [ 10/Jul/10 ]

Hi Benjamin, thanks for the quick answer, I already thought you'd answer that. Only funny thing is that in my SQL log it seems the select query for the lock is called before the select query for populating the $author values! May be because it delays populating the values before its first use? In the lock could actually be done at once. Any ideas?

Comment by Benjamin Eberlei [ 10/Jul/10 ]

Ah ok that is a very good optimization case, let me think about how and if this is easily implementable.

I close this issue now and open up a new one





[DDC-130] Cascading and @ManyToMany associations is broken Created: 09/Nov/09  Updated: 10/Jul/10  Resolved: 10/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: Nico Kaiser Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 1
Labels: None

Issue Links:
Reference
relates to DDC-677 Allow DQL DELETE statements to work w... Open

 Description   

I have two Entities: Users and Alerts. They are associated with @ManyToMany, the assiciation table should be "user_alert".

Entities:
http://pastebin.com/m7bca724a

Sample code:
http://pastebin.com/m4e530f42

The "remove" command in the sample code deletes the user entry and alert entry, but not the user_alert entry (which was automatically created though). This leaves an orphan entry (or the DBMS will complain because of FK constraints).

INSERT INTO users (name) VALUES (?)
array(1) {
  [1]=>
  string(3) "Bob"
}
INSERT INTO alert (name) VALUES (?)
array(1) {
  [1]=>
  string(9) "Testalert"
}
INSERT INTO user_alert (userId, alertId) VALUES (?, ?)
array(2) {
  [0]=>
  int(1)
  [1]=>
  int(1)
}
DELETE FROM users WHERE id = ?
array(1) {
  [0]=>
  int(1)
}
DELETE FROM alert WHERE id = ?
array(1) {
  [0]=>
  int(1)
}

The user_alert table should be automatically updated (on creation and removal)...



 Comments   
Comment by Roman S. Borschel [ 11/Nov/09 ]

I think in most cases the entry in the association table should be deleted by the FK constraint. However I think that is currently not the default. You can force it by using onDelete="CASCADE" on the @JoinColumn definitions inside the @JoinTable. That should probably be set by default on join columns on an association table.

I'm not sure its worth supporting manual deletion of association table entries through Doctrine as all databases support proper foreign key constraints and this is the most effective way to delete these entries and also enforces the integrity on the database side.

So I'm rather voting for making onDelete="CASCADE" the default for join columns of association tables unless specified otherwise.

Comment by Roman S. Borschel [ 13/Nov/09 ]

It appears that this doesnt work in JPA(2) either, or well, it depends on the implementation provider. But cascade=REMOVE on @ManyToMany is not allowed per the spec.

This issue here is probably one of the reasons for this. Meaning there might be no easy way for the ORM to deal with entries in association tables transparently on cascade=REMOVE.

We have the following options:

1) Disallow cascade=REMOVE on ManyToManyMapping (throw a MappingException)

2) Allow it but clearly document that entries in association tables are not removed by Doctrine, so that you need to apply onDelete="CASCADE" on the @JoinColumn definitions. Are there any more caveats with this I can not think of currently?

3) Someone has an idea for transparently deleting entries in association tables on cascade=REMOVE. Solutions would need to be performant and not too cumbersome to implement.

Waiting for feedback.

Comment by Benjamin Eberlei [ 14/Nov/09 ]

Hm after thinking about it the problem is that the collection has to be either:

1. loaded into memory completely and put through to regular remove a single entity process. This would be required to mark entities for deleted that are also hydrated but attached to another many to many collection of this type.
2. selected for all their ids and there should be a many to many deletion mechanism that works based on id, however then also finds also hydrated entities based on their id.

Comment by Nico Kaiser [ 16/Nov/09 ]

Hm, I don't know if my report was unclear... What I meant was automatic update of the join table ("user_alert" in this example). When I delete e.g. a User, I don't want to have to care about updates in the "user_alert" table - this table was automatically generated (by the @JoinTable statement), so it should be automatically updated, e.g. when I delete either a User or an Alert...

Comment by Alexander Brouwer [ 19/Nov/09 ]

How about removing a relation between a user and an alert?

Meaning the user and alert both remain in the database but they are not connected anymore (think about users with multiple roles).

Comment by Roman S. Borschel [ 19/Nov/09 ]

@Alexander: Just like in normal OOP. If a user as a collection of alerts and an alert a collection of users (bidirectional many-many) and you want to remove an alert from a user you simply remove the alert from the collection and the user from the collection of the corresponding alert. This is very clean, the only problem with this is the efficiency because in order to remove the elements from the collections, the collections must be loaded. (Strictly speaking, you only need to adjust the "owning side", so only 1 collection needs to be loaded). Of course you can always use some custom SQL that directly manipulates the intermediate table. See also DDC-128. Also there is DDC-45 but I dont think thats doable.

Collections of entities always only represent the association of the entity that has the collection and the contained entities. If you remove an entity from a collection, the association is removed, not the entity itself. "The association is removed" means that either the foreign key is NULLed out (in the case of one-to-many) or that the entry in the association table is removed.

Comment by Alexander Brouwer [ 19/Nov/09 ]

@Roman: All sounds very simple. But in practice this doesn't seem to work.
I have the following:

$uRep = $em->getRepository('System_User');
$user = $uRep->findOneById(1);
unset($user->roles[0]);
$em->persist($user);
$em->flush();

Now, when I clear the manager and reload the user:

  • If the user had 1 role -> it still has one role
  • If the user had more roles -> it still has the removed role and all others are duplicated.
Comment by Roman S. Borschel [ 19/Nov/09 ]

@Alexander: Please open a separate issue for this. Thanks.

Comment by Marc Hodgins [ 06/May/10 ]

Pastebin entries have expired and this bug is not attached to a specific version so it is difficult to tell the status. Nico, is this still a problem with the current release?

Comment by Roman S. Borschel [ 17/May/10 ]

My currently proposed solution to this would be to simply make onDelete="CASCADE" the default for join columns in join tables. Anyone has a strong objection or better idea?

Comment by Benjamin Eberlei [ 30/Jun/10 ]

sounds good to me

Comment by Benjamin Eberlei [ 09/Jul/10 ]

Other tools actually delete the related records manually, no cascading involved. Since Sqlite doesn't even support foreign keys we should probably do the same instead of relying on "onDelete"="cascade".

Whats your take roman?

Comment by Benjamin Eberlei [ 09/Jul/10 ]

We have to support this anyways for the following reason:

Doctrine 2 gives you absolutely no access to the many-to-many join table, i.e. to be working with cascade it should not only be the default, but the only option (since other options don't work). We need to extend the way "delete" works inside each persister to also clean up many-to-many tables.

We need this mechanism anyways for Element Collections to delete all the related entries.

The option onDelete="cascade|noaction" is therefore only a hint for the persisters to decide if they perform this action themselves, or let the database vendor perform it.

Comment by Benjamin Eberlei [ 09/Jul/10 ]

I pushed my proposed changes to a feature branch: http://github.com/doctrine/doctrine2/tree/DDC-130

There are definatly refactorings that need to be done, however in that state its currently doing its job very well.

Comment by Benjamin Eberlei [ 10/Jul/10 ]

What about DQL DELETE?

DELETE FROM Boo WHERE bar

For each join table:

DELETE FROM join_foo WHERE (SELECT id FROM foo WHERE bar)

Can this be done determinstically?

Comment by Benjamin Eberlei [ 10/Jul/10 ]

The DQL stuff is just way to much to add. Foreign Key constraints should fail in these cases imho

Comment by Roman S. Borschel [ 10/Jul/10 ]

I think we have to do this on bulk deletion then, too.

Examples from EclipseLink:

em.createQuery("delete from User2 u where u.id=1").executeUpdate();
=>
DELETE FROM USER2_GROUP2 WHERE EXISTS(SELECT ID FROM USER2 WHERE (ID = ?) AND ID = USER2_GROUP2.User2_ID)
DELETE FROM USER2 WHERE (ID = ?)
em.createQuery("delete from User2 u").executeUpdate();
=>
DELETE FROM USER2_GROUP2
DELETE FROM USER2
Comment by Benjamin Eberlei [ 10/Jul/10 ]

Fixed in master using the following semantics:

  • If a many-to-many join table is to be found not using onDelete="CASCADE" execute this manually
  • In self-referential relationships delete both possible pairs.
  • Use Database onDelete="CASCADE" if present on both foreign keys of the join table.

Tested against normal entities, self-referential, CTI and STI, from the owning and the inverse side.





[DDC-596] Add @DiscriminatorMap validation to orm:validate-schema Created: 17/May/10  Updated: 10/Jul/10  Resolved: 10/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Improvement Priority: Major
Reporter: Marc Hodgins Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

Assuming that all child classes of an @InheritanceType("SINGLE_TABLE") and @InheritanceType("JOINED") table should be defined in the topmost parent's @DiscriminatorMap, it would be very helpful to add this validation to the CLI orm:valdiate-schema.

At present, if a child table is defined in the topmost table @DiscriminatorMap on a SINGLE_TABLE inheritance, then orm:validate-schema states that the mapping files are correct but then a Doctrine\DBAL\Schema\SchemaException is thrown. The same exception is also thrown for other schema-tool commands (such as 'create').

Haven't tested what happens on a JOINED table..

/**
 * @Entity @InheritanceType("SINGLE_TABLE")
 * @DiscriminatorColumn(name="discr", type="string")
 * @DiscriminatorMap({"employee" = "Employee"})   // note, the second child table was accidentally omitted here
 */
class Person
{
    // ...
}

/**
 * @Entity
 */
class Employee extends Person
{
    // ...
}

/**
 * @Entity
 */
class Customer extends Person
{
    // ....
}

Schema tool commands (create, validate-schema) then throw:

  [Doctrine\DBAL\Schema\SchemaException]
  The table with name 'person' already exists.

... and orm:validate-schema actually says that the mapping is correct (which it obviously isn't) before throwing the exception:

[Mapping]  OK - The mapping files are correct.

  [Doctrine\DBAL\Schema\SchemaException]
  The table with name 'person' already exists.

Not critical since the mapping is obviously wrong here, but having this extra check in the validator could save a lot of debugging time. A duplicate table name error is confusing in this situation, and it seems wrong that the validate-schema command presently reports that the mapping files are correct when in fact the inheritance is broken.



 Comments   
Comment by Roman S. Borschel [ 17/May/10 ]

Note: Not all child classes must be specified, only those that are entities. You can have non-mapped as well as mapped-superclasses as subclasses as well.

Comment by Benjamin Eberlei [ 30/Jun/10 ]

The validation task to perform here would be:

Throw a warning if there exists and entity that extends the Parentclass, is however not part of the DiscriminatorMap

Comment by Benjamin Eberlei [ 10/Jul/10 ]

Implemented





[DDC-625] orm:schema-tool:update --dump-sql showing SQL when DB is up-to-date Created: 03/Jun/10  Updated: 09/Jul/10  Resolved: 09/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Glen Ainscow Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

With the following entities ...

<?php

/**
 * @Entity
 * @Table(name="users")
 */
class App_Model_User
{
    /**
     * @Id
     * @GeneratedValue(strategy="IDENTITY")
     * @Column(type="integer")
     */
    private $id;

    /**
     * @ManyToOne(targetEntity="App_Model_Role")
     * @JoinColumn(name="role_id", nullable=false)
     */
    private $role;

    /** @Column(type="string", columnDefinition="CHAR(64) NOT NULL") */
    private $password;
}

<?php

/**
 * @Entity
 * @Table(name="roles")
 */
class App_Model_Role
{
    /**
     * @Id
     * @GeneratedValue(strategy="IDENTITY")
     * @Column(type="smallint")
     */
    private $id;
}

I'm getting this output from orm:schema-tool:update --dump-sql, when the database is already up-to-date:

ALTER TABLE users CHANGE password password CHAR(64) NOT NULL;
ALTER TABLE users DROP FOREIGN KEY users_ibfk_1;
ALTER TABLE users ADD FOREIGN KEY (role_id) REFERENCES roles(id)


 Comments   
Comment by Christian Heinrich [ 22/Jun/10 ]

This is most probably equivalent with the following problem:

It's got to do with cli: when you DB is up to date and you run orm:schema-tool:update --dump-sql, you get something like this:

ALTER TABLE user_users CHANGE avatar avatar LONGTEXT NOT NULL <- but the column avatar is already LONGTEXT

This is caused by the following: Two schema objects are being compared. The first schema is created from the current mapping files (YAML, e.g.) and within these, this column is marked as "object". When the DB is setup, object is transferred into another type, like LONGTEXT. (Platform dependent)

The second schema is created from your current database and is marked as "TextType" because Doctrine cannot decide which other type - there are several who use LONGTEXT - would be correct

See /DBAL/Schema/Comparator.php ll. 172ff. & 293

Comparing whether both Types return the same SQL-Statement might do the job but would be really ugly.

Comment by Benjamin Eberlei [ 28/Jun/10 ]

The columnDefintion affected field can obviously not be solved by any means.

The foreign key thing i see more often in my own projects though, i have to dig deeper there.

Comment by Benjamin Eberlei [ 09/Jul/10 ]

Fixed Foreign Key issue in DBAL master, it was an error in the MySQL query to fetch the foreign keys.





[DDC-575] Ignoring some annotations Created: 07/May/10  Updated: 09/Jul/10  Resolved: 09/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Václav Novotný Assignee: Benjamin Eberlei
Resolution: Cannot Reproduce Votes: 0
Labels: None

Attachments: GZip Archive example.tar.gz    

 Description   

Some placement of handler annotation is ignored.

PrePersist is ignored:

/**
 * @PrePersist
 *
 * Will trigger error.
 */
public function error()
{
	trigger_error('ERROR');
}

PrePersist works fine:

/**
 * @PrePersist
 * @foo
 *
 * Will trigger error.
 */
public function error()
{
	trigger_error('ERROR');
}


 Comments   
Comment by Roman S. Borschel [ 01/Jul/10 ]

Is this issue still valid? Is Doctrine Common Beta3 still affected? or is it an ORM issue?

Comment by Benjamin Eberlei [ 09/Jul/10 ]

Cannot reproduce, i have added a test-case that shows exactly the described code:

http://github.com/doctrine/common/commit/3b42776ada70cf4161082c155f5d8ba9326ce674

However here the relevant annotation is detected correctly in both cases.

Closed.





[DDC-455] E_NOTICE Undefined index when setting field to a property that is not persisted Created: 21/Mar/10  Updated: 07/Jul/10  Resolved: 07/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Jaka Jancar Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None

Issue Links:
Reference
is referenced by DDC-203 Detached entities are recognized as n... Closed

 Description   

Affects trunk.

A and B have a One-To-One mapping, with A being the owning side of the relationship. No cascade persist is set.

// Create entity A
$a = new A();
$em->persist($pt);
$em->flush();

echo "Created A {$b->getId()}\n";

// Create B and add it to A
$b = new B();
$a->setB($b);
//$em->persist($b); // oops, forgot

$em->flush();

echo "Created B {$b->getId()}\n";

Expected: either throw an exception saying that A is attempting to reference an instance of B that is not persisted, or silently ignore the field.

Actual: Cryptic notice:

E_NOTICE (8): Undefined index: 0000000069d80795000000006ebfc57d (Doctrine/ORM/UnitOfWork.php:1903)



 Comments   
Comment by Guilherme Blanco [ 23/Mar/10 ]

The issue you have is the same as if you use result cache. The entity is not managed by EM.
Maybe an "Entity of class " . get_class($entity) . " not managed by EntityManager." exception is the best solution here.

Cheers,

Comment by Benjamin Eberlei [ 23/Mar/10 ]

Given that the combination:

$hash = spl_object_hash($object);
return $this->somefoo[$hash];

is probably one of the most called constructs in the complete code I tend to disagree with a check on each and everyone of them. However i to see the tendency towards errors of this kind as annoying, but maybe we can catch them earlier in those spots where they occour most often?





[DDC-203] Detached entities are recognized as new ones Created: 09/Dec/09  Updated: 06/Jul/10  Resolved: 06/Jul/10

Status: Closed
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-ALPHA3
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Giorgio Sironi Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None
Environment:

Cli tests with PHPUnit
PHP 5.3.1 (cli) (built: Nov 27 2009 10:08:49)
PHPUnit 3.4.1


Attachments: Text File ddc-203.patch     Text File ddc203-beberlei-update.patch     Text File ddc203-beberlei.patch    
Issue Links:
Reference
relates to DDC-455 E_NOTICE Undefined index when setting... Closed

 Description   

From
http://www.doctrine-project.org/documentation/manual/2_0/en/working-with-objects
on using persist() on an entity X:

  1. If X is a detached entity, an InvalidArgumentException will be thrown.

So I do this:
$picard = $this->_getUser('Picard');
$this->_em->persist($picard);
$this->_em->flush();
$this->_em->detach($picard);
$this->_em->persist($picard);
$this->_howMany("Picard", 1);
Result:
1) NakedPhp\Storage\DoctrineTest::testSavesUpdatedEntities
There are 2 instances of Picard saved instead of 1.
Failed asserting that <string:2> matches expected <integer:1>.
The entity is recognized as new instead of detached, so no error is thrown and it is duplicated in the database.



 Comments   
Comment by Giorgio Sironi [ 09/Dec/09 ]

Patch tentative. Resolved this bug but breaks another test of DetachedEntityTest, so I added another to make clear what is the issue: relying on keys to know if an entity is detached is not correct when entities have natural keys like CmsPhonenumber.
It seems that the only option would be hitting the database to see if the entity is already present.
Eliminating the error and simply persisting the entity anyway would not be cool because a duplicate will be created everytime entity is passed to persist(); even to pass it internally to merge() we have to know if it's new or detached.
A last option would be declaring: pass detached entities to persist at your own risk. It's normal for Doctrine not being aware of the difference between new and detached, if not 'detached' would not be so detached if the Orm remembers it. I have no problem in keeping new entities separated from detached ones in my data structure. Or I can simply merge() all, catching exceptions and add the entity that resulted in exceptions with persist().

Comment by Roman S. Borschel [ 09/Dec/09 ]

Yes, that exactly is the problem and I am/was aware of this but hitting the database is a no-go for that. Determining the state (UnitOfWork#getEntityState) has to be very fast. So its just a matter of finding the best compromise.

Comment by Giorgio Sironi [ 09/Dec/09 ]

We can't save any information in Doctrine objects because serialized or cached entities travel between http requests and sessions. merge() uses the same assumption that entities without identifiers defined are new and when passing a phonenumber it gives a false protection because the defined natural key does not result always in a real entity in the database.
Substituting a proxy subclass which adds a $_state private property is definitely an overkill. I suggest removing the exception and assuming that detached objects should be passed to merge() before any operation is performed on them, and only detached objects should be passed to merge(). Behavior if other operations are done previously would be undefined.
The use case for the merge()/detach() methods is serialization/unserialization of objects from a cache. A developer would then only merge objects in the access point of the cache or on saving.

Comment by Benjamin Eberlei [ 07/Feb/10 ]

My take at this issue:

1. _doPersist() assumes all entities are new. This is wrong.
2. An entity is new if it has no identifier.
3. Detached entities have identifiers.

Now the heuristics:

1. If the IdGenerator is NOT (Assigned <-> None) and we find identifiers then it is obviusly a detached Entity
2. If we dont find identifiers the entity is obviusly is new.

Problematic case: IdGenerator is Assigned and we find identifiers: Here i assume the entity is new, by definition of an entity persist() leads to a duplicate key error and catches this error.

I think this is the best approach.

Comment by Benjamin Eberlei [ 07/Feb/10 ]

Updated version that introduces the "EntityExistsException"

Comment by Roman S. Borschel [ 16/Feb/10 ]

The problem I have with this is that by doing this, every single entity passed to persist() gets its identifier read out using reflection just to determine whether its really new.

I would rather change the contract and the documentation to clearly indicate that detached instances must not be passed to persist().

Comment by Giorgio Sironi [ 16/Feb/10 ]

There are no problems in a stricter contract for persist() if detached instances are kept separate from new ones in applications, which is usually true since they come from a cache or a store. I would go with the change as it's the simplest reliable solution.

Comment by Roman S. Borschel [ 06/May/10 ]

I updated the documentation to be more clear about this issue: http://www.doctrine-project.org/documentation/manual/2_0/en/working-with-objects:persisting-entities

What do you think?

The problem is, we can not do it reliably for all cases. Yes, we could look into the identifier property if the identifier is generated and conclude from that whether it is detached or not but that does not work for manually assigned identifiers. For these we would need to hit the database and I just don't think we can do that.

So right now changing the persist contract as I did in the documentation, explicitly stating that detached entities should not be passed to persist() and that the behavior is undefined if done so, is the "best" thing.

Tell me what you think. The alternative would be to do the above (peek into identifier field of generated identifiers or look the identifier up in the db if it is assigned manually). I'm pretty sure the JPA implementations do just that but I dont know for sure and I dont think we want that.

I am still undecided though Something inside me prefers to go the other route, even if it means some additional db hits for manually assigned identifiers. Please make your vote.

Comment by Giorgio Sironi [ 06/May/10 ]

If an identifier is a natural key, and not generated, a detached object of that class and a new one are really indistinguishable if they have the same values on the other columns.
I am for simplicity: the detached/merge entities are related to use cases like caches, sessions and serialization of every kind. So typically you in an application you always know when they come from such a source and can call merge() appropriately.
If calling persist() erroneously will result in throwing an exception on flush() (primary key value already present), the application developer will notice the problem.

Comment by Roman S. Borschel [ 03/Jun/10 ]

Pushing final resolution back to beta3. The problem is that this missing detection of detached objects can lead to weird errors in other cases as well. Need to think about it some more.





[DDC-669] order of fields in sql query and parameters to be bound to the dbal's statement is different Created: 05/Jul/10  Updated: 05/Jul/10  Resolved: 05/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Sergei Lissovski Assignee: Benjamin Eberlei
Resolution: Duplicate Votes: 0
Labels: None
Environment:

win7, apache2, php 5.3.2



 Description   

Hi guys,

It seems that I found a bug in the Doctrine\ORM\Persisters\BasicEntityPersister. Let me explain.

Put it simply, I have the following entity:

Foo.php
/**
 * @Entity
 */
class Foo
{
    /**
     * @Id
     * @Column(type="integer")
     * @GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @ManyToOne(targetEntity="Foo", inversedBy="children")
     * @JoinColumn(name="parent_id", referencedColumnName="id")
     */
    private $parent;
    /**
     * @OneToMany(targetEntity="Foo", mappedBy="parent")
     */
    private $children;
    /**
     * @Column(type="integer", nullable=false)
     */
    private $verticalOrder;

    // a bunch of getters/setters

As you can see, there's a mandatory field called "verticalOrder", it is not intended to be managed manually but rather with a listener attached to onFlush event. ( In that listener, after a value for the verticalOrder is set, the uow::recomputeSingleEntityChangeSet() method is invoked, as it was suggested in documentation ).

The thing is that when I persist an instance of this class and then invoke flush ( without providing a value for it, the verticalOrder ), Doctrine dies silently, with no exception being thrown. The easy fix can be done with adding this piece of source code to the BasicEntityPersister on line 218:

Dumb fix
if (!$stmt->execute()) {
    throw new \Exception('Unable to execute query: '.var_export($stmt->errorInfo(), true));
}

That was the first problem I have faced with while trying to find out why my entity is not persisted. After I have delved a bit deeper, I found out
that the problem lies in BasicEntityPersister ::_getInsertColumnList() or in BasicEntityPersister ::_prepareUpdateData(). The thing is that order of columns in the SQL query generated by the BasicEntityPersister::_getInsertColumnList() and values to be bound to the statement and after executed is different. Line 214 in BasicEntityPersister is meant here.

In my case it was reversed, instead of parent_id, a value for verticalOrder was inserted and vice versa.

Array of parameters to be bound to the Doctrine\DBAL\Statement:

Array
(
    [verticalOrder] => 1278334736 // at the moment timestamp is used rather than MAX from the table
    [parent_id] => 
)

And the query that will be executed:

INSERT INTO Foo(parent_id, verticalOrder) VALUES (?, ?)

These snippets clearly illustrate the point.

I hope I was clear in my explanations.

I will try to devote some spare time and write tests to lock this issue, but i'm not able to say at the moment when it happens.

All the best,
Sergei Lissovski



 Comments   
Comment by Benjamin Eberlei [ 05/Jul/10 ]

This issue is a duplicate of DDC-656. which was fixed this weekend. The current master has a fix for it.

Comment by Sergei Lissovski [ 05/Jul/10 ]

Okay, thank you for a quick response. Will take it into account.





[DDC-410] review all sql to ensure that it works with concurrent requests Created: 11/Mar/10  Updated: 04/Jul/10  Resolved: 04/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: 2.0-ALPHA4
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Task Priority: Minor
Reporter: Lukas Kahwe Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None

Attachments: File TableLocking.php    
Issue Links:
Reference
is referenced by DDC-178 Query Hint for LOCK mechanisms plus s... Resolved

 Description   

this is just a reminder ticket .. it is important that all SQL code in D2 takes concurrent requests into account. this means simply fetching data and then writing data back to the RDBMS needs to take into account that there could be concurrent requests that change the fetched data. this means employing necessary explicit locking or optimistic locking etc.



 Comments   
Comment by Roman S. Borschel [ 12/Mar/10 ]

I think we dont use a read - update - write approach that is supposed to be atomic anywhere. Its obvious that this is fragile. Its like the select max() + increment in memory + write record variant for generating primary keys which is obviously a bad idea (at least it would need SERIALIZABLE transaction isolation I think).

(Obviously, any object retrieval -> modification -> persistence is a read-update-write but thats expectedly not "concurrency safe". Thats what optimistic or pessimistic (not yet implemented) locking is for.)

Comment by Roman S. Borschel [ 12/Mar/10 ]

As this is a "reminder" and I dont see any issues currently, this is surely not a blocker until someone finds a serious (and valid) problem related to this.

Comment by Lukas Kahwe [ 12/Mar/10 ]

and i want to reverse that logic .. i want to ensure that things have been reviewed so that it is clear that things will work concurrently. for example using CURRENT_TIMESTAMP, even just as an option, for versionable versions is uhm a very bad idea. at least make it microtime.

also D2 needs to support LOCK TABLE and it needs to be ensured that its possible to write "behaviors" that figure out the list of tables that need to be locked at the beginning of a flush() execution, because without it you are in a world of concurrency hurt when you want to implement stuff like Sortable or NestedSet on top of D2.

Comment by Roman S. Borschel [ 12/Mar/10 ]

What is your definition of "that things work concurrently" ? The default transaction isolation for most databases (READ COMMITTED) is designed to allow concurrency for more liveness. This implies that things like lost updates and other stuff are allowed to happen. If you dont want that you can simply use a SERIALIZABLE isolation level for the transactions which will give stronger consistency but less liveness/performance, right?

I'm not convinced (yet) that timestamps are a "very bad idea" for optimistic locking (thats what it is used for, there is no "versionable" behavior or whatever built into Doctrine). Yes, you can still get lost updates in rare, high concurrency situations but most of the time lost updates are prevented and if this is fine for the particular application and a timestamp is a more useable and meaningful value then I dont see anything wrong with it. We added that feature because it was in the JPA spec and every major ORM I know supports timestamps for optimistic locking and I'm relatively confident that they know what they're doing (Its still possible that we just have a broken implementation in Doctrine, I dont know that yet, I didnt have the time to look in detail at the other implementations).

As for LOCK TABLE, we will probably provide an abstraction over the specific SQL in the different platforms in the DBAL. Then maybe Doctrine will use these in some scenarios for pessimistic locking but that is stuff for the future.

I'm not sure where you're aiming at with this ticket. Yes, locking features are going to be improved. Yes, concurrent transactions can cause lost updates and other stuff unless you use a higher isolation level. But this is all not new. The transaction isolation level is already under your control. The locking features will be improved. Where is the flaw you're apparently seeing? At least I get the feeling out of your ticket and comment that you seem to be very worried about concurrency, thinking that Doctrine doesnt do enough or does it wrong. I, however, dont yet see what more we should do than to provide support for optimistic and pessimistic locking and I dont see the world of concurrency hurt you're seeing and in which way this is the fault of Doctrine. I think I am very aware of concurrency-related problems but as its often a very difficult topic, its easy to overlook something.

Comment by Lukas Kahwe [ 12/Mar/10 ]

i am aiming at being able to deliver algorithms on top of D2 that ensure the C in ACID .. for simple INSERT/UPDATE etc you can rely on transactions and native isolation levels, but for the things D2 needs to be able to do (versionable, sortable etc.) we need more than that. of course most apps do not going to have a bazillion of parallel requests .. but its still quite common for people to double submit .. or for multiple admins spotting and fixing the same data issue at the same time and if this breaks your nested set tree .. you are screwed. currently i have no reason to believe that D2 was written with sufficient brain cycles spend on these issues. if you are willing to release D2 1.0 without this being checked, then i think you are doing a horrible mistake.

D1 behaviors have tons of flaws that you have mentioned in your blog. but the biggest one never got mentioned, nor did it ever generate a response when i mailed the dev list. i fear that there is just too little attention being paid to this topic, maybe also because there isnt enough expertise in this area. but i think its mainly just not being looked at enough.

anyways, if you are certain enough that none of my concerns above are valid and that if there are any issues you can fix them without a BC break, then close the ticket.

Comment by Roman S. Borschel [ 12/Mar/10 ]

"i am aiming at being able to deliver algorithms on top of D2 that ensure the C in ACID .. for simple INSERT/UPDATE etc you can rely on transactions and native isolation levels, but for the things D2 needs to be able to do (versionable, sortable etc.) we need more than that."

I assume you mean the "application level" consistency right? Such that an ordering is not messed up with 2 items having the same position or that a nested set tree is not corrupt? I understand that but why can't these be solved with a higher isolation level for the operations that modify the order/the tree?. I would be especially interested in such examples that can not be solved by using a higher isolation level. I currently cant see how it would be possible to break consistency of a sorted association or nestedset tree when using a SERIALIZABLE isolation level for the transactions that modify the order or the tree.

I'm not trying to dismiss your critiques here, I'm taking you very seriously, but you need to get more specific than "ensure the sql works with concurrent requests". That alone doesnt tell us much. We need concrete examples of problematic concurrent scenarios and what Doctrine itself can or should do to avoid these problems.

Currently, we dont know what to look for.

Thanks for your help.

Comment by Lukas Kahwe [ 12/Mar/10 ]

the issue is that with FOR UPDATE .. and ultra paranoid isolation levels you can get locks on things that exist, but its not possible to necessarily get a LOCK on things that are just being inserted or altered to suddenly fall in your "range" or worse yet that get appended to your "range". some RDBMS do support range locks (like if I do WHERE foo BETWEEN 1 AND 100), but not all do .. and IIRC you do not really have much control over those (its simply a LOCK escalation strategy RDBMS may use to reduce the number of row level locks). this means in many situations fetching data with a FOR UPDATE even, might not be enough to ensure consistency when you do rights later on.

i have not done a code review of D2 .. actually i have spend very little time looking at any of the code, but from the questions and comments i have seen, there doesnt seem to be enough emphasis. for example i also saw code for the slug extension. which again employs the model of fetching all potential collisions and then doing an insert on the fetched data, without ensuring that between fetching and inserting there are no new collisions. stuff like this should try harder to do things in a single commit if possible, or getting the necessary locks.

locking is non trivial business and so expecting end users to get this right is a bad move imho.

but aside from any of the internal sql, i would also urge to ensure that some of the things i have been asking for (an efficient way to determine the affected tables and if they need to be locked at the start of a flush) a solid way to mark specific models or all model instances as dirty to ensure that users can either choose to reload certain dirty models at once or automatically when they are accessed again (see DDC-343) is really possible.

at the same time i acknowledge that i am just pointing at theoretical problems, without a promise to do the actual analysis and fixing. its just that this month i have promised people to spend my spare time on other stuff. next month might be more doable, i am traveling a lot .. where i can hopefully spend some time reviewing, then again traveling means that providing feedback and discussing things is more difficult.

Comment by Benjamin Eberlei [ 13/Mar/10 ]

Ok I have read some stuff, InnoDb for example locks on index ranges also locking the prev and next keys to avoid insert behind problms.

We are also planing to add support for "FOR UPDATE" and "FOR SHARE" as a query hint to all DQL queries aswell as a "Lock" Enum that allows to set this flags for transactions with the Entity Repositories (i.e ntityManager::find(), EntityRepository::findOne, findall() and such). See the linked issue DDC-178.

An automatic lock table is slightly more complex to compute but its possible using the "onFlush" event. My draft of a table locking manager is attached to the ticket.

Comment by Benjamin Eberlei [ 13/Mar/10 ]

A TableLocking Manager, usage:

$lock = new TableLocking();

$evm = new EventManager();
$evm->addListener($lock);

$em = EntityManager::create(..., $evm);

// do stuff
$em->flush();
$lock->unlockTables();

There should probably be a postCommit() event or something to clean up the mess automatically

Btw, this won't work at all with Joined Inheritance.

@lsmith: can you comment on the code?

Comment by Lukas Kahwe [ 13/Mar/10 ]

This is looking quite promising. I guess it would be wise to give each model class the choice if it wants to cause a lock or not, because especially in high concurrency scenarios locking a table can of course have some drastic performance effects. Actually if I for example have a sortable behavior and I am not messing with the sorting at all, I do not need to lock the table either. So probably the best approach would be that if a model method is called that requires locking. Each model could register an onFlush() event (theoretically it could try and be smart and only register the event when it knows it has something to do) and then given the value of the flag tell the lock class to issue a lock for the given underlying table.

BTW: as for innodb .. like i said some RDBMS have this behavior .. but its nothing we can rely on at all ..

Comment by Benjamin Eberlei [ 14/Mar/10 ]

yeah of course, this code locks all the time, however some kind of configuration to check for models that need full table locks is probably necessary, but as you can see from the code, easy to implement.

My idea would be to add a "TableLockRequired" marker interface and in the loops check if any dirty model implements this marker interface.

Comment by Jordi Boggiano [ 14/Mar/10 ]

Just as a reminder, I think if some code attempts to lock and the current RDBMS/Engine doesn't allow the requested lock, D2 should (make it an option maybe, but default true imo) throw an exception saying locking doesn't work with the current engine. Silently doing nothing is good in production to avoid pages exploding, but in development I want to know when I lock if it's not really locking in the background. And if I can know that without reading all detailed specs about whatever engine is in use that's even better.

Comment by Roman S. Borschel [ 15/Mar/10 ]

Synchronizing schedule with DDC-178. This issue is considered resolved when the improved locking support from DDC-178 has been implemented. As Benjamin demonstrated more custom (and generic, reusable) locking strategies are possible through the use of the event system.

Comment by Benjamin Eberlei [ 04/Jul/10 ]

resolved.





[DDC-178] Query Hint for LOCK mechanisms plus support in $em->find() Created: 26/Nov/09  Updated: 04/Jul/10  Resolved: 04/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: New Feature Priority: Major
Reporter: Benjamin Eberlei Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 2
Labels: None

Attachments: File ddc178_locking.diff     File ddc178_pessimistic.diff    
Issue Links:
Reference
relates to DDC-410 review all sql to ensure that it work... Resolved
Sub-Tasks:
Key
Summary
Type
Status
Assignee
DDC-590 Create Documentation for new Lock sup... Sub-task Resolved Benjamin Eberlei  
DDC-591 Support Pessimistic Locks for Entitie... Sub-task Resolved Benjamin Eberlei  

 Description   

In some scenarios it is necessary to explicitly lock rows for update in a select query. My idea would be to support it twofold:

1. Add a LockMode Class:

final abstract class Lock
{
    const NONE = 0;
    const OPTIMISTIC = 1;
    const PESSIMISTIC_READ = 2;
    const PESSIMISTIC_WRITE = 4;
}

2. Add methods to platforms that can add necessary READ/WRITE Lock query additions like FOR UPDATE/SHARED which afaik are supported by all rmdbs in some way.
3. Add a query hint "lockMode" which takes a Lock constant.
4. Add a query hint "lockVersion" which takes an integer or timestamp versioning value.
5. Change DQL Parser to apply lock mode if set, and if lock_mode = optimistic, add a where clause for the only top-level class (more not supported? Reread Evans DDD / Aggregates)
6. Change the $em->find() method to the following signature:

public function find($class, $identifier, $lockMode=0, $lockVersion=null);

And if the values are set, set the appropriate query hints.


Updated API specification

LockModes

final abstract class LockMode
{
    const NONE = 0;
    const OPTIMISTIC = 1;
    const PESSIMISTIC_READ = 2;
    const PESSIMISTIC_WRITE = 4;
}

Constraints

  • LockMode::OPTIMISTIC requires entities to be versioned
  • LockMode::PESSIMISTIC_READ/WRITE works similarly for versioned as well as non-versioned entities. However, lock() after read only works for versioned entities.

API spec

$query->setLockMode(LockMode::OPTIMISTIC)

Effects:

  • Throw OptimisticLockException if any of the entities fetched (or fetch-joined) by the query are not versioned.
  • Otherwise proceed normally, SQL is not modified.

$query->setLockMode(LockMode::PESSIMISTIC_READ/WRITE)

Effects:

  • Throw TransactionRequiredException if there is no running transaction.
  • Modify the SQL with an appropriate locking clause (i.e. FOR UPDATE) that can be platform-specific, to acquire a pessimistic lock on all read entities.
  • Throw PessimisticLockException if lock(s) could not be obtained.

$em->find($entity, LockMode::OPTIMISTIC) (or findBy et al)

Effects:

  • Throw OptimisticLockException if entity is not versioned.
  • Otherwise proceeed normally.

$em->find($entity, LockMode::OPTIMISTIC, $version) (or findBy et al)

Effects:

  • Throw OptimisticLockException if entity is not versioned.
  • find() entity normally, no SQL modification.
  • Throw OptimisticLockException if there is a version mismatch ($version != $entity->version)
  • Otherwise proceed normally.

$em->find($entity, LockMode::PESSIMISTIC_READ/WRITE)

Effects:

  • Throw TransactionRequiredException if there is no active transaction.
  • Modify the SQL to incude an appropriate platform-specific pessimistic lock (i.e. FOR UPDATE)
  • throw PessimisticLockException If lock could not be obtained
  • Otherwise proceed normally
  • Refresh entity with lock when entity with id exists in identity map already

$em->refresh($entity, LockMode::OPTIMISTIC)

Effects:

  • Throw OptimisticLockException if entity is not versioned.
  • Otherwise proceed normally. (What about cascades here? Need to take that into account probably)

$em->refresh($entity, LockMode::PESSIMISTIC_READ/WRITE)

Effects:

  • Throw TransactionRequiredException if there is no active transaction.
  • Ensure the SQL used for refreshing is modified accordingly with the platform-specific pessimistic locking clause.
  • Throw PessimisticLockException if the lock could not be obtained.
  • Otherwise proceed normally.

$em->lock($entity, LockMode::OPTIMISTIC)

Effects:

  • Throw OptimisticLockException if entity is not versioned.
  • Otherwise do nothing (NOOP).

$em->lock($entity, LockMode::PESSIMISTIC_READ/WRITE)

Effects:

  • Throw TransactionRequiredException if there is no active transaction.
  • Throw PessimisticLockException if entity is not versioned (this is always a "lock after read")
  • Issue straight, minimal locking SQL (we probably must include the version column in the select), platform-specific. Note: Probably get the SQL from the persisters to account for different inheritance mapping strategies. The last part of the SQL, the locking clause, is taken from the platforms.
  • Throw PessimisticLockException if the lock could not be obtained.
  • Compare the newly read version with the old version. If they dont match throw PessimisticLockException (this means the entity was changed since it was read).
  • Otherwise proceed normally.


 Comments   
Comment by Guilherme Blanco [ 09/Dec/09 ]

We should be aware that PHP 5.3 now uses mysqlnd driver internally.
This means that queries like LOCK are applied via unbuffered queries, which may compromise subsequent data changes on DB table.

We need to do some testing before effectively apply any type of approach here.

Comment by Benjamin Eberlei [ 09/Dec/09 ]

What does this have to do with locking? I don't understand it Please elaborate

Comment by Benjamin Eberlei [ 09/Dec/09 ]

Attached a quick try for pessimistic lock.

Optimistic lock enforcement is much more difficult...

Comment by Benjamin Eberlei [ 09/Dec/09 ]

Update of the patch adding tests to the Query SELECT tests.

Comment by Roman S. Borschel [ 19/Dec/09 ]

Regarding Nr. 5, indeed more than one root entity is supported in DOctrine 2 when querying.

Example (assuming Customer and Employee are not related in any way):

$q = $em->createQuery("SELECT c, e FROM Customer c, Employee e WHERE c.hatsize = e.shoesize"

The result would (or should) be an array with both Customer and Employee objects.

Comment by Benjamin Eberlei [ 26/Jan/10 ]

We should probably go only for the PESSIMISTIC lock in the interface.

However it might be convenient to make optimistic locking possible via an additional parameter. Given that PHP is Stateless it might be a good pattern to do:

$id = $_POST['id'];
$lastKnownVersion = $_POST['version'];
$entity = $em->find('Yadda', $id, $lastKnownVersion);
// do stuff with $entity

$em->flush();
Comment by Benjamin Eberlei [ 13/Mar/10 ]

Hm given our discussion yesterday, we should translate the OPTIMISTIC_FORCE_INCREMENT into PHPs execution context (script per request):

final abstract class Lock
{
    const NONE = 0;
    const OPTIMISTIC_READ = 1;
    const PESSIMISTIC_READ = 2;
    const PESSIMISTIC_WRITE = 4;
}

OPTIMISTIC_READ requires the fourth parameter of EM::find() to find being a positive integer and adds a version chck for that given version. This wont work with Dql though.

EntityRepository should also be changed, this affects:

EntityRepository::find($id, Lock $lockMode = null, $lockVersion = null);
EntityRpository::findOneBy(array $criteria, Lock $lockMode = null, $lockVersion = null);

Entity Repository would translate a $lockVersion != null into a $criteria. Then we onlly need to change:

StandardEntityPersister::load($criteria, $entity, $assoc, $hints = array(), $lockMode = null)
StandardEntityPersister::_getSelectEntitiesSQL($criteria, $assoc=null, $lockMode = null)

The all queries have to be done with DQL to get pessimistic locks imho. A DQL Query with locking would look like:

$q->setHint(Query::HINT_LOCK_MODE, Lock::PESSIMISTIC_READ);
Comment by Roman S. Borschel [ 13/Mar/10 ]

I would also like to see an EntityManager#lock() method. However, after re-reading the spec and testing with Hibernate 3.5.0-CR-2, I'm not sure we really need the *_FORCE_INCREMENT variations. In Hibernate, em.lock() with OPTIMISTIC doesnt do anything (it doesnt need to, versions are compared on update anyway). Even with OPTIMISTIC_FORCE_INCREMENT nothing happens, except the normal version update when the entity changed. Not sure whether this is a bug or intended. PESSIMISTIC_FORCE_INCREMENT works as expected.

So I currently think we should have the following as a start:

final abstract class LockMode
{
    const NONE = 1; // default
    const PESSIMISTIC_READ = 2;
    const PESSIMISTIC_WRITE = 3;
}

The LockMode can then be used either in queries or with EntityManager#lock(). Moreso, these lock modes work independantly of whether the entity is versioned (@Version) or not. PESSIMISTIC_READ would acquire a shared read lock while PESSIMISTIC_WRITE would acquire an exclusive write lock.

What I'm not sure yet about are the following things:

1) Whether to add a query hint or an explicit setLockMode() method + $_lockMode property on the query. I'm tending towards the latter to give a more explicit API for locking (hints are a bit non-obvious and may be overlooked).

2) Whether we need any kind of OPTIMISTIC support in LockMode or *_FORCE_INCREMENT support. I currently think we should start without both.

Comment by Benjamin Eberlei [ 14/Mar/10 ]

In my opinion EntityManager#lock() is not necessary for PHP

I think it has a place in long running scripts, where you dont know if you have already a write lock for an entity you have retrieved from the session some time ago, however in PHP you certainly know if a script you are executing needs a read/write consistency lock or not.

This is also where optimistic force increment shines in Java, because you update the version column you are certain you have a read consistent version of your entity. In PHP the time-frame between retrievial and a call to lock() is just unnecessary small and could be solved by directly locking the entity.

This is why i think EntityManager::find needs to be able to set the version also. A long running script in php means:

1. Session A retrieves Entity with Id 1 and Version 1 and displays a form
2. Session B retrieves Entity with Id 1 and Version 1and displays a form
3. Session B retrieves Entity withId 1 and Version 1 and updates it to Version 2
4. Session A retrieves Entity with Id 1 and Version 2 and a lost update happens.

This would be the case without checking the version column. A pessimistic lock in this case is not possible, because it would be lost between point 1 and point 4. However an optimistic lock could check upon retrieval of the entity in point 4, if it is still at version 1.

Comment by Roman S. Borschel [ 14/Mar/10 ]

You dont always know at the point of reading an entity whether you want to lock it or not because these decisions can be made in different layers of the application, even during a single HTTP request. And pessimistic (online) locks are never retrieved in "long-running scripts" / "business transactions", i.e. held during user think time, that would be a concurrency nightmare! And the pessimistic locks in JPA are online locks (select ... for update), not offline locks, so there is no difference between Java and PHP when it comes to the pessimistic online locks (select ... for update).

We are really only talking about pessimistic online locks and optimistic offline locks here. (I dont even know if there is such a thing as an optimistic online lock).

All/Most of the examples on the following blog do not refer to long-running business transactions (transactions that span multiple database transactions with user-think time in between).
http://weblogs.java.net/blog/2009/07/30/jpa-20-concurrency-and-locking

Of course EntityManaher#find et al should be extended with optional lockMode and lockVersion arguments like you said but nevertheless there should still be EntityManager#lock().

Comment by Roman S. Borschel [ 14/Mar/10 ]

Let me clarify, pessimistic online locks only really make sense for the duration of a transaction since they're released at the end of a transaction. You dont want to hold such a transaction open during a long-running business transaction (user-think time / multiple requests). Thats why there is no difference in usage of such locks in Java or PHP.

Comment by Lukas Kahwe [ 14/Mar/10 ]

actually pessimistic (time limited) offline locks are also commonly used.

Comment by Roman S. Borschel [ 14/Mar/10 ]

Moreso, even if you do know you want to lock at the point of reading the entity (which may not be the case like I said above, these decisions can be made in different layers, or even in code of an extension that receives an existing object and now wants to lock it to do its work properly. Without em.lock() that would mean re-reading the whole entity), you may still not want to lock at the point of reading:

1) read entity
2) do some (potentially expensive) computations/calculations/whatever based on the state of the entity but we dont need a lock yet
3) after inspecting the outcome of 2) we decide we want to lock

(yes, locking after reading risks stale data but it locks for a shorter duration, its a compromise)

When you can only lock during reading, this would mean in such a case holding the lock unnecessarily long.

Comment by Lukas Kahwe [ 14/Mar/10 ]

Err .. what you just described is optimistic offline locking. Aka you read the version identifier, go off do your thing, commit .. possibly discover that there was a concurrent write ..

Comment by Roman S. Borschel [ 14/Mar/10 ]

@Lukas: Yea. We're really only talking about pessimistic online locks and optimistic offline locks here. I know there are pessimistic offline locks (D1 even has an implementation of that which I wrote) its not currently in the focus for D2 and can even be provided by an extension.

Comment by Benjamin Eberlei [ 14/Mar/10 ]

Ok given layering EntityManager#lock() has use-cases.

However from my experience I want to lock entities at the beginning of long running scripts, because these runs are more important to my business than those small runs from users.

Say i have an entity that at a certain point in time needs to calculate very expensive stuff, before I start the calculations i would want to make sure that these wont get busted by a user updating the entity during that run, so i would apply SELECT * FOR UPDATE before the calculations and not after them. At point 3 when you apply the lock, you only know if you have a consistent lock between 1 and 3 when you have a version field to check against, a pessimistic lock wont help there.

The pessimistic lock in EntityManager#lock() makes sense if you want to make sure that after the aquiring no changes happen, for example:

1. read entity
2. do some simple checks
3. realize after 2, we need to make expensive computations, lock() or refresh(entity, lockMode) here? (probably depends if the entity has a version column or not)
4. do expensive computations
5. flush

Comment by Roman S. Borschel [ 14/Mar/10 ]

Based on all the discussion so far I updated the main issue description with an API specification. Feel free to comment.

Comment by Roman S. Borschel [ 14/Mar/10 ]

Next on my list is working out the transaction semantics for all the cases, i.e. where to enforce that it is invoked inside an active transaction and in which cases the Optimistic/Pessimistic exceptions cause a transaction rollback (or whether there is actually a case in which they do not result in a rollback).

Comment by Roman S. Borschel [ 14/Mar/10 ]

First update for transaction requirements.

Comment by Roman S. Borschel [ 15/Mar/10 ]

Updated refresh() specification.

Comment by Benjamin Eberlei [ 03/Apr/10 ]

Add another condition for PESSIMITIC locks in combination with find()*

  • Refresh entity with lock when entity with id exists in identity map already
Comment by Benjamin Eberlei [ 03/Apr/10 ]

hm actually only EntityManager#lock($entity, $mode) has to be called.

Comment by Benjamin Eberlei [ 03/Apr/10 ]

Patch for the said functionality, its missing refresh() changes though.

Comment by Benjamin Eberlei [ 04/Apr/10 ]

I made the following observations on lock timeouts:

  • MySQL supports them for InnoDb at the Database Configuration level
  • Postgres has either NO WAIT or no timeout at all
  • Oracle has either NO WAIT, no timeout, or a specified number of miliseconds.
  • Sqlite has no locking

Unrelated but:

  • MsSql has a very weird row lock syntax, we need a new construct in AbstractPlatform for this.

Question:

Should we support something like lock timeouts via a query hint? If yes:

  • Should we only specifiy something like setHint(Query::HINT_LOCK_TIMEOUT_NOWAIT, true);
  • or like setHint(Query::HINT_LOCK_TIMEOUT, 0);

Vendors except Oracle and Postgres would simply ignore this option. Postgres would only support TIMEOUT = 0 as NOWAIT

Comment by Benjamin Eberlei [ 02/May/10 ]

The latest version of lock-support is available here:

http://github.com/beberlei/doctrine2/tree/lock-support

The Lock Support is now tested using Gearman Job Server allowing to have functional scenarios where waiting for lock releases is necessary, see:

http://www.whitewashing.de/blog/articles/129

Refresh() is still missing, I am not sure if this should be included in the 2.0 version (use-case is very slim).

Comment by Roman S. Borschel [ 19/May/10 ]

Great work so far. I think we can skip refresh() support for now, so post-2.0 if at all.

@"Should we support something like lock timeouts via a query hint?"

I think setHint(Query::HINT_LOCK_TIMEOUT, 0) would be good. That way we keep the possibility open for later enhancements regarding other timeout values (i.e. if features change on databases) without requiring public API changes. However, it should be clearly documented that this is a hint and not a guarantee and it should be documented which database vendors interpret the timeout in which way I think.
As a start, only having timeout = 0 => NOWAIT would be enough.

Comment by Roman S. Borschel [ 19/May/10 ]

You can reschedule the lock timeout / nowait to beta3 if you want that. I think we already have enough for beta2.

Comment by Roman S. Borschel [ 05/Jun/10 ]

Pushing outstanding work back to beta3.

Comment by Benjamin Eberlei [ 04/Jul/10 ]

Implemented, Lock Timeouts will be handled in a dedicated ticket.





Query Hint for LOCK mechanisms plus support in $em->find() (DDC-178)

[DDC-591] Support Pessimistic Locks for Entities with a JoinedSubclassPersister Created: 15/May/10  Updated: 04/Jul/10  Resolved: 04/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Sub-task Priority: Major
Reporter: Benjamin Eberlei Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

Support Pessimistic Locks for Entities with a JoinedSubclassPersister



 Comments   
Comment by Benjamin Eberlei [ 04/Jul/10 ]

Fixed.





[DDC-649] SQL Error with single table inheritance and findAll method Created: 20/Jun/10  Updated: 04/Jul/10  Resolved: 04/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA1, 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: Paul Fariello Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

Postgresql



 Description   

When selecting all objects of SecondClass and ThirdClass which both inherit from FirstClass with the following code :

$em->getRepository('FirstClass')->findAll();

Postgresql is throwing the following error :
Invalid text representation: 7 ERROR: invalid input syntax for integer: "" LINE 1: ...er_stuff3 FROM "first_class" t0 WHERE t0.type IN ('', '1', '...

I think it's because of the empty string after the IN clause.

There is the fail test case :

Classes :

class FirstClass {

  private $id;
  private $type;
}

class SecondClass extends FirstClass {
  private $otherStuff;
}

class ThirdClass extends FirstClass {
  private $otherStuff;
}

Mapping :

<?xml version="1.0" encoding="UTF-8"?>
  <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

  <entity name="FirstClass" table='"first_class"' inheritance-type="SINGLE_TABLE">
    <id name="id" type="integer" column="id">
      <generator strategy="SEQUENCE"/>
      <sequence-generator sequence-name="first_class_id_seq" allocation-size="1" initial-value="1"/>
    </id>

    <discriminator-column name="type" type="integer" field-name="type" />

    <discriminator-map>
      <discriminator-mapping value="1" class="ThirdClass" />
      <discriminator-mapping value="2" class="SecondClass" />
    </discriminator-map>
  </entity>
  </doctrine-mapping>

<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

  <entity name="SecondClass">
    <field name="otherStuff" column="other_stuff" type="string" />
  </entity>
</doctrine-mapping>

<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

  <entity name="ThirdClass">
    <field name="otherStuff" column="other_stuff" type="string" />
  </entity>
</doctrine-mapping>

The SQL :

CREATE TABLE first_class
(
  id serial NOT NULL,
  "type" integer,
  other_stuff character varying,
  CONSTRAINT first_class_pkey PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);

The fail test :

require_once 'class/FirstClass.php';
require_once 'class/SecondClass.php';
require_once 'class/ThirdClass.php';

$entities = $doctrineEntityManager->getRepository('FirstClass')->findAll();


 Comments   
Comment by Paul Fariello [ 20/Jun/10 ]

The problem seems to come from Doctrine\ORM\Persisters\SingleTablePersister::_getSelectConditionSQL()

Comment by Bryan Mills [ 22/Jun/10 ]

I am also experiencing this exact issue. The value of the empty string in the 'IN' clause would appear to be the 'discriminatorValue' field on the instance of ClassMetadataInfo that gets passed into SingleTablePersister. For me, that value is null.

As a side note, this came up when I was trying to switch all my entities from using Annotation to XML. When using Annotations, I do not experience the problem with this query, which leads me to believe the issues lies somewhere in the XML drivers.

That's about as far as I've gotten trying to debug it.

Comment by Paul Fariello [ 24/Jun/10 ]

Why do not juste change the _getSelectConditionSQL() to the following :

protected function _getSelectConditionSQL(array $criteria, $assoc = null)
    {
        $conditionSql = parent::_getSelectConditionSQL($criteria, $assoc);

        // Append discriminator condition
        if ($conditionSql) $conditionSql .= ' AND ';
        if (isset($this->_class->discriminatorValue)) {
            $values = array($this->_conn->quote($this->_class->discriminatorValue));
        }
        $discrValues = array_flip($this->_class->discriminatorMap);
        foreach ($this->_class->subClasses as $subclassName) {
            $values[] = $this->_conn->quote($discrValues[$subclassName]);
        }
        $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'
                . $this->_class->discriminatorColumn['name']
                . ' IN (' . implode(', ', $values) . ')';

        return $conditionSql;
    }
Comment by Benjamin Eberlei [ 30/Jun/10 ]

There is a fundamental flaw in your logic i believe.

The parent entity has to be part of the entity map, otherwise it cannot be used as a starting point for findAll() on the repository.

   <discriminator-map>
      <discriminator-mapping value="0" class="FirstClass" />
      <discriminator-mapping value="1" class="ThirdClass" />
      <discriminator-mapping value="2" class="SecondClass" />
    </discriminator-map>
Comment by Paul Fariello [ 30/Jun/10 ]

But what if the FirstClass is abstract ?
Then it can't be part of the entity map, isn't it ?

However I should be able to select all its children classes. Anyway I think Doctrine should allow me to do it

Comment by Benjamin Eberlei [ 01/Jul/10 ]

even if it is abstract it is part of the hierachy and can be selected in DQL, repository and find methods. Since you cannot instantiate
the abstract class in userland anyways there is never going to be a problem. It still has to be done.

Maybe we should throw an exception in the mapping drivers if this is not done properly

Comment by Paul Fariello [ 01/Jul/10 ]

Correct me if I'm wrong, but if I do such a thing then i'll have a discriminator value wich will never be used.
It seems strange to me but as long as it works

Thank you for the explanation.

Comment by Benjamin Eberlei [ 01/Jul/10 ]

add it for now, you are right its never used. We will look into a change.

Comment by Benjamin Eberlei [ 04/Jul/10 ]

Fixed in master





[DDC-656] UnitOfWork::recomputeSingleEntityChangeSet does not preserve field order Created: 25/Jun/10  Updated: 04/Jul/10  Resolved: 04/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: Andriy Savchenko Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

php 5.3.2, ubuntu 10.4



 Description   

XML

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xsi="http://www.w3.org/2001/XMLSchema-instance" schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
    <entity name="Entities\Specification" table="specification">
        <field name="name" type="string" column="name" length="255"/>
        <field name="type" type="string" column="type"/>
        <id name="specificationId" type="integer" column="specification_id">
            <generator strategy="AUTO"/>
        </id>
    </entity>
</doctrine-mapping>

Code

$spec = new \Entities\Specification();
$spec->setName('test1');
$spec->setType('type1');
$em->persist($spec);
$em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(get_class($spec)), $spec);
$data1 = $em->getUnitOfWork()->getEntityChangeset($spec);
$spec->setType('type2');
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($em->getClassMetadata(get_class($spec)), $spec);
$data2 = $em->getUnitOfWork()->getEntityChangeset($spec);
// data1 contains keys in correct order: name, type
var_dump($data1);
// data2 contains keys in reverse order: type, name
var_dump($data2);

I got this issue when was trying to change entity properties using onFlush event



 Comments   
Comment by Benjamin Eberlei [ 27/Jun/10 ]

fixed format

Comment by Benjamin Eberlei [ 27/Jun/10 ]

how is this a bug?

Comment by Andriy Savchenko [ 30/Jun/10 ]

when you call $em->flush() values are inserted switched

Comment by Benjamin Eberlei [ 01/Jul/10 ]

Updating priority to critical

Comment by Benjamin Eberlei [ 04/Jul/10 ]

fixed





[DDC-555] @ManyToMany - curious behaviour - assigned values toggle Created: 29/Apr/10  Updated: 03/Jul/10  Resolved: 03/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-ALPHA4
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Minor
Reporter: Romeo Disca Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

ubuntu x86_64

PHP Version 5.3.2-0.dotdeb.2 (/etc/apt/sources.list: deb http://php53.dotdeb.org stable all)

Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
with Xdebug v2.0.5, Copyright (c) 2002-2008, by Derick Rethans
with Suhosin v0.9.31, Copyright (c) 2007-2010, by SektionEins GmbH



 Description   

BUG:
the entries in M:N table article_categories toggle

SOLUTION:
extra flush entry at [*]

Code:

$article = $em->find('Entity\Blog\Article', 1);

printf("-- %s\n", count($article->getCategories()));

$article->getCategories()->clear();
//$em->flush(); // [*]

foreach(array(4) as $id)$article->getCategories()->add(
            $em->getReference('Entity\Blog\Category', $id)
        );

$em->flush();

===

Entities:

class Article
{
/**
     * @ManyToMany(targetEntity="Entity\Blog\Category", inversedBy="articles")
     * @JoinTable(name="article_category",
     *             joinColumns={@JoinColumn(name="fk_article",  referencedColumnName="pk")},
     *      inverseJoinColumns={@JoinColumn(name="fk_category", referencedColumnName="pk")}
     * )
     */
    private $categories;
}

class Category
{
    /**
     * @ManyToMany(targetEntity="Entity\Blog\Article", mappedBy="categories")
     */
    private $articles;
}

tried to apply cascade=

{"all"}

, but has no effect.



 Comments   
Comment by Benjamin Eberlei [ 29/Apr/10 ]

I dont understand this issue, can you please elaborate what exactly is wrong?

Comment by Romeo Disca [ 30/Apr/10 ]

To be more verbose:

I have two tables article and category.
These tables are connected via a m:n-relationship with table article_category.

You can see the entity configuration snippets within the code Entities.

To reconstruct this bug, set up your database with these 3 tables. Put in some data, but keep the table article_category empty.

Now, with an properly configured entity manager, you can execute the displayed code.

There is a line

printf("-- %s\n", count($article->getCategories()));

which shows the number of categories connected with one article.

Please adjust array(4) to use valid category primary keys.

You will notice, as a result of this script, that sequential uses will produce

-- 0
-- n
-- 0
-- n
-- 0
... 

with n the number of categories you add. it will be 1 in this example (see: array(4))

If you check the database between the runs, you will notice that the table article_categories is filled, empty, filled, empty, filled, ... corresponding to the script results.

SOLUTION: You can solve this with one extra line:

$em->flush(); // [*]

I think the described behavior is not intended.

Comment by Benjamin Eberlei [ 01/Jul/10 ]

The problem is that clear() is actually scheduling the deletion of the whole colleciton. This is a completly different operation compared to:

foreach ($col AS $obj) {
    $col->removeElement($obj);
}

If you do it this way and than re-add some of the objects, those associations will not be changed.

clear() however deletes all, then adds the new ones. again

Comment by Benjamin Eberlei [ 03/Jul/10 ]

Verified as a bug though.

Comment by Romeo Disca [ 03/Jul/10 ]

I see.

I suggest an additional method:

public function removeElements() { ... }

http://www.doctrine-project.org/jira/browse/DDC-665

Comment by Benjamin Eberlei [ 03/Jul/10 ]

Not necessary, this was actually a bug. It should be fixed in the current master.





[DDC-662] Missing default value for attribute 'AutoGenerateProxyClasses' Created: 30/Jun/10  Updated: 01/Jul/10  Resolved: 01/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Marcus Stöhr Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

When not setting the attribute 'AutoGenerateProxyClasses' and running orm:ensure-production-settings an exception is thrown:

Notice: Undefined index: autoGenerateProxyClasses in Doctrine/ORM/lib/Doctrine/ORM/Configuration.php on line 332

There should be a default value set or a notice that this attribute is mandatory.



 Comments   
Comment by Roman S. Borschel [ 30/Jun/10 ]

the code inside the method that checks the production settings should simply use the getters instead of accessing the attributes array directly. So: $this->getAutoGenerateProxyClasses() instead of $this->_attributes[....].

The default values are encapsulated in the getter methods to avoid having a large array with default values that grows with each new setting even if the setting is never used.

Comment by Benjamin Eberlei [ 01/Jul/10 ]

fixed





[DDC-501] Merging entities that contained unloaded proxy collections will delete those associations Created: 08/Apr/10  Updated: 01/Jul/10  Resolved: 01/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: Markus Wößner Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP-Version 5.3.2
PHPUnit-Version 3.4.11
PDO Driver for MySQL, client library version => 5.0.83
doctrine-trunk@7546
Kubuntu 9.10 / 32bit


Attachments: File DetachedPartiallyLoadedEntityTest.php    

 Description   

If loading an entity without its -to-many-collections, detaching and merging it back WITHOUT having touched those associations will result in two strange behaviours:

oneToMany (bidrectional, mapped by loaded entity): After merge the collection remains empty. Flushing EM and reloading entity will reveal associated entities again

manyToMany (bidirectional, mapped by targeted entity): After merge the collection remains empty. Flushing EM will physically delete associations.

  • ----------------- !! NOTE !! --------------------
  • To reproduce the manyToMany-Bug it's necessary
  • to cascade "merge" on cmUser::groups
  • -------------------------------------------------


 Comments   
Comment by Markus Wößner [ 08/Apr/10 ]

Forgot to tell trunk revision

Comment by Christian Heinrich [ 12/May/10 ]

This issue is mainly caused by the entity not being initialized before serialization. Additionally, the PersistenCollection does loose all information that is needed to regain the kept entities because the collection itself is not initialized before serialization.

I've added an initialization call here http://github.com/Shurakai/doctrine2/commit/6c185a2891111dfbd83d381bad8c5a2b16536cad#diff-0

However, I'm not sure whether this is the best solution. Any thoughts?

Comment by Roman S. Borschel [ 13/May/10 ]

@Christian: I looked at the testcase you committed there and the assumptions it makes are not correct. The original test case provided by Markus made the right assumptions, that is, after the user is unserialized it can and should not know about its groups or phonenumbers since these were not serialized.
When you serialize an entity, you serialize a partial snapshot of its state. When you unserialize it you have all the state that was loaded prior to serialization but you can not get at the rest, the object is detached from the rest of the object graph. Thats where merge() comes in, it reattaches a detached entity to a managed environment where associations can be lazy-loaded, state changes are tracked, etc.

So the problem must come later, at the point of merging.

Comment by Roman S. Borschel [ 03/Jun/10 ]

Pushing back to beta3.

Comment by Benjamin Eberlei [ 01/Jul/10 ]

Fixed.





[DDC-660] quoting not-in-values Created: 29/Jun/10  Updated: 01/Jul/10  Resolved: 01/Jul/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA3
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Dietmar Bauer Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

Win7



 Description   

There seems to be a bug in ORM/Query/expr.php method notIn. All literals should be quoted like:

    /**
     * Creates a NOT IN() expression with the given arguments.
     *
     * @param string $x Field in string format to be restricted by NOT IN() function
     * @param mixed $y Argument to be used in NOT IN() function.
     * @return Expr\Func
     */
    public function notIn($x, $y)
    {
        if (is_array($y)) {
            foreach ($y as &$literal) { 
                if ( ! ($literal instanceof Expr\Literal)) { 
                    $literal = $this->_quoteLiteral($literal);
                }
            }
        }
        return new Expr\Func($x . ' NOT IN', (array) $y);
    }


 Comments   
Comment by Dietmar Bauer [ 29/Jun/10 ]

Of course without "unknown macro" in the code!

Comment by Benjamin Eberlei [ 29/Jun/10 ]

this was fixed for the in() function already some time ago, good oversight

Comment by Dietmar Bauer [ 01/Jul/10 ]

added

{ code }

to description

Comment by Benjamin Eberlei [ 01/Jul/10 ]

fixed





[DDC-618] INDEX BY not working Created: 31/May/10  Updated: 28/Jun/10  Resolved: 28/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: DQL
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Dennis Verspuij Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 1
Labels: None

Attachments: File DDC618Test.php    

 Description   

It looks like INDEX BY does not work from DQL queries.
Haven't tested it when using query builder.



 Comments   
Comment by Dennis Verspuij [ 31/May/10 ]

Test case

Comment by Benjamin Eberlei [ 28/Jun/10 ]

INDEX BY got optimized away, the SqlWalker currently does not interpret the INDEX BY clause.

Comment by Benjamin Eberlei [ 28/Jun/10 ]

Fixed





[DDC-611] Clearing APC cache is broken Created: 24/May/10  Updated: 28/Jun/10  Resolved: 28/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Romain D. Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

When running ./doctrine orm:clear-cache with the APC cache, it simply won't work because the cache is not shared between the different PHP processes.

So, the php instance run by ./doctrine and the one run by apache are different and won't share their cache. This is an APC feature.

A workaround is needed, other than calling it via apache, or simply remove it. And write it in the documentation.



 Comments   
Comment by Benjamin Eberlei [ 28/Jun/10 ]

Fixed, throwing an exception now





[DDC-647] string length not taken into account on id while using Yaml driver Created: 20/Jun/10  Updated: 28/Jun/10  Resolved: 22/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: Mapping Drivers
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Guillaume ORIOL Assignee: Christian Heinrich
Resolution: Fixed Votes: 0
Labels: None


 Description   

The string lenght specified in the Yaml file is not respected on id columns.

Sample YAML file

---
# Entities.Stock.dcm.yml
Entities\Stock:
  type: entity
  table: stocks
  id:
    id:
      type: string
      length: 10
      generator:
        strategy: NONE
  fields:
    nature:
      type: string
      length: 10
    name:
      type: string
      length: 40
    creationDate:
      name: creation_date
      type: datetime
    owner:
      type: string
      length: 8

Command used to generate the SQL statements:
./doctrine orm:schema-tool:create --dump-sql

Result:

CREATE TABLE stocks (id VARCHAR(255) NOT NULL, nature VARCHAR(12) NOT
NULL, name VARCHAR(40) NOT NULL, creationDate DATETIME NOT NULL, owner
VARCHAR(8) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB

The "id" is generated as VARCHAR(255) instead of VARCHAR(10).



 Comments   
Comment by Benjamin Eberlei [ 20/Jun/10 ]

Fixed formating

Comment by Christian Heinrich [ 22/Jun/10 ]

Fixed: http://github.com/Shurakai/doctrine2/tree/DDC-647

Comment by Benjamin Eberlei [ 28/Jun/10 ]

Hey Christian, please don't mark bugs as fixed that have not been merged into the main repository yet.

Merged now





[DDC-616] Reverse engineering with Oracle Created: 29/May/10  Updated: 20/Jun/10  Resolved: 20/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Mickael Perraud Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

Ubuntu 10.04 + Oracle 11g Entreprise + PHP 5.3.2 + Doctrine2 Git (up-to-date)


Issue Links:
Reference
relates to DDC-627 Unexpected Duplicate Field Mapping Ex... Resolved

 Description   

I am playing with reverse engineering with Oracle and I have some problems:

My schema:

create table TABLE_TEST1 (
   TEST1_FIRST_COLUMN      NUMBER(4)                       not null,
   TEST1_SECOND_COLUMN     VARCHAR2(50)                    not null,
   TEST1_THIRD_COLUMN      DATE,
   constraint PK_TABLE_TEST1 primary key (TEST1_FIRST_COLUMN)
         using index
       tablespace TBS_INDEX
       storage
       (
           initial 100K
           next 100K
       )
)
storage
(
    initial 100K
    next 100K
)
tablespace TBS_DATA;

create table TABLE_TEST2 (
   TEST2_FIRST_COLUMN      NUMBER(4)                       not null,
   TEST2_SECOND_COLUMN     VARCHAR2(50)                    not null,
   TEST2_THIRD_COLUMN      DATE,
   TEST1_FIRST_COLUMN      NUMBER(4)                       not null,
   constraint PK_TABLE_TEST2 primary key (TEST2_FIRST_COLUMN)
         using index
       tablespace TBS_INDEX
       storage
       (
           initial 100K
           next 100K
       )
)
storage
(
    initial 100K
    next 100K
)
tablespace TBS_DATA;

alter table TABLE_TEST2
   add constraint TABLE_TEST2__TABLE_TEST1 foreign key (TEST1_FIRST_COLUMN)
      references TABLE_TEST1 (TEST1_FIRST_COLUMN);

My reverse engineering code:

ini_set('display_errors', 1);
set_include_path(realpath(__DIR__ . '/../doctrine-orm/lib/'));

require 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', realpath(__DIR__ . '/../doctrine-orm/lib/'));
$classLoader->register();

$config = new \Doctrine\ORM\Configuration;
$cache = new \Doctrine\Common\Cache\ApcCache;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver(realpath(__DIR__. '/Infofab/Entities'));
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setProxyDir('Proxies');
$config->setProxyNamespace('Infofab');
$connectionOptions = array(
    'dbname' => 'bddmkk',
    'user' => 'doctrine',
    'password' => 'xxxxxxx',
    'host' => 'localhost',
    'driver' => 'pdo_oci',
    'driverOptions' => array(PDO::ATTR_CASE => PDO::CASE_LOWER)
);

$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);

$sm = $em->getConnection()->getSchemaManager();

$em->getConfiguration()->setMetadataDriverImpl(
    new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
        $em->getConnection()->getSchemaManager()
    )
);

$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory($em);
$metadata = $cmf->getAllMetadata();

$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
$exporter = $cme->getExporter('annotation', 'Infofab');
$exporter->setMetadata($metadata);
$etg = new \Doctrine\ORM\Tools\EntityGenerator;
$exporter->setEntityGenerator($etg);
$exporter->export();

If I run this code, I obtain 2 entities:

<?php
/**
 * TableTest1
 *
 * @Table(name="TABLE_TEST1")
 * @Entity
 */
class TableTest1
{
    /**
     * @var integer $test1FirstColumn
     *
     * @Column(name="TEST1_FIRST_COLUMN", type="integer", nullable=false)
     * @Id
     * @GeneratedValue(strategy="SEQUENCE")
     * @SequenceGenerator(sequenceName="TABLE_TEST1_TEST1_FIRST_COLUMN", allocationSize="10", initialValue="1")
     */
    private $test1FirstColumn;

    /**
     * @var string $test1SecondColumn
     *
     * @Column(name="TEST1_SECOND_COLUMN", type="string", length=50, nullable=false)
     */
    private $test1SecondColumn;

    /**
     * @var datetime $test1ThirdColumn
     *
     * @Column(name="TEST1_THIRD_COLUMN", type="datetime", nullable=true)
     */
    private $test1ThirdColumn;
}

and

<?php
/**
 * TableTest2
 *
 * @Table(name="TABLE_TEST2")
 * @Entity
 */
class TableTest2
{
    /**
     * @var integer $test2FirstColumn
     *
     * @Column(name="TEST2_FIRST_COLUMN", type="integer", nullable=false)
     * @Id
     * @GeneratedValue(strategy="SEQUENCE")
     * @SequenceGenerator(sequenceName="TABLE_TEST2_TEST2_FIRST_COLUMN", allocationSize="10", initialValue="1")
     */
    private $test2FirstColumn;

    /**
     * @var integer $test1FirstColumn
     *
     * @Column(name="TEST1_FIRST_COLUMN", type="integer", nullable=false)
     */
    private $test1FirstColumn;

    /**
     * @var string $test2SecondColumn
     *
     * @Column(name="TEST2_SECOND_COLUMN", type="string", length=50, nullable=false)
     */
    private $test2SecondColumn;

    /**
     * @var datetime $test2ThirdColumn
     *
     * @Column(name="TEST2_THIRD_COLUMN", type="datetime", nullable=true)
     */
    private $test2ThirdColumn;

    /**
     * @var TABLETEST1
     *
     * @OneToOne(targetEntity="TABLETEST1")
     * @JoinColumns({
     *   @JoinColumn(name="TEST1_FIRST_COLUMN", referencedColumnName="TEST1_FIRST_COLUMN")
     * })
     */
    private $tEST1FIRSTCOLUMN;
}

As you can see, it declares 2 times the same column: private $test1FirstColumn; and private $tEST1FIRSTCOLUMN;



 Comments   
Comment by Benjamin Eberlei [ 13/Jun/10 ]

Fixed and scheduled for BETA 3

Comment by Mickael Perraud [ 13/Jun/10 ]

Something is broken with your commit:

Fatal error: Uncaught exception 'Doctrine\ORM\Mapping\MappingException' with message 'No identifier/primary key specified for Entity 'OUTILLAGE_OPERATION_COLLECTE'. Every Entity must have an identifier/primary key.' in /mkk01/doctrine/doctrine-orm/lib/Doctrine/ORM/Mapping/MappingException.php on line 37

Doctrine\ORM\Mapping\MappingException: No identifier/primary key specified for Entity 'OUTILLAGE_OPERATION_COLLECTE'. Every Entity must have an identifier/primary key. in /mkk01/doctrine/doctrine-orm/lib/Doctrine/ORM/Mapping/MappingException.php on line 37

In file 'lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php ' (http://github.com/doctrine/doctrine2/commit/b7db8df7efed4517859be562fd58e6ce4cc6354a) around line 86, the iteration is not complete and it is not able to define the primary key

Comment by Benjamin Eberlei [ 13/Jun/10 ]

It seems the detection of many to many tables can cause severe problems. Some internal refactoring has to be done to get this reverse-engineering right.

Comment by Benjamin Eberlei [ 20/Jun/10 ]

This should now be finally solved. Many-To-Many tables are detected and supported in reverse engineering now.





[DDC-646] Missing inclusion of namespace Created: 19/Jun/10  Updated: 19/Jun/10  Resolved: 19/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: Tools
Affects Version/s: 2.0-BETA2
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Blocker
Reporter: Antoine Hedgecock Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None


 Description   

ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand.php requires that you include the following namespace

Doctrine\ORM\Tools\EntityGenerator,

else the following error is thrown

PHP Warning:  require(Doctrine/ORM/Tools/Console/Command/EntityGenerator.php): failed to open stream: No such file or directory in /usr/local/zend/share/pear/Doctrine/Common/ClassLoader.php on line 148
PHP Fatal error:  require(): Failed opening required 'Doctrine/ORM/Tools/Console/Command/EntityGenerator.php' (include_path='/Users/antoine/Sites/startaeget-3.3/application/library:.:/usr/local/zend/share/ZendFramework/library:/usr/local/zend/share/pear') in /usr/local/zend/share/pear/Doctrine/Common/ClassLoader.php on line 148


 Comments   
Comment by Benjamin Eberlei [ 19/Jun/10 ]

fixed.





[DDC-613] QueryBuilder doesn't permit any functions in select() Created: 25/May/10  Updated: 16/Jun/10  Resolved: 16/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: DQL
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Critical
Reporter: David Abdemoulaie Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Example:

        $expr = $qb->expr();
        $qb->select($expr->count('e.id'));

Result:

InvalidArgumentException: Expression of type 'Doctrine\ORM\Query\Expr\Func' not allowed in this context.






[DDC-642] Conversion from annotation to xml mapping doesn't equal (missing inversedBy) Created: 16/Jun/10  Updated: 16/Jun/10  Resolved: 16/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Frantisek Troster Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Hi,

I've converted schema from annotations to XML, but after the conversion the association is missing inversedBy parameter:

public $text;
/**
 * @ManyToOne(targetEntity="CmsUser", inversedBy="articles")
 * @JoinColumn(name="user_id", referencedColumnName="id")
*/

converted XML is missing inversedBy attribute:

<many-to-one field="user" target-entity="CmsUser" orphan-removal="">
  <join-columns>
    <join-column name="user_id" referenced-column-name="id" nullable="1"/>
  </join-columns>
</many-to-one>

Thank you.






[DDC-641] Conversion from annotation to xml mapping not matching XML schema (Cascade persist) Created: 16/Jun/10  Updated: 16/Jun/10  Resolved: 16/Jun/10

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.0-BETA1
Fix Version/s: 2.0-BETA3
Security Level: All

Type: Bug Priority: Major
Reporter: Frantisek Troster Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Hi,

I'm converting schema from annotations to XML, but the result doesn't match XML schema:

/**
 * @ManyToMany(targetEntity="ECommerceProduct", cascade={"persist"})
 * @JoinTable(name="ecommerce_carts_products",
        joinColumns={@JoinColumn(name="cart_id", referencedColumnName="id")},
        inverseJoinColumns={@JoinColumn(name="product_id", referencedColumnName="id")})
*/

gets converted to:

<many-to-many field="related" target-entity="ECommerceProduct" inversed-by="par-relates">
....
          <cascade>
            <persist/>
          </cascade>
.....

but the XML schema allows only:

<xs:complexType name="cascade-type">
  <xs:sequence>
    <xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/> 
    <xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/> 
    <xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/> 
    <xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/> 
    <xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/> 
  </xs:sequence>
</xs:complexType>

Thank you.






Generated at Wed Aug 27 17:05:10 UTC 2014 using JIRA 6.2.3#6260-sha1:63ef1d6dac3f4f4d7db4c1effd405ba38ccdc558.