Compare commits

...

160 Commits
3.2.3 ... 2.2.3

Author SHA1 Message Date
Benjamin Eberlei
a65377a570 Release 2.2.3 2012-07-29 13:22:34 +02:00
Benjamin Eberlei
09d900f89f Merge branch 'DDC-1937' into 2.2 2012-07-29 11:57:37 +02:00
Benjamin Eberlei
6dfd812d3b [DDC-1937] Fix bug with apc and annotation caching using a workaround. 2012-07-29 11:57:10 +02:00
Benjamin Eberlei
14d6f364bb Fix check for ParserResult to implicit instanceof to avoid cache mixups 2012-07-29 11:40:41 +02:00
Benjamin Eberlei
0add6635c5 Merge remote-tracking branch 'origin/2.2' into 2.2 2012-07-29 09:32:20 +02:00
Benjamin Eberlei
03622158f9 Merge branch 'DDC-1939' into 2.2 2012-07-29 09:29:56 +02:00
Benjamin Eberlei
e6512209c4 [DDC-1939] Add test for persistent collection delete with composite key 2012-07-29 09:29:17 +02:00
Marco Pivetta
63e7a0a83d DDC-1939 - Removing references to non-existing AssociationMapping class 2012-07-29 09:29:17 +02:00
Benjamin Eberlei
fe6018ce9b Merge pull request #408 from benlumley/2.2
Fix typo in remove method template in entity generator
2012-07-29 00:08:19 -07:00
Ben Lumley
c84607c3aa Fix typo in remove method template in entity generator 2012-07-25 18:51:49 +01:00
Benjamin Eberlei
5d2a3bcb3b [DDC-1775] Fix NotifyPropertyChanged Listener being attached in addIdentityMap(), which is too late for certain use-cases in the persist lifecycle. 2012-07-07 17:48:00 +02:00
Benjamin Eberlei
5739c069d3 [DDC-1846] Fix EntityRepository#find() with pessimistic locking. 2012-07-07 16:49:55 +02:00
Benjamin Eberlei
e8f4c299b5 Merge branch 'DDC-1735' into 2.2 2012-07-05 22:24:59 +02:00
Benjamin Eberlei
e123931e92 [DDC-1735] Change file_put_contents with LOCK_EX usage to temporary filename + rename, which works on Windows NFS drives. 2012-07-05 22:24:36 +02:00
Bart van den Burg
98ee3180f3 fixed DDC-1895 2012-07-05 21:59:04 +02:00
Benjamin Eberlei
e1e2248dfd [DDC-1861] Fix UnitOfWork#doMerge() 2012-07-04 21:56:11 +02:00
Benjamin Eberlei
a86bfada8b [DDC-1907] Merge from master 2012-07-04 21:09:06 +02:00
Fabio B. Silva
5f66c65c9a Fix DDC-1784 2012-05-27 19:13:46 +02:00
Benjamin Eberlei
9126f751fd Merge branch 'DDC-1798' into 2.2 2012-05-27 18:46:56 +02:00
Benjamin Eberlei
627a5a43f9 [DDC-1798] Exporter generate error when composite primary key is generated. Fixes GH-342 2012-05-27 18:45:41 +02:00
Benjamin Eberlei
f60096240d Merge branch 'DDC-1777' into 2.2 2012-05-27 17:12:42 +02:00
Benjamin Eberlei
1916349f6f [DDC-1777] Fix bug in BasicEntityPersister#exists() when no primary key is set. 2012-05-27 17:11:56 +02:00
Benjamin Eberlei
cb4e14e86d Merge branch 'DDC-1783' into 2.2 2012-05-27 12:02:38 +02:00
Benjamin Eberlei
7ada288b9b [DDC-1783] Fix memory leak in ObjectHydrator when using AbstractQuery#iterate() and EntityManager#clear() 2012-05-27 12:02:08 +02:00
Benjamin Eberlei
b7bcbc20b6 Merge pull request #356 from kdambekalns/DDC-1835
[DDC-1835] Fix clone side effects in PersistentCollection
2012-05-27 01:12:38 -07:00
Benjamin Eberlei
9dbf747bd0 Merge branch 'DDC-1799' into 2.2 2012-05-27 09:59:14 +02:00
Benjamin Eberlei
dfd734010c [DDC-1799] Fix bug in YamlExporter using OneToOne instead of ManyToOne 2012-05-27 09:58:30 +02:00
Karsten Dambekalns
f2bcb19956 [DDC-1835] Fix clone side effects in PersistentCollection 2012-05-24 18:53:34 +02:00
Philipp Scheit
1f9435de5c prevent the validator to stop with an "undefined array index"-error while validating a wrong inversedBy Attribute 2012-05-22 19:09:54 +02:00
Benjamin Eberlei
a0f4bd1134 Merge remote-tracking branch 'origin/2.2' into 2.2 2012-05-22 19:08:49 +02:00
Philipp Scheit
ef79c1c9e8 don't call exit() in execute() 2012-05-22 19:06:13 +02:00
Benjamin Eberlei
36cac855dc Merge pull request #351 from stof/composer_requirement
Composer requirement
2012-05-20 14:18:49 -07:00
Christophe Coevoet
8abe8fe15b Fixed the composer requirement to allow the 2.2-dev version too 2012-05-20 22:48:09 +02:00
Benjamin Eberlei
202e675d2b Merge branch 'DDC-1786' into 2.2 2012-05-05 09:29:15 +02:00
Benjamin Eberlei
7a10e03507 [DDC-1786] Add note about BC in EntityManager#find(null) 2012-05-05 09:29:06 +02:00
Tim Nagel
159b6f8947 Cherry-pick GH-336 into 2.2 2012-04-16 19:00:31 +02:00
Benjamin Eberlei
6db528e713 Merge remote-tracking branch 'origin/2.2' into 2.2 2012-04-16 18:51:02 +02:00
Tiago Ribeiro
6babe26941 Fixes autoloading of generated Annotations 2012-04-16 18:50:24 +02:00
Benjamin Eberlei
62a72603bd Bump dev version to 2.2.3 2012-04-13 10:02:34 +02:00
Benjamin Eberlei
4e9438ba60 Release 2.2.2 2012-04-13 10:02:34 +02:00
Benjamin Eberlei
73483bf003 Bump Versions 2012-04-13 10:01:15 +02:00
Benjamin Eberlei
245c30a1a0 Merge branch 'DDC-1534' into 2.2 2012-04-07 10:43:49 +02:00
Benjamin Eberlei
15dc64f2f9 [DDC-1534] YamlDriver wrongly used "inversedBy" inside join table condition although its independent. 2012-04-07 10:43:21 +02:00
Benjamin Eberlei
66951f3c47 Merge remote-tracking branch 'origin/2.2' into 2.2 2012-04-07 10:35:59 +02:00
Benjamin Eberlei
5f21f177e9 Merge branch 'DDC-1771' into 2.2 2012-04-07 10:31:02 +02:00
Benjamin Eberlei
7af877d17e [DDC-1771] Abstract classes cannot be proxies and should be skipped in complete generation. 2012-04-07 10:30:32 +02:00
Benjamin Eberlei
f628a7f609 Merge pull request #327 from Netpositive/2.2
2.2 addDiscriminatorMapClass fix
2012-04-07 00:44:16 -07:00
Benjamin Eberlei
64c222e4d3 Merge branch 'DDC-1766' into 2.2 2012-04-07 09:12:17 +02:00
Benjamin Eberlei
13db27609c [DDC-1766] Rewrite getHydrationCacheId() to use existing processParameterValue() method. Other code style changes. 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
f039902a44 [DDC-1766] More cleanups 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
59d0c02aa9 [DDC-1766] Cleaned up code. 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
63d1d847ac [DDC-1766] Explain details of Hydration cache, introduce AbstractQuery#setResultCacheProfile method 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
ce4aacaaee [DDC-1766] Add usage of default result cache driver, add more docs. 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
dadcc7e9fc [DDC-1766] Add test with explicit cache key. 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
cc9b96e259 [DDC-1766] Remove some testcode 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
006412bad9 [DDC-1766] Rename closure 2012-04-07 09:11:18 +02:00
Benjamin Eberlei
ffbaaece93 [DDC-1766] Initial implementation of hydration cache. 2012-04-07 09:11:18 +02:00
Somfai Mátyás
c3fa29f298 Fixing a bug when calling setDiscriminatorMap from multiple sources (ie: from Events::loadClassMetadata and annotation). 2012-04-04 14:47:42 +02:00
Benjamin Eberlei
aa82a75726 Merge branch 'DDC-1705' into 2.2 2012-04-01 12:51:53 +02:00
Benjamin Eberlei
fba85d481f [DDC-1705] Fix notice 2012-04-01 12:51:35 +02:00
Benjamin Eberlei
5bdfad9e0a Merge branch 'DDC-1648' into 2.2 2012-03-14 21:40:19 +01:00
rivaros
d69a79c723 Convention fix 2012-03-14 21:39:45 +01:00
Rivaros
f51409b627 convention fixes #2 2012-03-14 21:39:45 +01:00
Rivaros
8c8deb9c9b Convention fixes 2012-03-14 21:39:45 +01:00
Rivaros
79c8f42483 Primary Keys as Foreign Keys - reverse engineering 2012-03-14 21:39:45 +01:00
Benjamin Eberlei
04ae8f2c64 Merge branch 'DDC-1692' into 2.2 2012-03-14 21:10:44 +01:00
Benjamin Eberlei
ecb495d293 [DBAL-1692] Throw exception if table has no primary key instead of fatal error. 2012-03-14 21:10:09 +01:00
Benjamin Eberlei
1418173870 Merge branch 'DDC-1683' into 2.2 2012-03-14 20:50:27 +01:00
Benjamin Eberlei
26a6b69993 [DDC-1683] Fix bug with booleans not handled by Expr#literal() in query builder. 2012-03-14 20:49:51 +01:00
Benjamin Eberlei
fa5ee57faf Merge branch 'DDC-1698' into 2.2 2012-03-14 20:05:55 +01:00
Benjamin Eberlei
2a2f010788 [DDC-1698] Add autoloader especially for the non PSR-0 Proxy class names. This is necessary when you want to deserialize your proxy classes from the session. 2012-03-14 20:03:34 +01:00
Benjamin Eberlei
e958085559 Merge branch 'DDC-1695' into 2.2 2012-03-11 23:31:33 +01:00
Benjamin Eberlei
98ba0c1ab2 [DDC-1695] Fix bug in SQL Walker array hydration with escaped fields. 2012-03-11 23:31:01 +01:00
Benjamin Eberlei
b574be5e3b Merge branch 'DDC-1693' into 2.2 2012-03-11 22:30:05 +01:00
Benjamin Eberlei
79ad9068e1 [DDC-1693] Fix fatal errors in DQL when using Optimistic or None lock modes. Added tests. 2012-03-11 22:29:38 +01:00
Benjamin Eberlei
af403d52fb Bump dev version to 2.2.2 2012-03-03 22:26:27 +01:00
Benjamin Eberlei
78fa740b94 Release 2.2.1 2012-03-03 22:26:27 +01:00
Benjamin Eberlei
715fdd7559 Merge branch 'DDC-1668' into 2.2 2012-03-03 22:25:36 +01:00
Benjamin Eberlei
884ef42075 [DDC-1668] Fix problem with the is_int fowards compatibility check. Its not really necesssary anymore, we should remove this code in the future. 2012-03-03 22:25:11 +01:00
Benjamin Eberlei
5d3a626c2b Merge branch 'DDC-1678' into 2.2 2012-03-03 22:17:07 +01:00
Francisco Facioni
587dda90d3 UnitTest for ManyToMany update notification 2012-03-03 22:16:44 +01:00
Francisco Facioni
21ae0d2a45 When using a ManyToMany relationship no listener is notified about any change to the owning entity.
What I'm doing with this patch is marking the entity for update when there is a modification in the ManyToMany relationship so the listeners are notified about it.

The main reason for this is for hooking up services like Solr or other indexers to update the entities even for ManyToMany relationships.
2012-03-03 22:16:44 +01:00
Benjamin Eberlei
f4dc533127 Bump Common to 2.2.1 2012-03-03 22:08:36 +01:00
Benjamin Eberlei
c9d1b3a428 Merge branch 'DDC-1652' into 2.2 2012-03-03 21:11:15 +01:00
Benjamin Eberlei
9170e1b810 [DDC-1652] Fix SqlWalker to include foreign key identifiers in SQL SELECT statement no matter what the meta column setting is suggesting. 2012-03-03 21:10:33 +01:00
Benjamin Eberlei
f1d2a79a87 Merge branch 'DDC-1673' into 2.2 2012-03-03 19:44:27 +01:00
Guilherme Blanco
dd2f3967cd [DDC-1673] Fixed unused in ProxyFactory. 2012-03-03 19:26:27 +01:00
Benjamin Eberlei
4100a6a7a3 Merge branch 'DDC-1667' into 2.2 2012-03-03 19:11:05 +01:00
Guilherme Blanco
bfb590459e [DDC-1667] Removed implicit obligation to define an Index and UniqueConstraint name. It is optional, but Annotations Driver was broken if not defined. 2012-03-03 19:10:06 +01:00
Sergio Moya
191520439b No unique join column fields for Single Table inheritance type. 2012-02-20 15:56:11 +01:00
Benjamin Eberlei
31709b4c1a Merge branch 'DDC-1649' into 2.2 2012-02-20 15:40:07 +01:00
Benjamin Eberlei
af6de10c5b [DDC-1649] Fix notice by last commit. 2012-02-20 15:39:39 +01:00
Benjamin Eberlei
f4a78e13ee [DDC-1649] Add additional check for not allowed mapping of dependent association keys. 2012-02-20 15:39:39 +01:00
Benjamin Eberlei
bd37b83f4f Merge branch 'DDC-1654' into 2.2 2012-02-20 10:34:27 +01:00
Benjamin Eberlei
19969e2d4b [DDC-1654] Add support for orphanRemoval on ManyToMany associations. This only makes sense when ManyToMany is used as uni-directional OneToMany association with join table. The join column has a unique constraint on it to enforce this on the DB level, but we dont validate that this actually happens. Foreign Key constraints help prevent issues and notify developers early if they use it wrong. 2012-02-20 10:33:43 +01:00
Benjamin Eberlei
573a7f81e1 Merge branch 'DDC-1659' into 2.2 2012-02-20 09:37:59 +01:00
Benjamin Eberlei
2460b3f115 [DDC-1659] Remove read only marker when clearing entities. 2012-02-20 09:37:11 +01:00
Benjamin Michotte
5f2fa7c08a Add fluent code for relations 2012-02-20 09:24:42 +01:00
Asmir Mustafic
a04df0622d nullable assoc 2012-02-20 00:32:30 +01:00
Benjamin Eberlei
b7e80decf3 Merge branch 'DDC-1651' into 2.2 2012-02-18 16:10:57 +01:00
Benjamin Eberlei
56d35f7932 [DDC-1651] Convert entities as parameters early in setParameter() to avoid them being part of result cache strings, which causes non-uniqueness. 2012-02-18 16:08:43 +01:00
Benjamin Eberlei
2116518096 Merge branch 'DDC-1643' into 2.2 2012-02-18 00:43:51 +01:00
Benjamin Eberlei
647bd2b2f2 [DDC-1643] Fix bugs when cloning PersistentCollection and re-using it. 2012-02-18 00:43:19 +01:00
Benjamin Eberlei
dadc85a650 Merge branch 'DDC-1655' into 2.2 2012-02-17 23:28:23 +01:00
Benjamin Eberlei
835943bd8d [DDC-1655][DDC-1650][DDC-1556] Fix issues with @postLoad Callback being not fired, or fired multiple times. 2012-02-17 23:27:35 +01:00
Benjamin Eberlei
39ff164c7e DDC-1641 - Fix test producing failure when skipped. 2012-02-10 21:38:42 +01:00
Benjamin Eberlei
5cdbc8ea6d Merge branch 'DDC-1634' into 2.2 2012-02-05 11:15:29 +01:00
Miha Vrhovnik
34ccde978a Proxy not initialized when parent has get<IDENTIFIER> function. Fixes DDC-1625 2012-02-05 11:14:55 +01:00
Benjamin Eberlei
5675e8cc8c Bump dev version to 2.2.1 2012-01-29 17:04:24 +01:00
Benjamin Eberlei
d679154fba Release 2.2.0 2012-01-29 17:04:23 +01:00
Benjamin Eberlei
7cf4337dc1 Bump and bind dependencies of ORM 2.2 2012-01-29 15:22:06 +01:00
Benjamin Eberlei
41cf2ce4b1 Fix test for non-mysql like datetimes. 2012-01-29 15:01:36 +01:00
Benjamin Eberlei
13b8d05def Bump dependencies to release versions. 2012-01-29 13:12:25 +01:00
Benjamin Eberlei
209d098672 Fix Oracle Tests 2012-01-29 13:09:51 +01:00
Benjamin Eberlei
53600484cd Merge branch 'DDC-1526' into 2.2 2012-01-28 12:26:54 +01:00
Benjamin Eberlei
ac25d62a24 [DDC-1526] Collections are not marked as initialized when they are fetch joined but dont contain any results. This only occurs when using LEFT JOINs on the assocations and causes another query to be fired when the empty collection is accessed again. 2012-01-28 12:26:25 +01:00
Benjamin Eberlei
f7936bda70 Merge branch 'DDC-1617' into 2.2 2012-01-28 11:17:08 +01:00
Benjamin Eberlei
78899cedac [DDC-1617] Implement support for Generating Unique Constraints/Indexes in @Table annotation of EntityGenerator. 2012-01-28 11:16:58 +01:00
Benjamin Eberlei
73fef2867f Merge remote-tracking branch 'origin/2.2' into 2.2 2012-01-25 23:29:44 +01:00
armetiz
c883ff2644 Update lib/Doctrine/ORM/Tools/SchemaTool.php 2012-01-25 22:06:37 +01:00
Benjamin Eberlei
7595025f26 Merge branch 'DDC-1619' into 2.2 2012-01-25 10:26:42 +01:00
Benjamin Eberlei
8fb6f986dd [DDC-1619] Add QueryBuilder#distinct 2012-01-25 10:25:38 +01:00
Benjamin Eberlei
6dbb368995 Merge branch 'DDC-1618' into 2.2 2012-01-25 00:02:44 +01:00
Thomas Rabaix
c39ad9250a Add SqlWalker::HINT_DISTINCT constant 2012-01-25 00:02:16 +01:00
Thomas Rabaix
8adc267d87 Fix DDC-1618 - add more check before throwing an iterateWithFetchJoinNotAllowed exception 2012-01-25 00:02:16 +01:00
Benjamin Eberlei
2a8dd72b33 Bump dev version to 2.2.0 2012-01-22 17:54:32 +01:00
Benjamin Eberlei
e44289dbdb Release 2.2.0-RC1 2012-01-22 17:54:32 +01:00
Benjamin Eberlei
dbf1c4cdb9 Bump DBAL to 2.2.0-RC3 2012-01-22 17:54:23 +01:00
Benjamin Eberlei
aa0c89e45e Fix MySqlSchemaTest failing with current DBAL adjustments. 2012-01-22 17:30:28 +01:00
Benjamin Eberlei
f77a1c2c8b Merge branch 'DDC-1613' into 2.2 2012-01-22 13:36:07 +01:00
Benjamin Eberlei
ab1835a9db [DDC-1613] Merge KnpLabs/Pagerfanta Pagination into a Doctrine\ORM\Tools\Pagination namespace. Thanks to @hobodave, pablo and the knplabs team for developing and maintaining this code. 2012-01-22 13:35:45 +01:00
Benjamin Eberlei
e5950d5fe9 Merge branch 'DDC-1603' into 2.2 2012-01-21 15:36:17 +01:00
armetiz
41f41d2bd8 Unique key name isn't correctly set - DDC-1603 2012-01-21 15:35:54 +01:00
Benjamin Eberlei
da47aa3ea2 Merge branch 'DDC-1610' into 2.2 2012-01-21 13:58:52 +01:00
Benjamin Eberlei
2f976ea03a [DDC-1610] Add test and fix wakeup reflection in combination with event listener 2012-01-21 13:58:44 +01:00
Benjamin Eberlei
e10d865b01 Merge branch 'DDC-1612' into 2.2 2012-01-21 13:07:00 +01:00
Benjamin Eberlei
d91e633eff [DDC-1612] Fix bug with EntityManager#flush($entity) on new entities. 2012-01-21 13:06:53 +01:00
Benjamin Eberlei
845d152604 Merge branch 'DBAL-204' into 2.2 2012-01-21 11:30:10 +01:00
Benjamin Eberlei
01eb894c4e [DBAL-204] Filter namespaced assets if Schemas/Emulation is not supported. 2012-01-21 11:29:48 +01:00
Benjamin Eberlei
d668d50ca4 DDC-742 - Flush Memcache, otherwise fail. 2012-01-18 21:31:49 +01:00
Benjamin Eberlei
cecfac5222 Merge branch 'DDC-1608' into 2.2 2012-01-18 20:15:26 +01:00
Guilherme Blanco
d391e28ffb Fixed DDC-1608. Non-initialized PersistentCollection methods removeElement and contains now deal correctly with managed entities. 2012-01-18 20:11:37 +01:00
Daniel Holmes
f94b405f6e Updated some comparisons to strict equality 2012-01-18 20:11:37 +01:00
Daniel Holmes
be6890e5aa Added fix for collection->contains when many-to-many extra lazy fetchMode 2012-01-18 20:11:37 +01:00
Guilherme Blanco
286eef1ca3 Fixes DDC-1596. Added table alias to discriminator column when using STI. 2012-01-16 14:09:29 +01:00
Guilherme Blanco
0aecff7f71 Added coverage to DDC-1595 and DDC-1596. 2012-01-16 14:01:57 +01:00
Guilherme Blanco
c73dae141f Fixed DDC-657. Added type conversion to scalar result. 2012-01-16 14:01:48 +01:00
Benjamin Eberlei
d535e1c2bc Merge branch 'DDC-1604' into 2.2 2012-01-16 12:52:22 +01:00
Benjamin Eberlei
f6d41ad9ce [DDC-1604] Have ORM Proxy implement new \Doctrine\Common\Persistence\Proxy
* Adjust ProxyFactory to generate proxies according to new naming schema.
* Change proxy naming and file-name generation to be a bit more consistent than previous approach.

[DDC-1598] Additional regexp to check for simple ID methods to make it even more safe.
2012-01-16 12:51:55 +01:00
Benjamin Eberlei
60de86e678 Fix 2.2 composer.json to have common and dbal versions correctly 2012-01-15 21:59:52 +01:00
jsor
a37aabb11f Pass options attribute in @Column annotation to Schema\Column's customSchemaOptions 2012-01-15 18:07:41 +01:00
Benjamin Eberlei
789ce1604f Merge branch 'DDC-1594' into 2.2 2012-01-15 17:44:53 +01:00
Benjamin Eberlei
e058b47966 DDC-1594 - Fix problem with merge and an existing managed proxy instance. 2012-01-15 17:43:00 +01:00
Benjamin Eberlei
7d77373a71 Merge branch 'DDC-1585' into 2.2 2012-01-15 14:59:48 +01:00
Benjamin Eberlei
b6896a04e5 DDC-1585 - Throw exception if setting target entity of the wrong type to an assocation. 2012-01-15 14:59:40 +01:00
Benjamin Eberlei
45fbc058a9 Merge remote-tracking branch 'origin/2.2' into 2.2 2012-01-15 12:15:17 +01:00
Benjamin Eberlei
1574d482fe Merge branch 'DDC-1601' into 2.2 2012-01-15 12:12:51 +01:00
Benjamin Eberlei
fb85cdfce0 [DDC-1601] Fix failing test and remove unused code 2012-01-15 12:12:40 +01:00
Benjamin Eberlei
0b6b87912c [DDC-1601] Fix bugs in SchemaValidator, using all modelsets as testdata for a large test 2012-01-15 12:12:40 +01:00
Benjamin Eberlei
212e1d6df6 Fix notice when using regenerate if exists and file is not new. 2012-01-12 11:21:11 +01:00
108 changed files with 4033 additions and 345 deletions

View File

@@ -73,4 +73,9 @@ Will now return a collection of arrays with index "user" pointing to the User ob
Thousands of lines were completely reviewed and optimized for best performance.
Removed redundancy and improved code readability made now internal Doctrine code easier to understand.
Also, Doctrine 2.2 now is around 10-15% faster than 2.1.
Also, Doctrine 2.2 now is around 10-15% faster than 2.1.
# EntityManager#find(null)
Previously EntityManager#find(null) returned null. It now throws an exception.

View File

@@ -92,8 +92,8 @@
<dependencies>
<php minimum_version="5.3.0" />
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
<package name="DoctrineCommon" channel="pear.doctrine-project.org" minimum_version="${dependencies.common}" />
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" />
<package name="DoctrineCommon" channel="pear.doctrine-project.org" minimum_version="${dependencies.common}" maximum_version="2.2.99" />
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" maximum_version="2.2.99" />
<package name="Console" channel="pear.symfony.com" minimum_version="2.0.0" />
<package name="Yaml" channel="pear.symfony.com" minimum_version="2.0.0" />
</dependencies>

View File

@@ -1,6 +1,6 @@
{
"name": "doctrine/orm",
"type": "library",
"type": "library","version":"2.2.3",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "http://www.doctrine-project.org",
@@ -14,8 +14,8 @@
"require": {
"php": ">=5.3.2",
"ext-pdo": "*",
"doctrine/common": "master-dev",
"doctrine/dbal": "master-dev"
"doctrine/common": "2.2.*",
"doctrine/dbal": "2.2.*"
},
"autoload": {
"psr-0": { "Doctrine\\ORM": "lib/" }

View File

@@ -350,6 +350,7 @@
<xs:attribute name="index-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>

View File

@@ -20,8 +20,9 @@
namespace Doctrine\ORM;
use Doctrine\DBAL\Types\Type,
Doctrine\DBAL\Cache\QueryCacheProfile,
Doctrine\ORM\Query\QueryException,
Doctrine\DBAL\Cache\QueryCacheProfile;
Doctrine\ORM\Internal\Hydration\CacheHydrator;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
@@ -29,7 +30,6 @@ use Doctrine\DBAL\Types\Type,
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
@@ -101,6 +101,11 @@ abstract class AbstractQuery
*/
protected $_expireResultCache = false;
/**
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_hydrationCacheProfile;
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
@@ -208,6 +213,7 @@ abstract class AbstractQuery
{
$key = trim($key, ':');
$value = $this->processParameterValue($value);
if ($type === null) {
$type = Query\ParameterTypeInferer::inferType($value);
}
@@ -218,6 +224,53 @@ abstract class AbstractQuery
return $this;
}
/**
* Process an individual parameter value
*
* @param mixed $value
* @return array
*/
private function processParameterValue($value)
{
switch (true) {
case is_array($value):
for ($i = 0, $l = count($value); $i < $l; $i++) {
$paramValue = $this->processParameterValue($value[$i]);
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
}
return $value;
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
return $this->convertObjectParameterToScalarValue($value);
default:
return $value;
}
}
protected function convertObjectParameterToScalarValue($value)
{
$class = $this->_em->getClassMetadata(get_class($value));
if ($class->isIdentifierComposite) {
throw new \InvalidArgumentException("Binding an entity with a composite primary key to a query is not supported. You should split the parameter into the explicit fields and bind them seperately.");
}
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
$values = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
} else {
$values = $class->getIdentifierValues($value);
}
$value = $values[$class->getSingleIdentifierFieldName()];
if (!$value) {
throw new \InvalidArgumentException("Binding entities to query parameters only allowed for entities that have an identifier.");
}
return $value;
}
/**
* Sets a collection of query parameters.
*
@@ -247,6 +300,68 @@ abstract class AbstractQuery
return $this;
}
/**
* Set a cache profile for hydration caching.
*
* If no result cache driver is set in the QueryCacheProfile, the default
* result cache driver is used from the configuration.
*
* Important: Hydration caching does NOT register entities in the
* UnitOfWork when retrieved from the cache. Never use result cached
* entities for requests that also flush the EntityManager. If you want
* some form of caching with UnitOfWork registration you should use
* {@see AbstractQuery::setResultCacheProfile()}.
*
* @example
* $lifetime = 100;
* $resultKey = "abc";
* $query->setHydrationCacheProfile(new QueryCacheProfile());
* $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
* @return \Doctrine\ORM\AbstractQuery
*/
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
{
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
$this->_hydrationCacheProfile = $profile;
return $this;
}
/**
* @return \Doctrine\DBAL\Cache\QueryCacheProfile
*/
public function getHydrationCacheProfile()
{
return $this->_hydrationCacheProfile;
}
/**
* Set a cache profile for the result cache.
*
* If no result cache driver is set in the QueryCacheProfile, the default
* result cache driver is used from the configuration.
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
* @return \Doctrine\ORM\AbstractQuery
*/
public function setResultCacheProfile(QueryCacheProfile $profile = null)
{
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
$this->_queryCacheProfile = $profile;
return $this;
}
/**
* Defines a cache driver to be used for caching result sets and implictly enables caching.
*
@@ -592,15 +707,68 @@ abstract class AbstractQuery
$this->setParameters($params);
}
$setCacheEntry = function() {};
if ($this->_hydrationCacheProfile !== null) {
list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
$queryCacheProfile = $this->getHydrationCacheProfile();
$cache = $queryCacheProfile->getResultCacheDriver();
$result = $cache->fetch($cacheKey);
if (isset($result[$realCacheKey])) {
return $result[$realCacheKey];
}
if ( ! $result) {
$result = array();
}
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
$result[$realCacheKey] = $data;
$cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
};
}
$stmt = $this->_doExecute();
if (is_numeric($stmt)) {
$setCacheEntry($stmt);
return $stmt;
}
return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
$setCacheEntry($data);
return $data;
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
* automatically generated for you.
*
* @return array ($key, $hash)
*/
protected function getHydrationCacheId()
{
$params = $this->getParameters();
foreach ($params AS $key => $value) {
$params[$key] = $this->processParameterValue($value);
}
$sql = $this->getSQL();
$queryCacheProfile = $this->getHydrationCacheProfile();
$hints = $this->getHints();
$hints['hydrationMode'] = $this->getHydrationMode();
ksort($hints);
return $queryCacheProfile->generateCacheKeys($sql, $params, $hints);
}
/**

View File

@@ -230,6 +230,28 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->_attributes['queryCacheImpl'] = $cacheImpl;
}
/**
* Gets the cache driver implementation that is used for the hydration cache (SQL cache).
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getHydrationCacheImpl()
{
return isset($this->_attributes['hydrationCacheImpl'])
? $this->_attributes['hydrationCacheImpl']
: null;
}
/**
* Sets the cache driver implementation that is used for the hydration cache (SQL cache).
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setHydrationCacheImpl(Cache $cacheImpl)
{
$this->_attributes['hydrationCacheImpl'] = $cacheImpl;
}
/**
* Gets the cache driver implementation that is used for metadata caching.
*

View File

@@ -124,8 +124,15 @@ class EntityRepository implements ObjectRepository
return null;
}
if ($lockMode !== LockMode::NONE) {
$this->_em->lock($entity, $lockMode, $lockVersion);
switch ($lockMode) {
case LockMode::OPTIMISTIC:
$this->_em->lock($entity, $lockMode, $lockVersion);
break;
case LockMode::PESSIMISTIC_READ:
case LockMode::PESSIMISTIC_WRITE:
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
$persister->refresh($sortedId, $entity, $lockMode);
break;
}
return $entity; // Hit!

View File

@@ -23,6 +23,7 @@ use PDO,
Doctrine\DBAL\Connection,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Events,
Doctrine\ORM\Mapping\ClassMetadata;
/**
@@ -83,6 +84,9 @@ abstract class AbstractHydrator
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$evm = $this->_em->getEventManager();
$evm->addEventListener(array(Events::onClear), $this);
$this->prepare();
return new IterableResult($this);
@@ -209,6 +213,7 @@ abstract class AbstractHydrator
case (isset($this->_rsm->scalarMappings[$key])):
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$cache[$key]['type'] = Type::getType($this->_rsm->typeMappings[$key]);
$cache[$key]['isScalar'] = true;
break;
@@ -231,6 +236,8 @@ abstract class AbstractHydrator
}
if (isset($cache[$key]['isScalar'])) {
$value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
$rowData['scalars'][$cache[$key]['fieldName']] = $value;
continue;
@@ -371,4 +378,12 @@ abstract class AbstractHydrator
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
}
/**
* When executed in a hydrate() loop we have to clear internal state to
* decrease memory consumption.
*/
public function onClear($eventArgs)
{
}
}

View File

@@ -286,4 +286,4 @@ class ArrayHydrator extends AbstractHydrator
return $this->_ce[$className];
}
}
}

View File

@@ -54,7 +54,6 @@ class ObjectHydrator extends AbstractHydrator
private $_rootAliases = array();
private $_initializedCollections = array();
private $_existingCollections = array();
//private $_createdEntities;
/** @override */
@@ -234,20 +233,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_hints['fetchAlias'] = $dqlAlias;
$entity = $this->_uow->createEntity($className, $data, $this->_hints);
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
if (isset($this->_ce[$className]->lifecycleCallbacks[Events::postLoad])) {
$this->_ce[$className]->invokeLifecycleCallbacks(Events::postLoad, $entity);
}
$evm = $this->_em->getEventManager();
if ($evm->hasListeners(Events::postLoad)) {
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
}
return $entity;
return $this->_uow->createEntity($className, $data, $this->_hints);
}
private function _getEntityFromIdentityMap($className, array $data)
@@ -359,7 +345,6 @@ class ObjectHydrator extends AbstractHydrator
continue;
}
$parentClass = $this->_ce[$this->_rsm->aliasMap[$parentAlias]];
$oid = spl_object_hash($parentObject);
$relationField = $this->_rsm->relationMap[$dqlAlias];
@@ -368,6 +353,7 @@ class ObjectHydrator extends AbstractHydrator
// Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
$reflFieldValue = $reflField->getValue($parentObject);
// PATH A: Collection-valued association
if (isset($nonemptyComponents[$dqlAlias])) {
$collKey = $oid . $relationField;
@@ -408,9 +394,12 @@ class ObjectHydrator extends AbstractHydrator
// Update result pointer
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
}
} else if ( ! $reflField->getValue($parentObject)) {
} else if ( ! $reflFieldValue) {
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
} else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
$reflFieldValue->setInitialized(true);
}
} else {
// PATH B: Single-valued association
$reflFieldValue = $reflField->getValue($parentObject);
@@ -528,4 +517,20 @@ class ObjectHydrator extends AbstractHydrator
}
}
}
/**
* When executed in a hydrate() loop we may have to clear internal state to
* decrease memory consumption.
*/
public function onClear($eventArgs)
{
parent::onClear($eventArgs);
$aliases = array_keys($this->_identifierMap);
$this->_identifierMap = array();
foreach ($aliases as $alias) {
$this->_identifierMap[$alias] = array();
}
}
}

View File

@@ -130,17 +130,6 @@ class SimpleObjectHydrator extends AbstractHydrator
$uow = $this->_em->getUnitOfWork();
$entity = $uow->createEntity($entityName, $data, $this->_hints);
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
if (isset($this->class->lifecycleCallbacks[Events::postLoad])) {
$this->class->invokeLifecycleCallbacks(Events::postLoad, $entity);
}
$evm = $this->_em->getEventManager();
if ($evm->hasListeners(Events::postLoad)) {
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
}
$result[] = $entity;
}
@@ -191,4 +180,4 @@ class SimpleObjectHydrator extends AbstractHydrator
);
}
}
}
}

View File

@@ -291,7 +291,6 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
// Invoke driver
try {
$this->driver->loadMetadataForClass($className, $class);
$this->wakeupReflection($class, $this->getReflectionService());
} catch (ReflectionException $e) {
throw MappingException::reflectionFailure($className, $e);
}
@@ -333,6 +332,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
$eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em);
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
$this->wakeupReflection($class, $this->getReflectionService());
$this->validateRuntimeMetadata($class, $parent);

View File

@@ -1105,7 +1105,7 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
}
if ( ($mapping['type'] & (self::MANY_TO_ONE|self::MANY_TO_MANY)) > 0 &&
if ( ($mapping['type'] & self::MANY_TO_ONE) > 0 &&
isset($mapping['orphanRemoval']) &&
$mapping['orphanRemoval'] == true) {
@@ -1207,9 +1207,11 @@ class ClassMetadataInfo implements ClassMetadata
$uniqueContraintColumns = array();
foreach ($mapping['joinColumns'] as $key => &$joinColumn) {
if ($mapping['type'] === self::ONE_TO_ONE) {
if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
if (count($mapping['joinColumns']) == 1) {
$joinColumn['unique'] = true;
if (! isset($mapping['id']) || ! $mapping['id']) {
$joinColumn['unique'] = true;
}
} else {
$uniqueContraintColumns[] = $joinColumn['name'];
}
@@ -1336,6 +1338,8 @@ class ClassMetadataInfo implements ClassMetadata
}
}
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
if (isset($mapping['orderBy'])) {
if ( ! is_array($mapping['orderBy'])) {
throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
@@ -1989,7 +1993,7 @@ class ClassMetadataInfo implements ClassMetadata
if ( ! class_exists($className)) {
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
}
if (is_subclass_of($className, $this->name)) {
if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
$this->subClasses[] = $className;
}
}

View File

@@ -142,9 +142,12 @@ class AnnotationDriver implements Driver
$classAnnotations = $this->_reader->getClassAnnotations($class);
// Compatibility with Doctrine Common 3.x
if ($classAnnotations && is_int(key($classAnnotations))) {
foreach ($classAnnotations as $annot) {
if ($classAnnotations) {
foreach ($classAnnotations as $key => $annot) {
if ( ! is_numeric($key)) {
continue;
}
$classAnnotations[get_class($annot)] = $annot;
}
}
@@ -176,17 +179,25 @@ class AnnotationDriver implements Driver
if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$primaryTable['indexes'][$indexAnnot->name] = array(
'columns' => $indexAnnot->columns
);
$index = array('columns' => $indexAnnot->columns);
if ( ! empty($indexAnnot->name)) {
$primaryTable['indexes'][$indexAnnot->name] = $index;
} else {
$primaryTable['indexes'][] = $index;
}
}
}
if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraint) {
$primaryTable['uniqueConstraints'][$uniqueConstraint->name] = array(
'columns' => $uniqueConstraint->columns
);
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
$uniqueConstraint = array('columns' => $uniqueConstraintAnnot->columns);
if ( ! empty($uniqueConstraintAnnot->name)) {
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
} else {
$primaryTable['uniqueConstraints'][] = $uniqueConstraint;
}
}
}
@@ -407,6 +418,7 @@ class AnnotationDriver implements Driver
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
$mapping['cascade'] = $manyToManyAnnot->cascade;
$mapping['indexBy'] = $manyToManyAnnot->indexBy;
$mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch);
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
@@ -424,9 +436,11 @@ class AnnotationDriver implements Driver
if ($method->isPublic() && $method->getDeclaringClass()->getName() == $class->name) {
$annotations = $this->_reader->getMethodAnnotations($method);
// Compatibility with Doctrine Common 3.x
if ($annotations && is_int(key($annotations))) {
foreach ($annotations as $annot) {
if ($annotations) {
foreach ($annotations as $key => $annot) {
if ( ! is_numeric($key)) {
continue;
}
$annotations[get_class($annot)] = $annot;
}
}
@@ -480,8 +494,7 @@ class AnnotationDriver implements Driver
{
$classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
// Compatibility with Doctrine Common 3.x
if ($classAnnotations && is_int(key($classAnnotations))) {
if ($classAnnotations && is_numeric(key($classAnnotations))) {
foreach ($classAnnotations as $annot) {
if ($annot instanceof \Doctrine\ORM\Mapping\Entity) {
return false;

View File

@@ -130,6 +130,13 @@ class DatabaseDriver implements Driver
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
}
if ( ! $table->hasPrimaryKey()) {
throw new MappingException(
"Table " . $table->getName() . " has no primary key. Doctrine does not ".
"support reverse engineering from tables that don't have a primary key."
);
}
$pkColumns = $table->getPrimaryKey()->getColumns();
sort($pkColumns);
sort($allForeignKeyColumns);
@@ -185,12 +192,13 @@ class DatabaseDriver implements Driver
$fieldMappings = array();
foreach ($columns as $column) {
$fieldMapping = array();
if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
$fieldMapping['id'] = true;
} else if (in_array($column->getName(), $allForeignKeyColumns)) {
if (in_array($column->getName(), $allForeignKeyColumns)) {
continue;
} else if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
$fieldMapping['id'] = true;
}
$fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false);
$fieldMapping['columnName'] = $column->getName();
$fieldMapping['type'] = strtolower((string) $column->getType());
@@ -291,13 +299,23 @@ class DatabaseDriver implements Driver
$associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
if ($primaryKeyColumns && in_array($localColumn, $primaryKeyColumns)) {
$associationMapping['id'] = true;
}
for ($i = 0; $i < count($cols); $i++) {
$associationMapping['joinColumns'][] = array(
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
);
}
$metadata->mapManyToOne($associationMapping);
//Here we need to check if $cols are the same as $primaryKeyColums
if (!array_diff($cols,$primaryKeyColumns)) {
$metadata->mapOneToOne($associationMapping);
} else {
$metadata->mapManyToOne($associationMapping);
}
}
}

View File

@@ -397,6 +397,10 @@ class XmlDriver extends AbstractFileDriver
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToManyElement['fetch']);
}
if (isset($manyToManyElement['orphan-removal'])) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphan-removal'];
}
if (isset($manyToManyElement['mapped-by'])) {
$mapping['mappedBy'] = (string)$manyToManyElement['mapped-by'];
} else if (isset($manyToManyElement->{'join-table'})) {

View File

@@ -402,9 +402,6 @@ class YamlDriver extends AbstractFileDriver
if (isset($manyToManyElement['mappedBy'])) {
$mapping['mappedBy'] = $manyToManyElement['mappedBy'];
} else if (isset($manyToManyElement['joinTable'])) {
if (isset($manyToManyElement['inversedBy'])) {
$mapping['inversedBy'] = $manyToManyElement['inversedBy'];
}
$joinTableElement = $manyToManyElement['joinTable'];
$joinTable = array(
@@ -434,6 +431,10 @@ class YamlDriver extends AbstractFileDriver
$mapping['joinTable'] = $joinTable;
}
if (isset($manyToManyElement['inversedBy'])) {
$mapping['inversedBy'] = $manyToManyElement['inversedBy'];
}
if (isset($manyToManyElement['cascade'])) {
$mapping['cascade'] = $manyToManyElement['cascade'];
}
@@ -446,6 +447,10 @@ class YamlDriver extends AbstractFileDriver
$mapping['indexBy'] = $manyToManyElement['indexBy'];
}
if (isset($manyToManyElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
}
$metadata->mapManyToMany($mapping);
}
}

View File

@@ -35,6 +35,8 @@ final class ManyToMany implements Annotation
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var boolean */
public $orphanRemoval = false;
/** @var string */
public $indexBy;
}

View File

@@ -284,7 +284,7 @@ final class PersistentCollection implements Collection
/**
* INTERNAL: Gets the association mapping of the collection.
*
* @return \Doctrine\ORM\Mapping\AssociationMapping
* @return array
*/
public function getMapping()
{
@@ -305,6 +305,7 @@ final class PersistentCollection implements Collection
if ($this->association !== null &&
$this->association['isOwningSide'] &&
$this->association['type'] === ClassMetadata::MANY_TO_MANY &&
$this->owner &&
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
$this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
}
@@ -387,7 +388,8 @@ final class PersistentCollection implements Collection
$this->changed();
if ($this->association !== null &&
$this->association['type'] == ClassMetadata::ONE_TO_MANY &&
$this->association['type'] & ClassMetadata::TO_MANY &&
$this->owner &&
$this->association['orphanRemoval']) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
}
@@ -425,7 +427,8 @@ final class PersistentCollection implements Collection
$this->changed();
if ($this->association !== null &&
$this->association['type'] === ClassMetadata::ONE_TO_MANY &&
$this->association['type'] & ClassMetadata::TO_MANY &&
$this->owner &&
$this->association['orphanRemoval']) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
}
@@ -630,7 +633,9 @@ final class PersistentCollection implements Collection
$uow = $this->em->getUnitOfWork();
if ($this->association['type'] === ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
if ($this->association['type'] & ClassMetadata::TO_MANY &&
$this->association['orphanRemoval'] &&
$this->owner) {
// we need to initialize here, as orphan removal acts like implicit cascadeRemove,
// hence for event listeners we need the objects in memory.
$this->initialize();
@@ -759,4 +764,29 @@ final class PersistentCollection implements Collection
return $this->coll->slice($offset, $length);
}
/**
* Cleanup internal state of cloned persistent collection.
*
* The following problems have to be prevented:
* 1. Added entities are added to old PC
* 2. New collection is not dirty, if reused on other entity nothing
* changes.
* 3. Snapshot leads to invalid diffs being generated.
* 4. Lazy loading grabs entities from old owner object.
* 5. New collection is connected to old owner and leads to duplicate keys.
*/
public function __clone()
{
if (is_object($this->coll)) {
$this->coll = clone $this->coll;
}
$this->initialize();
$this->owner = null;
$this->snapshot = array();
$this->changed();
}
}

View File

@@ -204,4 +204,4 @@ abstract class AbstractCollectionPersister
* @param mixed $element
*/
abstract protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element);
}
}

View File

@@ -364,7 +364,19 @@ class BasicEntityPersister
$targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']);
$where[] = $this->_class->associationMappings[$idField]['joinColumns'][0]['name'];
$params[] = $id[$idField];
$types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type'];
switch (true) {
case (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])):
$types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type'];
break;
case (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])):
$types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type'];
break;
default:
throw ORMException::unrecognizedField($targetMapping->identifier[0]);
}
} else {
$where[] = $this->_class->getQuotedColumnName($idField, $this->_platform);
$params[] = $id[$idField];
@@ -690,24 +702,14 @@ class BasicEntityPersister
* column or field names to values.
* @param object $entity The entity to refresh.
*/
public function refresh(array $id, $entity)
public function refresh(array $id, $entity, $lockMode = 0)
{
$sql = $this->_getSelectEntitiesSQL($id);
$sql = $this->_getSelectEntitiesSQL($id, null, $lockMode);
list($params, $types) = $this->expandParameters($id);
$stmt = $this->_conn->executeQuery($sql, $params, $types);
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
$hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true));
if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) {
$this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity);
}
$evm = $this->_em->getEventManager();
if ($evm->hasListeners(Events::postLoad)) {
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
}
}
/**
@@ -1153,6 +1155,7 @@ class BasicEntityPersister
$placeholder = '?';
if (isset($this->_columnTypes[$column]) &&
isset($this->_class->fieldNames[$column]) &&
isset($this->_class->fieldMappings[$this->_class->fieldNames[$column]]['requireSQLConversion'])) {
$type = Type::getType($this->_columnTypes[$column]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
@@ -1534,6 +1537,10 @@ class BasicEntityPersister
{
$criteria = $this->_class->getIdentifierValues($entity);
if ( ! $criteria) {
return false;
}
if ($extraConditions) {
$criteria = array_merge($criteria, $extraConditions);
}

View File

@@ -178,7 +178,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
// Composite identifier
$sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
$sourceClass = $this->_em->getClassMetadata(get_class($coll->getOwner()));
foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
@@ -253,8 +253,15 @@ class ManyToManyPersister extends AbstractCollectionPersister
{
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
// Shortcut for new entities
$entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW);
if ($entityState === UnitOfWork::STATE_NEW) {
return false;
}
// Entity is scheduled for inclusion
if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) {
return false;
}
@@ -275,7 +282,15 @@ class ManyToManyPersister extends AbstractCollectionPersister
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
$entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW);
if ($entityState === UnitOfWork::STATE_NEW) {
return false;
}
// If Entity is scheduled for inclusion, it is not in this collection.
// We can assure that because it would have return true before on array check
if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) {
return false;
}

View File

@@ -159,7 +159,14 @@ class OneToManyPersister extends AbstractCollectionPersister
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
$entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW);
if ($entityState === UnitOfWork::STATE_NEW) {
return false;
}
// Entity is scheduled for inclusion
if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) {
return false;
}
@@ -168,7 +175,7 @@ class OneToManyPersister extends AbstractCollectionPersister
// only works with single id identifier entities. Will throw an
// exception in Entity Persisters if that is not the case for the
// 'mappedBy' field.
$id = current( $uow->getEntityIdentifier($coll->getOwner()) );
$id = current( $uow->getEntityIdentifier($coll->getOwner()));
return $persister->exists($element, array($mapping['mappedBy'] => $id));
}
@@ -183,7 +190,15 @@ class OneToManyPersister extends AbstractCollectionPersister
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
$entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW);
if ($entityState === UnitOfWork::STATE_NEW) {
return false;
}
// If Entity is scheduled for inclusion, it is not in this collection.
// We can assure that because it would have return true before on array check
if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) {
return false;
}

View File

@@ -48,12 +48,13 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
$columnList = parent::_getSelectColumnListSQL();
// Append discriminator column
$discrColumn = $this->_class->discriminatorColumn['name'];
$columnList .= ', ' . $discrColumn;
$rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
$tableAlias = $this->_getSQLTableAlias($rootClass->name);
// Append discriminator column
$discrColumn = $this->_class->discriminatorColumn['name'];
$columnList .= ', ' . $tableAlias . '.' . $discrColumn;
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName);

View File

@@ -0,0 +1,78 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Proxy;
/**
* Special Autoloader for Proxy classes because them not being PSR-0 compatible.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class Autoloader
{
/**
* Resolve proxy class name to a filename based on the following pattern.
*
* 1. Remove Proxy namespace from class name
* 2. Remove namespace seperators from remaining class name.
* 3. Return PHP filename from proxy-dir with the result from 2.
*
* @param string $proxyDir
* @param string $proxyNamespace
* @param string $className
* @return string
*/
static public function resolveFile($proxyDir, $proxyNamespace, $className)
{
if (0 !== strpos($className, $proxyNamespace)) {
throw ProxyException::notProxyClass($className, $proxyNamespace);
}
$className = str_replace('\\', '', substr($className, strlen($proxyNamespace) +1));
return $proxyDir . DIRECTORY_SEPARATOR . $className.'.php';
}
/**
* Register and return autoloader callback for the given proxy dir and
* namespace.
*
* @param string $proxyDir
* @param string $proxyNamespace
* @param Closure $notFoundCallback Invoked when the proxy file is not found.
* @return Closure
*/
static public function register($proxyDir, $proxyNamespace, \Closure $notFoundCallback = null)
{
$proxyNamespace = ltrim($proxyNamespace, "\\");
$autoloader = function($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) {
if (0 === strpos($className, $proxyNamespace)) {
$file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className);
if ($notFoundCallback && ! file_exists($file)) {
$notFoundCallback($proxyDir, $proxyNamespace, $className);
}
require $file;
}
};
spl_autoload_register($autoloader);
return $autoloader;
}
}

View File

@@ -1,7 +1,5 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -21,10 +19,12 @@
namespace Doctrine\ORM\Proxy;
use Doctrine\Common\Persistence\Proxy as BaseProxy;
/**
* Interface for proxy classes.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
interface Proxy {}
interface Proxy extends BaseProxy {}

View File

@@ -40,4 +40,12 @@ class ProxyException extends \Doctrine\ORM\ORMException {
return new self("You must configure a proxy namespace. See docs for details");
}
}
public static function notProxyClass($className, $proxyNamespace)
{
return new self(sprintf(
"The class %s is not part of the proxy namespace %s",
$className, $proxyNamespace
));
}
}

View File

@@ -21,7 +21,7 @@ namespace Doctrine\ORM\Proxy;
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\AssociationMapping;
Doctrine\Common\Util\ClassUtils;
/**
* This factory is used to create proxy objects for entities at runtime.
@@ -41,6 +41,14 @@ class ProxyFactory
/** The directory that contains all proxy classes. */
private $_proxyDir;
/**
* Used to match very simple id methods that don't need
* to be proxied since the identifier is known.
*
* @var string
*/
const PATTERN_MATCH_ID_METHOD = '((public\s)?(function\s{1,}%s\s?\(\)\s{1,})\s{0,}{\s{0,}return\s{0,}\$this->%s;\s{0,}})i';
/**
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
* connected to the given <tt>EntityManager</tt>.
@@ -74,13 +82,12 @@ class ProxyFactory
*/
public function getProxy($className, $identifier)
{
$proxyClassName = str_replace('\\', '', $className) . 'Proxy';
$fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
$fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace);
if (! class_exists($fqn, false)) {
$fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
$fileName = $this->getProxyFileName($className);
if ($this->_autoGenerate) {
$this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
$this->_generateProxyClass($this->_em->getClassMetadata($className), $fileName, self::$_proxyClassTemplate);
}
require $fileName;
}
@@ -94,6 +101,22 @@ class ProxyFactory
return new $fqn($entityPersister, $identifier);
}
/**
* Generate the Proxy file name
*
* @param string $className
* @param string $baseDir Optional base directory for proxy file name generation.
* If not specified, the directory configured on the Configuration of the
* EntityManager will be used by this factory.
* @return string
*/
private function getProxyFileName($className, $baseDir = null)
{
$proxyDir = $baseDir ?: $this->_proxyDir;
return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
}
/**
* Generates proxy classes for all given classes.
*
@@ -101,32 +124,37 @@ class ProxyFactory
* @param string $toDir The target directory of the proxy classes. If not specified, the
* directory configured on the Configuration of the EntityManager used
* by this factory is used.
* @return int Number of generated proxies.
*/
public function generateProxyClasses(array $classes, $toDir = null)
{
$proxyDir = $toDir ?: $this->_proxyDir;
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR);
$num = 0;
foreach ($classes as $class) {
/* @var $class ClassMetadata */
if ($class->isMappedSuperclass) {
if ($class->isMappedSuperclass || $class->reflClass->isAbstract()) {
continue;
}
$proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
$proxyFileName = $proxyDir . $proxyClassName . '.php';
$this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate);
$proxyFileName = $this->getProxyFileName($class->name, $proxyDir);
$this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
$num++;
}
return $num;
}
/**
* Generates a proxy class file.
*
* @param $class
* @param $originalClassName
* @param $proxyClassName
* @param $file The path of the file to write to.
*/
private function _generateProxyClass($class, $proxyClassName, $fileName, $file)
private function _generateProxyClass($class, $fileName, $file)
{
$methods = $this->_generateMethods($class);
$sleepImpl = $this->_generateSleep($class);
@@ -138,21 +166,36 @@ class ProxyFactory
'<methods>', '<sleepImpl>', '<cloneImpl>'
);
if(substr($class->name, 0, 1) == "\\") {
$className = substr($class->name, 1);
} else {
$className = $class->name;
}
$className = ltrim($class->name, '\\');
$proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->_proxyNamespace);
$parts = explode('\\', strrev($proxyClassName), 2);
$proxyClassNamespace = strrev($parts[1]);
$proxyClassName = strrev($parts[0]);
$replacements = array(
$this->_proxyNamespace,
$proxyClassName, $className,
$methods, $sleepImpl, $cloneImpl
$proxyClassNamespace,
$proxyClassName,
$className,
$methods,
$sleepImpl,
$cloneImpl
);
$file = str_replace($placeholders, $replacements, $file);
file_put_contents($fileName, $file, LOCK_EX);
$parentDirectory = dirname($fileName);
if ( ! is_dir($parentDirectory)) {
if (false === @mkdir($parentDirectory, 0775, true)) {
throw ProxyException::proxyDirectoryNotWritable();
}
} else if ( ! is_writable($parentDirectory)) {
throw ProxyException::proxyDirectoryNotWritable();
}
$tmpFileName = $fileName . '.' . uniqid("", true);
file_put_contents($tmpFileName, $file);
rename($tmpFileName, $fileName);
}
/**
@@ -230,6 +273,14 @@ class ProxyFactory
}
/**
* Check if the method is a short identifier getter.
*
* What does this mean? For proxy objects the identifier is already known,
* however accessing the getter for this identifier usually triggers the
* lazy loading, leading to a query that may not be necessary if only the
* ID is interesting for the userland code (for example in views that
* generate links to the entity, but do not display anything else).
*
* @param ReflectionMethod $method
* @param ClassMetadata $class
* @return bool
@@ -237,7 +288,7 @@ class ProxyFactory
private function isShortIdentifierGetter($method, $class)
{
$identifier = lcfirst(substr($method->getName(), 3));
return (
$cheapCheck = (
$method->getNumberOfParameters() == 0 &&
substr($method->getName(), 0, 3) == "get" &&
in_array($identifier, $class->identifier, true) &&
@@ -245,6 +296,18 @@ class ProxyFactory
(($method->getEndLine() - $method->getStartLine()) <= 4)
&& in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string'))
);
if ($cheapCheck) {
$code = file($method->getDeclaringClass()->getFileName());
$code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1)));
$pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
if (preg_match($pattern, $code)) {
return true;
}
}
return false;
}
/**
@@ -318,6 +381,12 @@ class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
}
}
/** @private */
public function __isInitialized()
{
return $this->__isInitialized__;
}
<methods>
public function __sleep()

View File

@@ -21,6 +21,7 @@ namespace Doctrine\ORM;
use Doctrine\DBAL\LockMode,
Doctrine\ORM\Query\Parser,
Doctrine\ORM\Query\ParserResult,
Doctrine\ORM\Query\QueryException;
/**
@@ -218,7 +219,7 @@ final class Query extends AbstractQuery
$hash = $this->_getQueryCacheId();
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
if ($cached !== false) {
if ($cached instanceof ParserResult) {
// Cache hit.
$this->_parserResult = $cached;
@@ -282,7 +283,8 @@ final class Query extends AbstractQuery
}
$sqlPositions = $paramMappings[$key];
$value = array_values($this->processParameterValue($value));
// optimized multi value sql positions away for now, they are not allowed in DQL anyways.
$value = array($value);
$countValue = count($value);
for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
@@ -305,39 +307,6 @@ final class Query extends AbstractQuery
return array($sqlParams, $types);
}
/**
* Process an individual parameter value
*
* @param mixed $value
* @return array
*/
private function processParameterValue($value)
{
switch (true) {
case is_array($value):
for ($i = 0, $l = count($value); $i < $l; $i++) {
$paramValue = $this->processParameterValue($value[$i]);
// TODO: What about Entities that have composite primary key?
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
}
return array($value);
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value));
}
$class = $this->_em->getClassMetadata(get_class($value));
return array_values($class->getIdentifierValues($value));
default:
return array($value);
}
}
/**
* Defines a cache driver to be used for caching queries.
*

View File

@@ -562,6 +562,8 @@ class Expr
{
if (is_numeric($literal) && !is_string($literal)) {
return (string) $literal;
} else if (is_bool($literal)) {
return $literal ? "true" : "false";
} else {
return "'" . str_replace("'", "''", $literal) . "'";
}

View File

@@ -94,7 +94,7 @@ class QueryException extends \Doctrine\ORM\ORMException
}
/**
* @param \Doctrine\ORM\Mapping\AssociationMapping $assoc
* @param array $assoc
*/
public static function iterateWithFetchJoinCollectionNotAllowed($assoc)
{
@@ -151,4 +151,4 @@ class QueryException extends \Doctrine\ORM\ORMException
return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " .
"inheritance hierachy exists between these two classes.");
}
}
}

View File

@@ -71,6 +71,12 @@ class ResultSetMapping
*/
public $scalarMappings = array();
/**
* @ignore
* @var array Maps column names in the result set to the alias/field type to use in the mapped result.
*/
public $typeMappings = array();
/**
* @ignore
* @var array Maps entities in the result set to the alias name to use in the mapped result.
@@ -296,12 +302,16 @@ class ResultSetMapping
*
* @param string $columnName The name of the column in the SQL result set.
* @param string $alias The result alias with which the scalar result should be placed in the result structure.
* @param string $type The column type
*
* @return ResultSetMapping This ResultSetMapping instance.
*
* @todo Rename: addScalar
*/
public function addScalarResult($columnName, $alias)
public function addScalarResult($columnName, $alias, $type = null)
{
$this->scalarMappings[$columnName] = $alias;
$this->typeMappings[$columnName] = $type ?: 'string';
if ( ! $this->isMixed && $this->fieldMappings) {
$this->isMixed = true;

View File

@@ -39,6 +39,11 @@ use Doctrine\DBAL\LockMode,
*/
class SqlWalker implements TreeWalker
{
/**
* @var string
*/
const HINT_DISTINCT = 'doctrine.distinct';
/**
* @var ResultSetMapping
*/
@@ -435,13 +440,15 @@ class SqlWalker implements TreeWalker
$sql .= ' ' . $this->_platform->getWriteLockSQL();
break;
case LockMode::PESSIMISTIC_OPTIMISTIC:
case LockMode::OPTIMISTIC:
foreach ($this->_selectedClasses AS $selectedClass) {
if ( ! $class->isVersioned) {
if ( ! $selectedClass['class']->isVersioned) {
throw \Doctrine\ORM\OptimisticLockException::lockFailed($selectedClass['class']->name);
}
}
break;
case LockMode::NONE:
break;
default:
throw \Doctrine\ORM\Query\QueryException::invalidLockMode();
@@ -590,6 +597,10 @@ class SqlWalker implements TreeWalker
$sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
$sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions));
if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
$this->_query->setHint(self::HINT_DISTINCT, true);
}
$addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
$this->_query->getHydrationMode() == Query::HYDRATE_OBJECT
||
@@ -627,11 +638,17 @@ class SqlWalker implements TreeWalker
}
// Add foreign key columns to SQL, if necessary
if ( ! $addMetaColumns) continue;
if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
continue;
}
// Add foreign key columns of class and also parent classes
foreach ($class->associationMappings as $assoc) {
if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue;
if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
continue;
} else if ( !$addMetaColumns && !isset($assoc['id'])) {
continue;
}
$owningClass = (isset($assoc['inherited'])) ? $this->_em->getClassMetadata($assoc['inherited']) : $class;
$sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
@@ -645,6 +662,11 @@ class SqlWalker implements TreeWalker
}
}
// Add foreign key columns to SQL, if necessary
if ( ! $addMetaColumns) {
continue;
}
// Add foreign key columns of subclasses
foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName);
@@ -811,9 +833,10 @@ class SqlWalker implements TreeWalker
// Ensure we got the owning side, since it has all mapping info
$assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $relation['type'] & ClassMetadata::TO_MANY) {
throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->_query->getHint(self::HINT_DISTINCT) || isset($this->_selectedClasses[$joinedDqlAlias]))) {
if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
}
}
if ($joinVarDecl->indexBy) {
@@ -1080,12 +1103,14 @@ class SqlWalker implements TreeWalker
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
$columnAlias = $this->getSQLColumnAlias($columnName);
$columnAlias = $this->getSQLColumnAlias($class->fieldMappings[$fieldName]['columnName']);
$col = $sqlTableAlias . '.' . $columnName;
$fieldType = $class->getTypeOfField($fieldName);
if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
$type = Type::getType($class->getTypeOfField($fieldName));
$type = Type::getType($fieldType);
$col = $type->convertToPHPValueSQL($col, $this->_conn->getDatabasePlatform());
}
@@ -1094,7 +1119,7 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
$this->_rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
$this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias;
}
break;
@@ -1118,7 +1143,8 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
// We cannot resolve field type here; assume 'string'.
$this->_rsm->addScalarResult($columnAlias, $resultAlias, 'string');
}
break;
@@ -1131,7 +1157,8 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
// We cannot resolve field type here; assume 'string'.
$this->_rsm->addScalarResult($columnAlias, $resultAlias, 'string');
}
break;

View File

@@ -50,6 +50,7 @@ class QueryBuilder
* @var array The array of DQL parts collected.
*/
private $_dqlParts = array(
'distinct' => false,
'select' => array(),
'from' => array(),
'join' => array(),
@@ -346,7 +347,7 @@ class QueryBuilder
* ->setParameters(array(
* 'user_id1' => 1,
* 'user_id2' => 2
* ));
));
* </code>
*
* @param array $params The query parameters to set.
@@ -503,6 +504,25 @@ class QueryBuilder
return $this->add('select', new Expr\Select($selects), false);
}
/**
* Add a DISTINCT flag to this query.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->distinct()
* ->from('User', 'u');
* </code>
*
* @param bool
* @return QueryBuilder
*/
public function distinct($flag = true)
{
$this->_dqlParts['distinct'] = (bool) $flag;
return $this;
}
/**
* Adds an item that is to be returned in the query result.
*
@@ -981,7 +1001,9 @@ class QueryBuilder
private function _getDQLForSelect()
{
$dql = 'SELECT' . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '));
$dql = 'SELECT'
. ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
. $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '));
$fromParts = $this->getDQLPart('from');
$joinParts = $this->getDQLPart('join');
@@ -1090,4 +1112,4 @@ class QueryBuilder
}
}
}
}
}

View File

@@ -84,6 +84,6 @@ EOT
$output->write('<info>[Database] OK - The database schema is in sync with the mapping files.</info>' . "\n");
}
exit($exit);
return $exit;
}
}

View File

@@ -20,8 +20,8 @@
namespace Doctrine\ORM\Tools;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\AssociationMapping,
Doctrine\Common\Util\Inflector;
Doctrine\Common\Util\Inflector,
Doctrine\DBAL\Types\Type;
/**
* Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances
@@ -128,12 +128,31 @@ public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
* <description>
*
* @param <variableType>$<variableName>
* @return <entity>
*/
public function <methodName>(<methodTypeHint>$<variableName>)
{
<spaces>$this-><fieldName>[] = $<variableName>;
<spaces>return $this;
}';
/**
* @var string
*/
private static $_removeMethodTemplate =
'/**
* <description>
*
* @param <variableType>$<variableName>
*/
public function <methodName>(<methodTypeHint>$<variableName>)
{
<spaces>$this-><fieldName>->removeElement($<variableName>);
}';
/**
* @var string
*/
private static $_lifecycleCallbackMethodTemplate =
'/**
* @<name>
@@ -191,6 +210,8 @@ public function <methodName>()
if ( ! $this->_isNew) {
$this->_parseTokensInEntityFile(file_get_contents($path));
} else {
$this->_staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array());
}
if ($this->_backupExisting && file_exists($path)) {
@@ -581,9 +602,32 @@ public function <methodName>()
$table[] = 'name="' . $metadata->table['name'] . '"';
}
if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
$constraints = $this->_generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
$table[] = 'uniqueConstraints={' . $constraints . '}';
}
if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
$constraints = $this->_generateTableConstraints('Index', $metadata->table['indexes']);
$table[] = 'indexes={' . $constraints . '}';
}
return '@' . $this->_annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
}
private function _generateTableConstraints($constraintName, $constraints)
{
$annotations = array();
foreach ($constraints as $name => $constraint) {
$columns = array();
foreach ($constraint['columns'] as $column) {
$columns[] = '"' . $column . '"';
}
$annotations[] = '@' . $this->_annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
}
return implode(', ', $annotations);
}
private function _generateInheritanceAnnotation($metadata)
{
if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
@@ -645,6 +689,9 @@ public function <methodName>()
if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
if ($code = $this->_generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
$methods[] = $code;
}
@@ -656,6 +703,9 @@ public function <methodName>()
private function _isAssociationIsNullable($associationMapping)
{
if (isset($associationMapping['id']) && $associationMapping['id']) {
return false;
}
if (isset($associationMapping['joinColumns'])) {
$joinColumns = $associationMapping['joinColumns'];
} else {
@@ -726,12 +776,9 @@ public function <methodName>()
private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
{
if ($type == "add") {
$addMethod = explode("\\", $typeHint);
$addMethod = end($addMethod);
$methodName = $type . $addMethod;
} else {
$methodName = $type . Inflector::classify($fieldName);
$methodName = $type . Inflector::classify($fieldName);
if (in_array($type, array("add", "remove")) && substr($methodName, -1) == "s") {
$methodName = substr($methodName, 0, -1);
}
if ($this->_hasMethod($methodName, $metadata)) {
@@ -775,7 +822,7 @@ public function <methodName>()
$this->_staticReflection[$metadata->name]['methods'][] = $methodName;
$replacements = array(
'<name>' => $this->_annotationsPrefix . $name,
'<name>' => $this->_annotationsPrefix . ucfirst($name),
'<methodName>' => $methodName,
);
@@ -832,6 +879,14 @@ public function <methodName>()
if ($this->_generateAnnotations) {
$lines[] = $this->_spaces . ' *';
if (isset($associationMapping['id']) && $associationMapping['id']) {
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Id';
if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
}
}
$type = null;
switch ($associationMapping['type']) {
@@ -997,11 +1052,11 @@ public function <methodName>()
}
if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
$sequenceGenerator[] = 'allocationSize="' . $metadata->sequenceGeneratorDefinition['allocationSize'] . '"';
$sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
}
if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
$sequenceGenerator[] = 'initialValue="' . $metadata->sequenceGeneratorDefinition['initialValue'] . '"';
$sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
}
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
@@ -1088,4 +1143,4 @@ public function <methodName>()
throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
}
}
}
}

View File

@@ -23,7 +23,6 @@
namespace Doctrine\ORM\Tools\Export\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\AssociationMapping,
Doctrine\ORM\Tools\EntityGenerator;
/**
@@ -69,4 +68,4 @@ class AnnotationExporter extends AbstractExporter
{
$this->_entityGenerator = $entityGenerator;
}
}
}

View File

@@ -92,7 +92,7 @@ class PhpExporter extends AbstractExporter
$lines[] = '$metadata->mapField(' . $this->_varExport($fieldMapping) . ');';
}
if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
if ( ! $metadata->isIdentifierComposite && $generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$lines[] = '$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_' . $generatorType . ');';
}
@@ -166,4 +166,4 @@ class PhpExporter extends AbstractExporter
return $export;
}
}
}

View File

@@ -127,7 +127,7 @@ class XmlExporter extends AbstractExporter
}
}
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
if ( ! $metadata->isIdentifierComposite && $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$id[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType;
}
@@ -317,4 +317,4 @@ class XmlExporter extends AbstractExporter
$result = $dom->saveXML();
return $result;
}
}
}

View File

@@ -114,8 +114,8 @@ class YamlExporter extends AbstractExporter
$fieldMappings[$name] = $fieldMapping;
}
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $this->_getIdGeneratorTypeString($metadata->generatorType);
if ( ! $metadata->isIdentifierComposite && $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType;
}
if ($ids) {
@@ -172,7 +172,13 @@ class YamlExporter extends AbstractExporter
);
$associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
$array['oneToOne'][$name] = $associationMappingArray;
if ($associationMapping['type'] & ClassMetadataInfo::ONE_TO_ONE) {
$array['oneToOne'][$name] = $associationMappingArray;
} else {
$array['manyToOne'][$name] = $associationMappingArray;
}
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
$oneToManyMappingArray = array(
'mappedBy' => $associationMapping['mappedBy'],

View File

@@ -0,0 +1,80 @@
<?php
/**
* Doctrine ORM
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to kontakt@beberlei.de so I can send you a copy immediately.
*/
namespace Doctrine\ORM\Tools\Pagination;
use Doctrine\ORM\Query\TreeWalkerAdapter,
Doctrine\ORM\Query\AST\SelectStatement,
Doctrine\ORM\Query\AST\SelectExpression,
Doctrine\ORM\Query\AST\PathExpression,
Doctrine\ORM\Query\AST\AggregateExpression;
/**
* Replaces the selectClause of the AST with a COUNT statement
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
class CountWalker extends TreeWalkerAdapter
{
/**
* Distinct mode hint name
*/
const HINT_DISTINCT = 'doctrine_paginator.distinct';
/**
* Walks down a SelectStatement AST node, modifying it to retrieve a COUNT
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$rootComponents = array();
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
$isParent = array_key_exists('parent', $qComp)
&& $qComp['parent'] === null
&& $qComp['nestingLevel'] == 0
;
if ($isParent) {
$rootComponents[] = array($dqlAlias => $qComp);
}
}
if (count($rootComponents) > 1) {
throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction");
}
$root = reset($rootComponents);
$parentName = key($root);
$parent = current($root);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
$parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT);
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, $distinct), null
)
);
// ORDER BY is not needed, only increases query execution through unnecessary sorting.
$AST->orderByClause = null;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* Doctrine ORM
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE. This license can also be viewed
* at http://hobodave.com/license.txt
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
namespace Doctrine\ORM\Tools\Pagination;
use Doctrine\DBAL\Types\Type,
Doctrine\ORM\Query\TreeWalkerAdapter,
Doctrine\ORM\Query\AST\SelectStatement,
Doctrine\ORM\Query\AST\SelectExpression,
Doctrine\ORM\Query\AST\PathExpression,
Doctrine\ORM\Query\AST\AggregateExpression;
/**
* Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
class LimitSubqueryWalker extends TreeWalkerAdapter
{
/**
* ID type hint
*/
const IDENTIFIER_TYPE = 'doctrine_paginator.id.type';
/**
* @var int Counter for generating unique order column aliases
*/
private $_aliasCounter = 0;
/**
* Walks down a SelectStatement AST node, modifying it to retrieve DISTINCT ids
* of the root Entity
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$parent = null;
$parentName = null;
$selectExpressions = array();
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
// preserve mixed data in query for ordering
if (isset($qComp['resultVariable'])) {
$selectExpressions[] = new SelectExpression($qComp['resultVariable'], $dqlAlias);
continue;
}
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
continue;
}
}
$identifier = $parent['metadata']->getSingleIdentifierFieldName();
$this->_getQuery()->setHint(
self::IDENTIFIER_TYPE,
Type::getType($parent['metadata']->getTypeOfField($identifier))
);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
$parentName,
$identifier
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
array_unshift($selectExpressions, new SelectExpression($pathExpression, '_dctrn_id'));
$AST->selectClause->selectExpressions = $selectExpressions;
if (isset($AST->orderByClause)) {
foreach ($AST->orderByClause->orderByItems as $item) {
if ($item->expression instanceof PathExpression) {
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
$item->expression->identificationVariable,
$item->expression->field
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions[] = new SelectExpression(
$pathExpression,
'_dctrn_ord' . $this->_aliasCounter++
);
}
}
}
$AST->selectClause->isDistinct = true;
}
}

View File

@@ -0,0 +1,180 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Tools\Pagination;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Tools\Pagination\WhereInWalker;
use Doctrine\ORM\Tools\Pagination\CountWalker;
use Countable;
use IteratorAggregate;
use ArrayIterator;
/**
* Paginator
*
* The paginator can handle various complex scenarios with DQL.
*
* @author Pablo Díez <pablodip@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @license New BSD
*/
class Paginator implements \Countable, \IteratorAggregate
{
/**
* @var Query
*/
private $query;
/**
* @var bool
*/
private $fetchJoinCollection;
/**
* @var int
*/
private $count;
/**
* Constructor.
*
* @param Query|QueryBuilder $query A Doctrine ORM query or query builder.
* @param Boolean $fetchJoinCollection Whether the query joins a collection (true by default).
*/
public function __construct($query, $fetchJoinCollection = true)
{
if ($query instanceof QueryBuilder) {
$query = $query->getQuery();
}
$this->query = $query;
$this->fetchJoinCollection = (Boolean) $fetchJoinCollection;
}
/**
* Returns the query
*
* @return Query
*/
public function getQuery()
{
return $this->query;
}
/**
* Returns whether the query joins a collection.
*
* @return Boolean Whether the query joins a collection.
*/
public function getFetchJoinCollection()
{
return $this->fetchJoinCollection;
}
/**
* {@inheritdoc}
*/
public function count()
{
if ($this->count === null) {
/* @var $countQuery Query */
$countQuery = $this->cloneQuery($this->query);
if ( ! $countQuery->getHint(CountWalker::HINT_DISTINCT)) {
$countQuery->setHint(CountWalker::HINT_DISTINCT, true);
}
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
try {
$data = $countQuery->getScalarResult();
$data = array_map('current', $data);
$this->count = array_sum($data);
} catch(NoResultException $e) {
$this->count = 0;
}
}
return $this->count;
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
$offset = $this->query->getFirstResult();
$length = $this->query->getMaxResults();
if ($this->fetchJoinCollection) {
$subQuery = $this->cloneQuery($this->query);
$subQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker'))
->setFirstResult($offset)
->setMaxResults($length);
$ids = array_map('current', $subQuery->getScalarResult());
$whereInQuery = $this->cloneQuery($this->query);
// don't do this for an empty id array
if (count($ids) > 0) {
$namespace = WhereInWalker::PAGINATOR_ID_ALIAS;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker'));
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
$whereInQuery->setFirstResult(null)->setMaxResults(null);
foreach ($ids as $i => $id) {
$i++;
$whereInQuery->setParameter("{$namespace}_{$i}", $id);
}
}
$result = $whereInQuery->getResult($this->query->getHydrationMode());
} else {
$result = $this->cloneQuery($this->query)
->setMaxResults($length)
->setFirstResult($offset)
->getResult($this->query->getHydrationMode())
;
}
return new \ArrayIterator($result);
}
/**
* Clones a query.
*
* @param Query $query The query.
*
* @return Query The cloned query.
*/
private function cloneQuery(Query $query)
{
/* @var $cloneQuery Query */
$cloneQuery = clone $query;
$cloneQuery->setParameters($query->getParameters());
foreach ($query->getHints() as $name => $value) {
$cloneQuery->setHint($name, $value);
}
return $cloneQuery;
}
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* Doctrine ORM
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE. This license can also be viewed
* at http://hobodave.com/license.txt
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
namespace Doctrine\ORM\Tools\Pagination;
use Doctrine\ORM\Query\AST\ArithmeticExpression,
Doctrine\ORM\Query\AST\SimpleArithmeticExpression,
Doctrine\ORM\Query\TreeWalkerAdapter,
Doctrine\ORM\Query\AST\SelectStatement,
Doctrine\ORM\Query\AST\PathExpression,
Doctrine\ORM\Query\AST\InExpression,
Doctrine\ORM\Query\AST\NullComparisonExpression,
Doctrine\ORM\Query\AST\InputParameter,
Doctrine\ORM\Query\AST\ConditionalPrimary,
Doctrine\ORM\Query\AST\ConditionalTerm,
Doctrine\ORM\Query\AST\ConditionalExpression,
Doctrine\ORM\Query\AST\ConditionalFactor,
Doctrine\ORM\Query\AST\WhereClause;
/**
* Replaces the whereClause of the AST with a WHERE id IN (:foo_1, :foo_2) equivalent
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
class WhereInWalker extends TreeWalkerAdapter
{
/**
* ID Count hint name
*/
const HINT_PAGINATOR_ID_COUNT = 'doctrine.id.count';
/**
* Primary key alias for query
*/
const PAGINATOR_ID_ALIAS = 'dpid';
/**
* Replaces the whereClause in the AST
*
* Generates a clause equivalent to WHERE IN (:dpid_1, :dpid_2, ...)
*
* The parameter namespace (dpid) is defined by
* the PAGINATOR_ID_ALIAS
*
* The total number of parameters is retrieved from
* the HINT_PAGINATOR_ID_COUNT query hint
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$rootComponents = array();
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
$isParent = array_key_exists('parent', $qComp)
&& $qComp['parent'] === null
&& $qComp['nestingLevel'] == 0
;
if ($isParent) {
$rootComponents[] = array($dqlAlias => $qComp);
}
}
if (count($rootComponents) > 1) {
throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction");
}
$root = reset($rootComponents);
$parentName = key($root);
$parent = current($root);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD, $parentName, $parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$count = $this->_getQuery()->getHint(self::HINT_PAGINATOR_ID_COUNT);
if ($count > 0) {
$arithmeticExpression = new ArithmeticExpression();
$arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression(
array($pathExpression)
);
$expression = new InExpression($arithmeticExpression);
$ns = self::PAGINATOR_ID_ALIAS;
for ($i = 1; $i <= $count; $i++) {
$expression->literals[] = new InputParameter(":{$ns}_$i");
}
} else {
$expression = new NullComparisonExpression($pathExpression);
$expression->not = false;
}
$conditionalPrimary = new ConditionalPrimary;
$conditionalPrimary->simpleConditionalExpression = $expression;
if ($AST->whereClause) {
if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) {
$AST->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary;
} elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) {
$AST->whereClause->conditionalExpression = new ConditionalExpression(array(
new ConditionalTerm(array(
$AST->whereClause->conditionalExpression,
$conditionalPrimary
))
));
} elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression) {
$tmpPrimary = new ConditionalPrimary;
$tmpPrimary->conditionalExpression = $AST->whereClause->conditionalExpression;
$AST->whereClause->conditionalExpression = new ConditionalTerm(array(
$tmpPrimary,
$conditionalPrimary
));
}
} else {
$AST->whereClause = new WhereClause(
new ConditionalExpression(array(
new ConditionalTerm(array(
$conditionalPrimary
))
))
);
}
}
}

View File

@@ -21,6 +21,8 @@ namespace Doctrine\ORM\Tools;
use Doctrine\ORM\ORMException,
Doctrine\DBAL\Types\Type,
Doctrine\DBAL\Schema\Schema,
Doctrine\DBAL\Schema\Visitor\RemoveNamespacedAssets,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Internal\CommitOrderCalculator,
@@ -127,7 +129,7 @@ class SchemaTool
$sm = $this->_em->getConnection()->getSchemaManager();
$metadataSchemaConfig = $sm->createSchemaConfig();
$metadataSchemaConfig->setExplicitForeignKeyIndexes(false);
$schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $metadataSchemaConfig);
$schema = new Schema(array(), array(), $metadataSchemaConfig);
$evm = $this->_em->getEventManager();
@@ -223,13 +225,13 @@ class SchemaTool
if (isset($class->table['indexes'])) {
foreach ($class->table['indexes'] AS $indexName => $indexData) {
$table->addIndex($indexData['columns'], $indexName);
$table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName);
}
}
if (isset($class->table['uniqueConstraints'])) {
foreach ($class->table['uniqueConstraints'] AS $indexName => $indexData) {
$table->addUniqueIndex($indexData['columns'], $indexName);
$table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName);
}
}
@@ -252,6 +254,10 @@ class SchemaTool
}
}
if ( ! $this->_platform->supportsSchemas() && ! $this->_platform->canEmulateSchemas() ) {
$schema->visit(new RemoveNamespacedAssets());
}
if ($evm->hasListeners(ToolEvents::postGenerateSchema)) {
$evm->dispatchEvent(ToolEvents::postGenerateSchema, new GenerateSchemaEventArgs($this->_em, $schema));
}
@@ -360,6 +366,10 @@ class SchemaTool
$options['columnDefinition'] = $mapping['columnDefinition'];
}
if (isset($mapping['options'])) {
$options['customSchemaOptions'] = $mapping['options'];
}
if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) {
$options['autoincrement'] = true;
}

View File

@@ -95,8 +95,8 @@ class SchemaValidator
}
foreach ($class->associationMappings AS $fieldName => $assoc) {
if (!$cmf->hasMetadataFor($assoc['targetEntity'])) {
$ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.';
if (!class_exists($assoc['targetEntity']) || $cmf->isTransient($assoc['targetEntity'])) {
$ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown or not an entity.';
return $ce;
}
@@ -106,6 +106,11 @@ class SchemaValidator
$targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']);
if (isset($assoc['id']) && $targetMetadata->containsForeignIdentifier) {
$ce[] = "Cannot map association '" . $class->name. "#". $fieldName ." as identifier, because " .
"the target entity '". $targetMetadata->name . "' also maps an association as identifier.";
}
/* @var $assoc AssociationMapping */
if ($assoc['mappedBy']) {
if ($targetMetadata->hasField($assoc['mappedBy'])) {
@@ -119,7 +124,7 @@ class SchemaValidator
$ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ".
"bi-directional relationship, but the specified mappedBy association on the target-entity ".
$assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ".
"'inversedBy' attribute.";
"'inversedBy=".$fieldName."' attribute.";
} else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) {
$ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " .
$assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ".
@@ -147,45 +152,38 @@ class SchemaValidator
}
// Verify inverse side/owning side match each other
$targetAssoc = $targetMetadata->associationMappings[$assoc['inversedBy']];
if ($assoc['type'] == ClassMetadataInfo::ONE_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_ONE){
$ce[] = "If association " . $class->name . "#" . $fieldName . " is one-to-one, then the inversed " .
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-one as well.";
} else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_MANY){
$ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-one, then the inversed " .
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-many.";
} else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY && $targetAssoc['type'] !== ClassMetadataInfo::MANY_TO_MANY){
$ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-many, then the inversed " .
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be many-to-many as well.";
if (array_key_exists($assoc['inversedBy'], $targetMetadata->associationMappings)) {
$targetAssoc = $targetMetadata->associationMappings[$assoc['inversedBy']];
if ($assoc['type'] == ClassMetadataInfo::ONE_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_ONE){
$ce[] = "If association " . $class->name . "#" . $fieldName . " is one-to-one, then the inversed " .
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-one as well.";
} else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_MANY){
$ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-one, then the inversed " .
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-many.";
} else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY && $targetAssoc['type'] !== ClassMetadataInfo::MANY_TO_MANY){
$ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-many, then the inversed " .
"side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be many-to-many as well.";
}
}
}
if ($assoc['isOwningSide']) {
if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) {
$identifierColumns = $class->getIdentifierColumnNames();
foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) {
if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) {
$ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " .
"have a corresponding field with this column name on the class '" . $class->name . "'.";
break;
}
$fieldName = $class->fieldNames[$joinColumn['referencedColumnName']];
if (!in_array($fieldName, $class->identifier)) {
if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) {
$ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " .
"has to be a primary key column.";
"has to be a primary key column on the target entity class '".$class->name."'.";
break;
}
}
foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) {
if (!isset($targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']])) {
$ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " .
"have a corresponding field with this column name on the class '" . $targetMetadata->name . "'.";
break;
}
$fieldName = $targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']];
if (!in_array($fieldName, $targetMetadata->identifier)) {
$ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " .
"has to be a primary key column.";
$identifierColumns = $targetMetadata->getIdentifierColumnNames();
foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) {
if (!in_array($inverseJoinColumn['referencedColumnName'], $identifierColumns)) {
$ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " .
"has to be a primary key column on the target entity class '".$targetMetadata->name."'.";
break;
}
}
@@ -204,29 +202,23 @@ class SchemaValidator
}
} else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) {
$identifierColumns = $targetMetadata->getIdentifierColumnNames();
foreach ($assoc['joinColumns'] AS $joinColumn) {
if (!isset($targetMetadata->fieldNames[$joinColumn['referencedColumnName']])) {
$ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " .
"have a corresponding field with this column name on the class '" . $targetMetadata->name . "'.";
break;
}
$fieldName = $targetMetadata->fieldNames[$joinColumn['referencedColumnName']];
if (!in_array($fieldName, $targetMetadata->identifier)) {
if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) {
$ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " .
"has to be a primary key column.";
"has to be a primary key column on the target entity class '".$targetMetadata->name."'.";
}
}
if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) {
if (count($identifierColumns) != count($assoc['joinColumns'])) {
$ids = array();
foreach ($assoc['joinColumns'] AS $joinColumn) {
$ids[] = $joinColumn['name'];
}
$ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " .
"have to match to ALL identifier columns of the source entity '". $class->name . "', " .
"however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $ids)) .
"have to match to ALL identifier columns of the target entity '". $class->name . "', " .
"however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $ids)) .
"' are missing.";
}
}

View File

@@ -390,7 +390,7 @@ class UnitOfWork implements PropertyChangedListener
*/
private function computeSingleEntityChangeSet($entity)
{
if ( ! $this->isInIdentityMap($entity) ) {
if ( $this->getEntityState($entity) !== self::STATE_MANAGED) {
throw new \InvalidArgumentException("Entity has to be managed for single computation " . self::objToStr($entity));
}
@@ -584,6 +584,23 @@ class UnitOfWork implements PropertyChangedListener
$assoc = $class->associationMappings[$propName];
// Persistent collection was exchanged with the "originally"
// created one. This can only mean it was cloned and replaced
// on another entity.
if ($actualValue instanceof PersistentCollection) {
$owner = $actualValue->getOwner();
if ($owner === null) { // cloned
$actualValue->setOwner($entity, $assoc);
} else if ($owner !== $entity) { // no clone, we have to fix
if (!$actualValue->isInitialized()) {
$actualValue->initialize(); // we have to do this otherwise the cols share state
}
$newValue = clone $actualValue;
$newValue->setOwner($entity, $assoc);
$class->reflFields[$propName]->setValue($entity, $newValue);
}
}
if ($orgValue instanceof PersistentCollection) {
// A PersistentCollection was de-referenced, so delete it.
$coid = spl_object_hash($orgValue);
@@ -620,6 +637,15 @@ class UnitOfWork implements PropertyChangedListener
foreach ($class->associationMappings as $field => $assoc) {
if (($val = $class->reflFields[$field]->getValue($entity)) !== null) {
$this->computeAssociationChanges($assoc, $val);
if (!isset($this->entityChangeSets[$oid]) &&
$assoc['isOwningSide'] &&
$assoc['type'] == ClassMetadata::MANY_TO_MANY &&
$val instanceof PersistentCollection &&
$val->isDirty()) {
$this->entityChangeSets[$oid] = array();
$this->originalEntityData[$oid] = $actualData;
$this->entityUpdates[$oid] = $entity;
}
}
}
}
@@ -707,6 +733,15 @@ class UnitOfWork implements PropertyChangedListener
$state = $this->getEntityState($entry, self::STATE_NEW);
$oid = spl_object_hash($entry);
if (!($entry instanceof $assoc['targetEntity'])) {
throw new ORMException(sprintf("Found entity of type %s on association %s#%s, but expecting %s",
get_class($entry),
$assoc['sourceEntity'],
$assoc['fieldName'],
$targetClass->name
));
}
switch ($state) {
case self::STATE_NEW:
if ( ! $assoc['isCascadePersist']) {
@@ -1083,6 +1118,10 @@ class UnitOfWork implements PropertyChangedListener
if (isset($this->entityIdentifiers[$oid])) {
$this->addToIdentityMap($entity);
}
if ($entity instanceof NotifyPropertyChanged) {
$entity->addPropertyChangedListener($this);
}
}
/**
@@ -1261,10 +1300,6 @@ class UnitOfWork implements PropertyChangedListener
$this->identityMap[$className][$idHash] = $entity;
if ($entity instanceof NotifyPropertyChanged) {
$entity->addPropertyChangedListener($this);
}
return true;
}
@@ -1367,6 +1402,7 @@ class UnitOfWork implements PropertyChangedListener
if (isset($this->identityMap[$className][$idHash])) {
unset($this->identityMap[$className][$idHash]);
unset($this->readOnlyObjects[$oid]);
//$this->entityStates[$oid] = self::STATE_DETACHED;
@@ -1613,7 +1649,7 @@ class UnitOfWork implements PropertyChangedListener
$oid = spl_object_hash($entity);
if (isset($visited[$oid])) {
return; // Prevent infinite recursion
return $visited[$oid]; // Prevent infinite recursion
}
$visited[$oid] = $entity; // mark visited
@@ -1676,6 +1712,10 @@ class UnitOfWork implements PropertyChangedListener
$class->setIdentifierValues($managedCopy, $id);
$this->persistNew($class, $managedCopy);
} else {
if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized__) {
$managedCopy->__load();
}
}
}
@@ -2176,6 +2216,7 @@ class UnitOfWork implements PropertyChangedListener
$this->collectionDeletions =
$this->collectionUpdates =
$this->extraUpdates =
$this->readOnlyObjects =
$this->orphanRemovals = array();
if ($this->commitOrderCalculator !== null) {
@@ -2315,11 +2356,12 @@ class UnitOfWork implements PropertyChangedListener
}
} else {
$entity = $this->newInstance($class);
$oid = spl_object_hash($entity);
$oid = spl_object_hash($entity);
$this->entityIdentifiers[$oid] = $id;
$this->entityStates[$oid] = self::STATE_MANAGED;
$this->entityIdentifiers[$oid] = $id;
$this->entityStates[$oid] = self::STATE_MANAGED;
$this->originalEntityData[$oid] = $data;
$this->identityMap[$class->rootEntityName][$idHash] = $entity;
if ($entity instanceof NotifyPropertyChanged) {
@@ -2485,6 +2527,17 @@ class UnitOfWork implements PropertyChangedListener
}
}
if ($overrideLocalValues) {
if (isset($class->lifecycleCallbacks[Events::postLoad])) {
$class->invokeLifecycleCallbacks(Events::postLoad, $entity);
}
if ($this->evm->hasListeners(Events::postLoad)) {
$this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em));
}
}
return $entity;
}
@@ -2738,6 +2791,10 @@ class UnitOfWork implements PropertyChangedListener
$this->originalEntityData[$oid] = $data;
$this->addToIdentityMap($entity);
if ($entity instanceof NotifyPropertyChanged) {
$entity->addPropertyChangedListener($this);
}
}
/**

View File

@@ -36,7 +36,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.2.0-DEV';
const VERSION = '2.2.3';
/**
* Compares a Doctrine version with the current one.

View File

@@ -28,7 +28,7 @@ class CompanyPerson
*/
private $name;
/**
* @OneToOne(targetEntity="CompanyPerson", mappedBy="spouse")
* @OneToOne(targetEntity="CompanyPerson")
* @JoinColumn(name="spouse_id", referencedColumnName="id")
*/
private $spouse;

View File

@@ -16,7 +16,7 @@ class DDC117Reference
/**
* @Id
* @ManyToOne(targetEntity="DDC117Article", inversedBy="references")
* @ManyToOne(targetEntity="DDC117Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
private $target;
@@ -61,4 +61,4 @@ class DDC117Reference
{
return $this->description;
}
}
}

View File

@@ -9,7 +9,7 @@ class DDC117Translation
{
/**
* @Id
* @ManyToOne(targetEntity="DDC117Article")
* @ManyToOne(targetEntity="DDC117Article", inversedBy="translations")
* @JoinColumn(name="article_id", referencedColumnName="article_id")
*/
private $article;
@@ -62,4 +62,4 @@ class DDC117Translation
{
return $this->reviewedByEditors;
}
}
}

View File

@@ -17,7 +17,7 @@ class LegacyUserReference
/**
* @Id
* @ManyToOne(targetEntity="LegacyUser", inversedBy="_references")
* @ManyToOne(targetEntity="LegacyUser")
* @JoinColumn(name="iUserIdTarget", referencedColumnName="iUserId")
*/
private $_target;

View File

@@ -26,16 +26,29 @@ class NavPointOfInterest
private $name;
/**
* @ManyToOne(targetEntity="NavCountry")
* @ManyToOne(targetEntity="NavCountry", inversedBy="pois")
*/
private $country;
/**
* @ManyToMany(targetEntity="NavUser", cascade={"persist"})
* @JoinTable(name="navigation_pois_visitors",
* inverseJoinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* joinColumns={
* @JoinColumn(name="poi_long", referencedColumnName="nav_long"),
* @JoinColumn(name="poi_lat", referencedColumnName="nav_lat")
* }
* )
*/
private $visitors;
public function __construct($lat, $long, $name, $country)
{
$this->lat = $lat;
$this->long = $long;
$this->name = $name;
$this->country = $country;
$this->visitors = new \Doctrine\Common\Collections\ArrayCollection;
}
public function getLong() {
@@ -53,4 +66,14 @@ class NavPointOfInterest
public function getCountry() {
return $this->country;
}
}
public function addVisitor(NavUser $user)
{
$this->visitors[] = $user;
}
public function getVisitors()
{
return $this->visitors;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\Tests\Models\Navigation;
/**
* @Entity
* @Table(name="navigation_users")
*/
class NavUser
{
/**
* @Id
* @Column(type="integer")
* @generatedValue
*/
private $id;
/**
* @column(type="string")
*/
private $name;
public function __construct($name)
{
$this->name = $name;
}
}

View File

@@ -1141,6 +1141,21 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush($user);
}
/**
* @group DDC-720
* @group DDC-1612
*/
public function testFlushSingleNewEntity()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->flush($user);
}
/**
* @group DDC-720
*/
@@ -1197,4 +1212,21 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user2 = $this->_em->find(get_class($user2), $user2->id);
$this->assertEquals('developer', $user2->status);
}
/**
* @group DDC-1585
*/
public function testWrongAssocationInstance()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$user->address = $user;
$this->_em->persist($user);
$this->setExpectedException("Doctrine\ORM\ORMException", "Found entity of type Doctrine\Tests\Models\CMS\CmsUser on association Doctrine\Tests\Models\CMS\CmsUser#address, but expecting Doctrine\Tests\Models\CMS\CmsAddress");
$this->_em->flush();
}
}

View File

@@ -448,4 +448,23 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
$manager = $this->_em->find('Doctrine\Tests\Models\Company\CompanyManager', $manager->getId());
$this->assertEquals(1, count($manager->getFriends()));
}
/**
* @group DDC-1777
*/
public function testExistsSubclass()
{
$manager = new CompanyManager();
$manager->setName('gblanco');
$manager->setSalary(1234);
$manager->setTitle('Awesome!');
$manager->setDepartment('IT');
$this->assertFalse($this->_em->getUnitOfWork()->getEntityPersister(get_class($manager))->exists($manager));
$this->_em->persist($manager);
$this->_em->flush();
$this->assertTrue($this->_em->getUnitOfWork()->getEntityPersister(get_class($manager))->exists($manager));
}
}

View File

@@ -4,6 +4,8 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Navigation\NavCountry;
use Doctrine\Tests\Models\Navigation\NavPointOfInterest;
use Doctrine\Tests\Models\Navigation\NavTour;
use Doctrine\Tests\Models\Navigation\NavPhotos;
use Doctrine\Tests\Models\Navigation\NavUser;
require_once __DIR__ . '/../../TestInit.php';
@@ -51,6 +53,25 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('Brandenburger Tor', $poi->getName());
}
/**
* @group DDC-1651
*/
public function testSetParameterCompositeKeyObject()
{
$this->putGermanysBrandenburderTor();
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('lat' => 100, 'long' => 200));
$photo = new NavPhotos($poi, "asdf");
$this->_em->persist($photo);
$this->_em->flush();
$this->_em->clear();
$dql = 'SELECT t FROM Doctrine\Tests\Models\Navigation\NavPhotos t WHERE t.poi = ?1';
$this->setExpectedException('Doctrine\ORM\Query\QueryException', 'A single-valued association path expression to an entity with a composite primary key is not supported.');
$sql = $this->_em->createQuery($dql)->getSQL();
}
public function testManyToManyCompositeRelation()
{
$this->putGermanysBrandenburderTor();
@@ -98,4 +119,26 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->setExpectedException('Doctrine\ORM\ORMException', 'The identifier long is missing for a query of Doctrine\Tests\Models\Navigation\NavPointOfInterest');
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('key1' => 100));
}
}
/**
* @group DDC-1939
*/
public function testDeleteCompositePersistentCollection()
{
$this->putGermanysBrandenburderTor();
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('lat' => 100, 'long' => 200));
$poi->addVisitor(new NavUser("test1"));
$poi->addVisitor(new NavUser("test2"));
$this->_em->flush();
$poi->getVisitors()->clear();
$this->_em->flush();
$this->_em->clear();
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('lat' => 100, 'long' => 200));
$this->assertEquals(0, count($poi->getVisitors()));
}
}

View File

@@ -64,7 +64,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
/**
* @group DDC-546
*/
public function testCountWhenNewEntitysPresent()
public function testCountWhenNewEntityPresent()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
@@ -223,13 +223,15 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized.");
$article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId);
// Test One to Many existance retrieved from DB
$article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId);
$queryCount = $this->getCurrentQueryCount();
$this->assertTrue($user->articles->contains($article));
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
// Test One to Many existance with state new
$article = new \Doctrine\Tests\Models\CMS\CmsArticle();
$article->topic = "Testnew";
$article->text = "blub";
@@ -238,12 +240,26 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertFalse($user->articles->contains($article));
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of new entity should cause no query to be executed.");
// Test One to Many existance with state clear
$this->_em->persist($article);
$this->_em->flush();
$queryCount = $this->getCurrentQueryCount();
$this->assertFalse($user->articles->contains($article));
$this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed.");
$this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of persisted entity should cause one query to be executed.");
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test One to Many existance with state managed
$article = new \Doctrine\Tests\Models\CMS\CmsArticle();
$article->topic = "How to not fail anymore on tests";
$article->text = "That is simple! Just write more tests!";
$this->_em->persist($article);
$queryCount = $this->getCurrentQueryCount();
$this->assertFalse($user->articles->contains($article));
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of managed entity (but not persisted) should cause no query to be executed.");
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
}
@@ -255,6 +271,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized.");
// Test Many to Many existance retrieved from DB
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount();
@@ -262,6 +279,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test Many to Many existance with state new
$group = new \Doctrine\Tests\Models\CMS\CmsGroup();
$group->name = "A New group!";
@@ -271,13 +289,26 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of new entity should cause no query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test Many to Many existance with state clear
$this->_em->persist($group);
$this->_em->flush();
$queryCount = $this->getCurrentQueryCount();
$this->assertFalse($user->groups->contains($group));
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed.");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of persisted entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test Many to Many existance with state managed
$group = new \Doctrine\Tests\Models\CMS\CmsGroup();
$group->name = "My managed group";
$this->_em->persist($group);
$queryCount = $this->getCurrentQueryCount();
$this->assertFalse($user->groups->contains($group));
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of managed entity (but not persisted) should cause no query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
}
@@ -313,6 +344,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized.");
// Test One to Many removal with Entity retrieved from DB
$article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId);
$queryCount = $this->getCurrentQueryCount();
@@ -321,6 +353,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
// Test One to Many removal with Entity state as new
$article = new \Doctrine\Tests\Models\CMS\CmsArticle();
$article->topic = "Testnew";
$article->text = "blub";
@@ -331,6 +364,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a new entity should cause no query to be executed.");
// Test One to Many removal with Entity state as clean
$this->_em->persist($article);
$this->_em->flush();
@@ -338,8 +372,21 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user->articles->removeElement($article);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed.");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed.");
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test One to Many removal with Entity state as managed
$article = new \Doctrine\Tests\Models\CMS\CmsArticle();
$article->topic = "How to not fail anymore on tests";
$article->text = "That is simple! Just write more tests!";
$this->_em->persist($article);
$queryCount = $this->getCurrentQueryCount();
$user->articles->removeElement($article);
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a managed entity should cause no query to be executed.");
}
/**
@@ -350,14 +397,16 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized.");
// Test Many to Many removal with Entity retrieved from DB
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount();
$user->groups->removeElement($group);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed.");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test Many to Many removal with Entity state as new
$group = new \Doctrine\Tests\Models\CMS\CmsGroup();
$group->name = "A New group!";
@@ -368,6 +417,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing new entity should cause no query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test Many to Many removal with Entity state as clean
$this->_em->persist($group);
$this->_em->flush();
@@ -375,7 +425,20 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user->groups->removeElement($group);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed.");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
// Test Many to Many removal with Entity state as managed
$group = new \Doctrine\Tests\Models\CMS\CmsGroup();
$group->name = "A New group!";
$this->_em->persist($group);
$queryCount = $this->getCurrentQueryCount();
$user->groups->removeElement($group);
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a managed entity should cause no query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\Tests\Models\Cms\CmsUser;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\Common\Cache\ArrayCache;
/**
* @group DDC-1766
*/
class HydrationCacheTest extends OrmFunctionalTestCase
{
public function setUp()
{
$this->useModelSet('cms');
parent::setUp();
$user = new CmsUser;
$user->name = "Benjamin";
$user->username = "beberlei";
$user->status = 'active';
$this->_em->persist($user);
$this->_em->flush();
$this->_em->clear();
}
public function testHydrationCache()
{
$cache = new ArrayCache();
$dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u";
$users = $this->_em->createQuery($dql)
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
->getResult();
$c = $this->getCurrentQueryCount();
$users = $this->_em->createQuery($dql)
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
->getResult();
$this->assertEquals($c, $this->getCurrentQueryCount(), "Should not execute query. Its cached!");
$users = $this->_em->createQuery($dql)
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
->getArrayResult();
$this->assertEquals($c + 1, $this->getCurrentQueryCount(), "Hydration is part of cache key.");
$users = $this->_em->createQuery($dql)
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
->getArrayResult();
$this->assertEquals($c + 1, $this->getCurrentQueryCount(), "Hydration now cached");
$users = $this->_em->createQuery($dql)
->setHydrationCacheProfile(new QueryCacheProfile(null, 'cachekey', $cache))
->getArrayResult();
$this->assertTrue($cache->contains('cachekey'), 'Explicit cache key');
$users = $this->_em->createQuery($dql)
->setHydrationCacheProfile(new QueryCacheProfile(null, 'cachekey', $cache))
->getArrayResult();
$this->assertEquals($c + 2, $this->getCurrentQueryCount(), "Hydration now cached");
}
public function testHydrationParametersSerialization()
{
$cache = new ArrayCache();
$user = new CmsUser();
$user->id = 1;
$dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u WHERE u.id = ?1";
$query = $this->_em->createQuery($dql)
->setParameter(1, $user)
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache));
$query->getResult();
$c = $this->getCurrentQueryCount();
$query->getResult();
$this->assertEquals($c, $this->getCurrentQueryCount(), "Should not execute query. Its cached!");
}
}

View File

@@ -9,6 +9,9 @@ use Doctrine\Tests\Models\CMS\CmsArticle,
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group locking
*/
class LockTest extends \Doctrine\Tests\OrmFunctionalTestCase {
protected function setUp() {
$this->useModelSet('cms');
@@ -139,7 +142,6 @@ class LockTest extends \Doctrine\Tests\OrmFunctionalTestCase {
/**
* @group DDC-178
* @group locking
*/
public function testLockPessimisticRead() {
$readLockSql = $this->_em->getConnection()->getDatabasePlatform()->getReadLockSql();
@@ -166,4 +168,17 @@ class LockTest extends \Doctrine\Tests\OrmFunctionalTestCase {
$query = array_pop( $this->_sqlLoggerStack->queries );
$this->assertContains($readLockSql, $query['sql']);
}
}
/**
* @group DDC-1693
*/
public function testLockOptimisticNonVersionedThrowsExceptionInDQL()
{
$dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'";
$this->setExpectedException('Doctrine\ORM\OptimisticLockException', 'The optimistic lock on an entity failed.');
$sql = $this->_em->createQuery($dql)->setHint(
\Doctrine\ORM\Query::HINT_LOCK_MODE, \Doctrine\DBAL\LockMode::OPTIMISTIC
)->getSQL();
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\ORM\Events;
require_once __DIR__ . '/../../TestInit.php';
/**
* ManyToManyEventTest
*
* @author Francisco Facioni <fran6co@gmail.com>
*/
class ManyToManyEventTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
/**
* @var PostUpdateListener
*/
private $listener;
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
$this->listener = new PostUpdateListener();
$evm = $this->_em->getEventManager();
$evm->addEventListener(Events::postUpdate, $this->listener);
}
public function testListenerShouldBeNotifiedOnlyWhenUpdating()
{
$user = $this->createNewValidUser();
$this->_em->persist($user);
$this->_em->flush();
$this->assertFalse($this->listener->wasNotified);
$group = new CmsGroup();
$group->name = "admins";
$user->addGroup($group);
$this->_em->persist($user);
$this->_em->flush();
$this->assertTrue($this->listener->wasNotified);
}
/**
* @return CmsUser
*/
private function createNewValidUser()
{
$user = new CmsUser();
$user->username = 'fran6co';
$user->name = 'Francisco Facioni';
$group = new CmsGroup();
$group->name = "users";
$user->addGroup($group);
return $user;
}
}
class PostUpdateListener
{
/**
* @var bool
*/
public $wasNotified = false;
/**
* @param $args
*/
public function postUpdate($args)
{
$this->wasNotified = true;
}
}

View File

@@ -40,9 +40,16 @@ class NotifyPolicyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($user);
$this->_em->persist($group);
$this->assertEquals(1, count($user->listeners));
$this->assertEquals(1, count($group->listeners));
$this->_em->flush();
$this->_em->clear();
$this->assertEquals(1, count($user->listeners));
$this->assertEquals(1, count($group->listeners));
$userId = $user->getId();
$groupId = $group->getId();
unset($user, $group);
@@ -52,6 +59,9 @@ class NotifyPolicyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$group = $this->_em->find(__NAMESPACE__.'\NotifyGroup', $groupId);
$this->assertEquals(1, $group->getUsers()->count());
$this->assertEquals(1, count($user->listeners));
$this->assertEquals(1, count($group->listeners));
$group2 = new NotifyGroup();
$group2->setName('nerds');
$this->_em->persist($group2);
@@ -63,6 +73,9 @@ class NotifyPolicyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$this->assertEquals(1, count($user->listeners));
$this->assertEquals(1, count($group->listeners));
$group2Id = $group2->getId();
unset($group2, $user);
@@ -77,7 +90,7 @@ class NotifyPolicyTest extends \Doctrine\Tests\OrmFunctionalTestCase
}
class NotifyBaseEntity implements NotifyPropertyChanged {
private $listeners = array();
public $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;

View File

@@ -127,14 +127,14 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->clear();
$train = $this->_em->find(get_class($train), $train->id);
$this->assertEquals(
$this->assertSQLEquals(
"SELECT t0.id AS id1, t0.driver_id AS driver_id2, t3.id AS id4, t3.name AS name5, t0.owner_id AS owner_id6, t7.id AS id8, t7.name AS name9 FROM Train t0 LEFT JOIN TrainDriver t3 ON t0.driver_id = t3.id INNER JOIN TrainOwner t7 ON t0.owner_id = t7.id WHERE t0.id = ?",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
$this->_em->clear();
$driver = $this->_em->find(get_class($driver), $driver->id);
$this->assertEquals(
$this->assertSQLEquals(
"SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id IN (?)",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
@@ -155,13 +155,13 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$waggon = $this->_em->find(get_class($waggon), $waggon->id);
// The last query is the eager loading of the owner of the train
$this->assertEquals(
$this->assertSQLEquals(
"SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id IN (?)",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
// The one before is the fetching of the waggon and train
$this->assertEquals(
$this->assertSQLEquals(
"SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery - 1]['sql']
);
@@ -176,7 +176,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->clear();
$waggon = $this->_em->find(get_class($owner), $owner->id);
$this->assertEquals(
$this->assertSQLEquals(
"SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id = ?",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);

View File

@@ -0,0 +1,105 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Query;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsComment;
use Doctrine\ORM\Tools\Pagination\Paginator;
/**
* @group DDC-1613
*/
class PaginationTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
$this->populate();
}
public function testCountSimpleWithoutJoin()
{
$dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u";
$query = $this->_em->createQuery($dql);
$paginator = new Paginator($query);
$this->assertEquals(3, count($paginator));
}
public function testCountWithFetchJoin()
{
$dql = "SELECT u,g FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g";
$query = $this->_em->createQuery($dql);
$paginator = new Paginator($query);
$this->assertEquals(3, count($paginator));
}
public function testIterateSimpleWithoutJoinFetchJoinHandlingOff()
{
$dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u";
$query = $this->_em->createQuery($dql);
$paginator = new Paginator($query, false);
$data = array();
foreach ($paginator as $user) {
$data[] = $user;
}
$this->assertEquals(3, count($data));
}
public function testIterateSimpleWithoutJoinFetchJoinHandlingOn()
{
$dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u";
$query = $this->_em->createQuery($dql);
$paginator = new Paginator($query, true);
$data = array();
foreach ($paginator as $user) {
$data[] = $user;
}
$this->assertEquals(3, count($data));
}
public function testIterateWithFetchJoin()
{
$dql = "SELECT u,g FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g";
$query = $this->_em->createQuery($dql);
$paginator = new Paginator($query, true);
$data = array();
foreach ($paginator as $user) {
$data[] = $user;
}
$this->assertEquals(3, count($data));
}
public function populate()
{
for ($i = 0; $i < 3; $i++) {
$user = new CmsUser();
$user->name = "Name$i";
$user->username = "username$i";
$user->status = "active";
$this->_em->persist($user);
for ($j = 0; $j < 3; $j++) {;
$group = new CmsGroup();
$group->name = "group$j";
$user->addGroup($group);
$this->_em->persist($group);
}
}
$this->_em->flush();
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Common\Persistence\PersistentObject;
/**
*/
class PersistentCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\PersistentCollectionHolder'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\PersistentCollectionContent'),
));
} catch (\Exception $e) {
}
PersistentObject::setObjectManager($this->_em);
}
public function testPersist()
{
$collectionHolder = new PersistentCollectionHolder();
$content = new PersistentCollectionContent('first element');
$collectionHolder->addElement($content);
$this->_em->persist($collectionHolder);
$this->_em->flush();
$this->_em->clear();
$collectionHolder = $this->_em->find(__NAMESPACE__ . '\PersistentCollectionHolder', $collectionHolder->getId());
$collectionHolder->getCollection();
$content = new PersistentCollectionContent('second element');
$collectionHolder->addElement($content);
$this->assertEquals(2, $collectionHolder->getCollection()->count());
}
}
/**
* @Entity
*/
class PersistentCollectionHolder extends PersistentObject
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
protected $id;
/**
* @var \Doctrine\Common\Collections\Collection
* @ManyToMany(targetEntity="PersistentCollectionContent", cascade={"all"})
*/
protected $collection;
public function __construct()
{
$this->collection = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @param PersistentCollectionContent $element
*/
public function addElement(PersistentCollectionContent $element)
{
$this->collection->add($element);
}
/**
* @return \Doctrine\Common\Collections\Collection
*/
public function getCollection()
{
return clone $this->collection;
}
}
/**
* @Entity
*/
class PersistentCollectionContent extends PersistentObject
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
protected $id;
}

View File

@@ -227,6 +227,42 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->clear();
}
public function testIterateResultClearEveryCycle()
{
$article1 = new CmsArticle;
$article1->topic = "Doctrine 2";
$article1->text = "This is an introduction to Doctrine 2.";
$article2 = new CmsArticle;
$article2->topic = "Symfony 2";
$article2->text = "This is an introduction to Symfony 2.";
$this->_em->persist($article1);
$this->_em->persist($article2);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery("select a from Doctrine\Tests\Models\CMS\CmsArticle a");
$articles = $query->iterate();
$iteratedCount = 0;
$topics = array();
foreach($articles AS $row) {
$article = $row[0];
$topics[] = $article->topic;
$this->_em->clear();
$iteratedCount++;
}
$this->assertEquals(array("Doctrine 2", "Symfony 2"), $topics);
$this->assertEquals(2, $iteratedCount);
$this->_em->flush();
}
/**
* @expectedException \Doctrine\ORM\Query\QueryException
*/
@@ -599,4 +635,24 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(3, count($users));
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]);
}
}
/**
* @group DDC-1651
*/
public function testSetParameterBindingSingleIdentifierObjectConverted()
{
$userC = new CmsUser;
$userC->name = 'Jonathan';
$userC->username = 'jwage';
$userC->status = 'developer';
$this->_em->persist($userC);
$this->_em->flush();
$this->_em->clear();
$q = $this->_em->createQuery("SELECT DISTINCT u from Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1");
$q->setParameter(1, $userC);
$this->assertEquals($userC->id, $q->getParameter(1));
}
}

View File

@@ -15,9 +15,12 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
parent::setUp();
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\ReadOnlyEntity'),
));
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\ReadOnlyEntity'),
));
} catch(\Exception $e) {
}
}
public function testReadOnlyEntityNeverChangeTracked()
@@ -36,6 +39,36 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals("Test1", $dbReadOnly->name);
$this->assertEquals(1234, $dbReadOnly->numericValue);
}
/**
* @group DDC-1659
*/
public function testClearReadOnly()
{
$readOnly = new ReadOnlyEntity("Test1", 1234);
$this->_em->persist($readOnly);
$this->_em->flush();
$this->_em->getUnitOfWork()->markReadOnly($readOnly);
$this->_em->clear();
$this->assertFalse($this->_em->getUnitOfWork()->isReadOnly($readOnly));
}
/**
* @group DDC-1659
*/
public function testClearEntitiesReadOnly()
{
$readOnly = new ReadOnlyEntity("Test1", 1234);
$this->_em->persist($readOnly);
$this->_em->flush();
$this->_em->getUnitOfWork()->markReadOnly($readOnly);
$this->_em->clear(get_class($readOnly));
$this->assertFalse($this->_em->getUnitOfWork()->isReadOnly($readOnly));
}
}
/**
@@ -58,4 +91,4 @@ class ReadOnlyEntity
$this->name = $name;
$this->numericValue = $number;
}
}
}

View File

@@ -6,6 +6,7 @@ use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Proxy\ProxyClassGenerator;
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
use Doctrine\Tests\Models\Company\CompanyAuction;
require_once __DIR__ . '/../../TestInit.php';
@@ -39,6 +40,18 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
return $product->getId();
}
public function createAuction()
{
$event = new CompanyAuction();
$event->setData('Doctrine Cookbook');
$this->_em->persist($event);
$this->_em->flush();
$this->_em->clear();
return $event->getId();
}
public function testLazyLoadsFieldValuesFromDatabase()
{
$id = $this->createProduct();
@@ -161,6 +174,21 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy.");
}
/**
* @group DDC-1625
*/
public function testDoNotInitializeProxyOnGettingTheIdentifier_DDC_1625()
{
$id = $this->createAuction();
/* @var $entity Doctrine\Tests\Models\Company\CompanyAuction */
$entity = $this->_em->getReference('Doctrine\Tests\Models\Company\CompanyAuction' , $id);
$this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
$this->assertEquals($id, $entity->getId());
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy when extending.");
}
public function testDoNotInitializeProxyOnGettingTheIdentifierAndReturnTheRightType()
{
$product = new ECommerceProduct();
@@ -195,4 +223,28 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('Doctrine Cookbook', $entity->getName());
$this->assertTrue($entity->__isInitialized__, "Getting something other than the identifier initializes the proxy.");
}
/**
* @group DDC-1604
*/
public function testCommonPersistenceProxy()
{
$id = $this->createProduct();
/* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */
$entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id);
$className = \Doctrine\Common\Util\ClassUtils::getClass($entity);
$this->assertInstanceOf('Doctrine\Common\Persistence\Proxy', $entity);
$this->assertFalse($entity->__isInitialized());
$this->assertEquals('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $className);
$restName = str_replace($this->_em->getConfiguration()->getProxyNamespace(), "", get_class($entity));
$restName = substr(get_class($entity), strlen($this->_em->getConfiguration()->getProxyNamespace()) +1);
$proxyFileName = $this->_em->getConfiguration()->getProxyDir() . DIRECTORY_SEPARATOR . str_replace("\\", "", $restName) . ".php";
$this->assertTrue(file_exists($proxyFileName), "Proxy file name cannot be found generically.");
$entity->__load();
$this->assertTrue($entity->__isInitialized());
}
}

View File

@@ -27,16 +27,14 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
$tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes);
$this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]);
$this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]);
$this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]);
$this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) ENGINE = InnoDB", $sql[2]);
$this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) ENGINE = InnoDB", $sql[3]);
$this->assertEquals("ALTER TABLE cms_addresses ADD CONSTRAINT FK_ACAC157BA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[4]);
$this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id)", $sql[5]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[6]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id)", $sql[7]);
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[8]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[5]);
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[6]);
$this->assertEquals(9, count($sql));
$this->assertEquals(7, count($sql));
}
public function testGetCreateSchemaSql2()
@@ -64,4 +62,31 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(1, count($sql));
$this->assertEquals("CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]);
}
/**
* @group DBAL-204
*/
public function testGetCreateSchemaSql4()
{
$classes = array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\\MysqlSchemaNamespacedEntity')
);
$tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes);
$this->assertEquals(0, count($sql));
}
}
/**
* @Entity
* @Table("namespace.entity")
*/
class MysqlSchemaNamespacedEntity
{
/** @Column(type="integer") @Id @GeneratedValue */
public $id;
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Tools\SchemaValidator;
/**
* Test the validity of all modelsets
*
* @group DDC-1601
*/
class SchemaValidatorTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
static public function dataValidateModelSets()
{
$modelSets = array();
foreach (self::$_modelSets as $modelSet => $classes) {
if ($modelSet == "customtype") {
continue;
}
$modelSets[] = array($modelSet);
}
return $modelSets;
}
/**
* @dataProvider dataValidateModelSets
*/
public function testValidateModelSets($modelSet)
{
$validator = new SchemaValidator($this->_em);
$classes = array();
foreach (self::$_modelSets[$modelSet] as $className) {
$classes[] = $this->_em->getClassMetadata($className);
}
foreach ($classes as $class) {
$ce = $validator->validateClass($class);
foreach ($ce as $key => $error) {
if (strpos($error, "must be private or protected. Public fields may break lazy-loading.") !== false) {
unset($ce[$key]);
}
}
$this->assertEquals(0, count($ce), "Invalid Modelset: " . $modelSet . " class " . $class->name . ": ". implode("\n", $ce));
}
}
}

View File

@@ -433,4 +433,32 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($this->article1->id(), $refRep->source()->id());
$this->assertEquals($this->article2->id(), $refRep->target()->id());
}
/**
* @group DDC-1652
*/
public function testArrayHydrationWithCompositeKey()
{
$dql = "SELECT r,s,t FROM Doctrine\Tests\Models\DDC117\DDC117Reference r INNER JOIN r.source s INNER JOIN r.target t";
$before = count($this->_em->createQuery($dql)->getResult());
$this->article1 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article1->id());
$this->article2 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article2->id());
$this->reference = new DDC117Reference($this->article2, $this->article1, "Test-Description");
$this->_em->persist($this->reference);
$this->reference = new DDC117Reference($this->article1, $this->article1, "Test-Description");
$this->_em->persist($this->reference);
$this->reference = new DDC117Reference($this->article2, $this->article2, "Test-Description");
$this->_em->persist($this->reference);
$this->_em->flush();
$dql = "SELECT r,s,t FROM Doctrine\Tests\Models\DDC117\DDC117Reference r INNER JOIN r.source s INNER JOIN r.target t";
$data = $this->_em->createQuery($dql)->getArrayResult();
$this->assertEquals($before + 3, count($data));
}
}

View File

@@ -36,7 +36,7 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase
->getQuery();
$this->assertEquals('SELECT o.id, o.date, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date ORDER BY o.id ASC', $query->getDQL());
$this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, COUNT(d1_.id) AS sclr2 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at ORDER BY d0_.order_id ASC', $query->getSQL());
$this->assertSQLEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, COUNT(d1_.id) AS sclr2 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at ORDER BY d0_.order_id ASC', $query->getSQL());
$result = $query->getResult();
@@ -68,7 +68,7 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date, o.status ORDER BY o.id ASC', $query->getDQL());
$this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL());
$this->assertSQLEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL());
$result = $query->getResult();
@@ -97,7 +97,7 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o ORDER BY o.id ASC', $query->getDQL());
$this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL());
$this->assertSQLEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL());
$result = $query->getResult();
@@ -294,4 +294,4 @@ class DDC1430OrderProduct
$this->value = $value;
}
}
}

View File

@@ -57,6 +57,8 @@ class DDC1514Test extends \Doctrine\Tests\OrmFunctionalTestCase
$dql = "SELECT a, b, ba, c FROM " . __NAMESPACE__ . "\DDC1514EntityA AS a LEFT JOIN a.entitiesB AS b LEFT JOIN b.entityATo AS ba LEFT JOIN a.entityC AS c";
$results = $this->_em->createQuery($dql)->getResult();
$this->assertInternalType('array', $results);
$this->assertTrue(count($results) >= 1, "At least one result expected in array");
$this->assertEquals($c->title, $results[1]->entityC->title);
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
/**
* @group DDC-1526
*/
class DDC1526Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1526Menu'),
));
}
public function testIssue()
{
$parents = array();
for ($i = 0; $i < 9; $i++) {
$entity = new DDC1526Menu;
if (isset ($parents[($i % 3)])) {
$entity->parent = $parents[($i%3)];
}
$this->_em->persist($entity);
$parents[$i] = $entity;
}
$this->_em->flush();
$this->_em->clear();
$dql = "SELECT m, c
FROM " . __NAMESPACE__ . "\DDC1526Menu m
LEFT JOIN m.children c";
$menus = $this->_em->createQuery($dql)->getResult();
// All Children collection now have to be initiailzed
foreach ($menus as $menu) {
$this->assertTrue($menu->children->isInitialized());
}
}
}
/**
* @Entity
*/
class DDC1526Menu
{
/**
* @Column(type="integer")
* @Id
* @GeneratedValue
*/
public $id;
/**
* @ManyToOne(targetEntity="DDC1526Menu", inversedBy="children")
*/
public $parent;
/**
* @OneToMany(targetEntity="DDC1526Menu", mappedBy="parent")
*/
public $children;
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Tests\Models\CMS\CmsComment;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsUser;
/**
* @group DDC-1594
*/
class DDC1594Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
$this->useModelSet('cms');
parent::setUp();
}
public function testIssue()
{
$user = new CmsUser();
$user->status = 'foo';
$user->username = 'foo';
$user->name = 'foo';
$this->_em->persist($user);
$this->_em->flush();
$this->_em->clear();
$detachedUser = clone $user;
$detachedUser->name = 'bar';
$detachedUser->status = 'bar';
$newUser = $this->_em->getReference(get_class($user), $user->id);
$mergedUser = $this->_em->merge($detachedUser);
$this->assertNotSame($mergedUser, $detachedUser);
$this->assertEquals('bar', $detachedUser->getName());
$this->assertEquals('bar', $mergedUser->getName());
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
/**
* @group DDC-1595
* @group DDC-1596
*/
class DDC1595Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();
$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\DebugStack);
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1595BaseInheritance'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1595InheritedEntity1'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1595InheritedEntity2'),
));
}
public function testIssue()
{
$e1 = new DDC1595InheritedEntity1();
$this->_em->persist($e1);
$this->_em->flush();
$this->_em->clear();
$sqlLogger = $this->_em->getConnection()->getConfiguration()->getSQLLogger();
$repository = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1595InheritedEntity1');
$entity1 = $repository->find($e1->id);
// DDC-1596
$this->assertSQLEquals(
"SELECT t0.id AS id1, t0.type FROM base t0 WHERE t0.id = ? AND t0.type IN ('Entity1')",
$sqlLogger->queries[count($sqlLogger->queries)]['sql']
);
$entities = $entity1->getEntities()->getValues();
$this->assertSQLEquals(
"SELECT t0.id AS id1, t0.type FROM base t0 INNER JOIN entity1_entity2 ON t0.id = entity1_entity2.item WHERE entity1_entity2.parent = ? AND t0.type IN ('Entity2')",
$sqlLogger->queries[count($sqlLogger->queries)]['sql']
);
$this->_em->clear();
$entity1 = $repository->find($e1->id);
$entities = $entity1->getEntities()->count();
$this->assertSQLEquals(
"SELECT COUNT(*) FROM entity1_entity2 t WHERE parent = ?",
$sqlLogger->queries[count($sqlLogger->queries)]['sql']
);
}
}
/**
* @Entity
* @Table(name="base")
*
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="type", type="string")
* @DiscriminatorMap({
* "Entity1" = "DDC1595InheritedEntity1",
* "Entity2" = "DDC1595InheritedEntity2"
* })
*/
abstract class DDC1595BaseInheritance
{
/**
* @Id @GeneratedValue
* @Column(type="integer")
*
* @var integer
*/
public $id;
}
/**
* @Entity
* @Table(name="entity1")
*/
class DDC1595InheritedEntity1 extends DDC1595BaseInheritance
{
/**
* @ManyToMany(targetEntity="DDC1595InheritedEntity2", fetch="EXTRA_LAZY")
* @JoinTable(name="entity1_entity2",
* joinColumns={@JoinColumn(name="parent", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="item", referencedColumnName="id")}
* )
*/
protected $entities;
public function getEntities()
{
return $this->entities;
}
}
/**
* @Entity
* @Table(name="entity2")
*/
class DDC1595InheritedEntity2 extends DDC1595BaseInheritance
{
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsGroup;
/**
* @group DDC-1643
*/
class DDC1643Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
private $user1;
private $user2;
public function setUp()
{
$this->useModelSet('cms');
parent::setUp();
$user1 = new CmsUser();
$user1->username = "beberlei";
$user1->name = "Benjamin";
$user1->status = "active";
$group1 = new CmsGroup();
$group1->name = "test";
$group2 = new CmsGroup();
$group2->name = "test";
$user1->addGroup($group1);
$user1->addGroup($group2);
$user2 = new CmsUser();
$user2->username = "romanb";
$user2->name = "Roman";
$user2->status = "active";
$this->_em->persist($user1);
$this->_em->persist($user2);
$this->_em->persist($group1);
$this->_em->persist($group2);
$this->_em->flush();
$this->_em->clear();
$this->user1 = $this->_em->find(get_class($user1), $user1->id);
$this->user2 = $this->_em->find(get_class($user1), $user2->id);
}
public function testClonePersistentCollectionAndReuse()
{
$user1 = $this->user1;
$user1->groups = clone $user1->groups;
$this->_em->flush();
$this->_em->clear();
$user1 = $this->_em->find(get_class($user1), $user1->id);
$this->assertEquals(2, count($user1->groups));
}
public function testClonePersistentCollectionAndShare()
{
$user1 = $this->user1;
$user2 = $this->user2;
$user2->groups = clone $user1->groups;
$this->_em->flush();
$this->_em->clear();
$user1 = $this->_em->find(get_class($user1), $user1->id);
$user2 = $this->_em->find(get_class($user1), $user2->id);
$this->assertEquals(2, count($user1->groups));
$this->assertEquals(2, count($user2->groups));
}
public function testCloneThenDirtyPersistentCollection()
{
$user1 = $this->user1;
$user2 = $this->user2;
$group3 = new CmsGroup();
$group3->name = "test";
$user2->groups = clone $user1->groups;
$user2->groups->add($group3);
$this->_em->persist($group3);
$this->_em->flush();
$this->_em->clear();
$user1 = $this->_em->find(get_class($user1), $user1->id);
$user2 = $this->_em->find(get_class($user1), $user2->id);
$this->assertEquals(3, count($user2->groups));
$this->assertEquals(2, count($user1->groups));
}
public function testNotCloneAndPassAroundFlush()
{
$user1 = $this->user1;
$user2 = $this->user2;
$group3 = new CmsGroup();
$group3->name = "test";
$user2->groups = $user1->groups;
$user2->groups->add($group3);
$this->assertEQuals(1, count($user1->groups->getInsertDiff()));
$this->_em->persist($group3);
$this->_em->flush();
$this->_em->clear();
$user1 = $this->_em->find(get_class($user1), $user1->id);
$user2 = $this->_em->find(get_class($user1), $user2->id);
$this->assertEquals(3, count($user2->groups));
$this->assertEquals(3, count($user1->groups));
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
/**
* @group DDC-1654
*/
class DDC1654Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();
$this->setUpEntitySchema(array(
__NAMESPACE__ . '\\DDC1654Post',
__NAMESPACE__ . '\\DDC1654Comment',
));
}
public function testManyToManyRemoveFromCollectionOrphanRemoval()
{
$post = new DDC1654Post();
$post->comments[] = new DDC1654Comment();
$post->comments[] = new DDC1654Comment();
$this->_em->persist($post);
$this->_em->flush();
$post->comments->remove(0);
$post->comments->remove(1);
$this->_em->flush();
$this->_em->clear();
$comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll();
$this->assertEquals(0, count($comments));
}
public function testManyToManyRemoveElementFromCollectionOrphanRemoval()
{
$post = new DDC1654Post();
$post->comments[] = new DDC1654Comment();
$post->comments[] = new DDC1654Comment();
$this->_em->persist($post);
$this->_em->flush();
$post->comments->removeElement($post->comments[0]);
$post->comments->removeElement($post->comments[1]);
$this->_em->flush();
$this->_em->clear();
$comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll();
$this->assertEquals(0, count($comments));
}
public function testManyToManyClearCollectionOrphanRemoval()
{
$post = new DDC1654Post();
$post->comments[] = new DDC1654Comment();
$post->comments[] = new DDC1654Comment();
$this->_em->persist($post);
$this->_em->flush();
$post->comments->clear();
$this->_em->flush();
$this->_em->clear();
$comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll();
$this->assertEquals(0, count($comments));
}
}
/**
* @Entity
*/
class DDC1654Post
{
/**
* @Id @Column(type="integer") @GeneratedValue
*/
public $id;
/**
* @ManyToMany(targetEntity="DDC1654Comment", orphanRemoval=true,
* cascade={"persist"})
*/
public $comments = array();
}
/**
* @Entity
*/
class DDC1654Comment
{
/**
* @Id @Column(type="integer") @GeneratedValue
*/
public $id;
}

View File

@@ -0,0 +1,144 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
/**
* @group DDC-1655
* @group DDC-1640
* @group DDC-1556
*/
class DDC1655Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Foo'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Bar'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Baz'),
));
} catch(\Exception $e) {
}
}
public function testPostLoadOneToManyInheritance()
{
$cm = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1655Foo');
$this->assertEquals(array("postLoad" => array("postLoad")), $cm->lifecycleCallbacks);
$cm = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1655Bar');
$this->assertEquals(array("postLoad" => array("postLoad", "postSubLoaded")), $cm->lifecycleCallbacks);
$baz = new DDC1655Baz();
$foo = new DDC1655Foo();
$foo->baz = $baz;
$bar = new DDC1655Bar();
$bar->baz = $baz;
$this->_em->persist($foo);
$this->_em->persist($bar);
$this->_em->persist($baz);
$this->_em->flush();
$this->_em->clear();
$baz = $this->_em->find(get_class($baz), $baz->id);
foreach ($baz->foos as $foo) {
$this->assertEquals(1, $foo->loaded, "should have loaded callback counter incremented for " . get_class($foo));
}
}
/**
* Check that post load is not executed several times when the entity
* is rehydrated again although its already known.
*/
public function testPostLoadInheritanceChild()
{
$bar = new DDC1655Bar();
$this->_em->persist($bar);
$this->_em->flush();
$this->_em->clear();
$bar = $this->_em->find(get_class($bar), $bar->id);
$this->assertEquals(1, $bar->loaded);
$this->assertEquals(1, $bar->subLoaded);
$bar = $this->_em->find(get_class($bar), $bar->id);
$this->assertEquals(1, $bar->loaded);
$this->assertEquals(1, $bar->subLoaded);
$dql = "SELECT b FROM " . __NAMESPACE__ . "\DDC1655Bar b WHERE b.id = ?1";
$bar = $this->_em->createQuery($dql)->setParameter(1, $bar->id)->getSingleResult();
$this->assertEquals(1, $bar->loaded);
$this->assertEquals(1, $bar->subLoaded);
$this->_em->refresh($bar);
$this->assertEquals(2, $bar->loaded);
$this->assertEquals(2, $bar->subLoaded);
}
}
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorMap({
* "foo" = "DDC1655Foo",
* "bar" = "DDC1655Bar"
* })
* @HasLifecycleCallbacks
*/
class DDC1655Foo
{
/** @Id @GeneratedValue @Column(type="integer") */
public $id;
public $loaded = 0;
/**
* @ManyToOne(targetEntity="DDC1655Baz", inversedBy="foos")
*/
public $baz;
/**
* @PostLoad
*/
public function postLoad()
{
$this->loaded++;
}
}
/**
* @Entity
* @HasLifecycleCallbacks
*/
class DDC1655Bar extends DDC1655Foo
{
public $subLoaded;
/**
* @PostLoad
*/
public function postSubLoaded()
{
$this->subLoaded++;
}
}
/**
* @Entity
*/
class DDC1655Baz
{
/** @Id @GeneratedValue @Column(type="integer") */
public $id;
/**
* @OneToMany(targetEntity="DDC1655Foo", mappedBy="baz")
*/
public $foos = array();
}

View File

@@ -0,0 +1,158 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
/**
* @group DDC-1695
*/
class DDC1695Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function testIssue()
{
if ($this->_em->getConnection()->getDatabasePlatform()->getName() != "sqlite") {
$this->markTestSkipped("Only with sqlite");
}
$dql = "SELECT n.smallText, n.publishDate FROM " . __NAMESPACE__ . "\\DDC1695News n";
$sql = $this->_em->createQuery($dql)->getSQL();
$this->assertEquals(
'SELECT d0_."SmallText" AS SmallText0, d0_."PublishDate" AS PublishDate1 FROM "DDC1695News" d0_',
$sql
);
}
}
/**
* @Table(name="`DDC1695News`")
* @Entity
*/
class DDC1695News
{
/**
* @var integer $idNews
*
* @Column(name="`IdNews`", type="integer", nullable=false)
* @Id
* @GeneratedValue
*/
private $idNews;
/**
* @var bigint $iduser
*
* @Column(name="`IdUser`", type="bigint", nullable=false)
*/
private $idUser;
/**
* @var integer $idLanguage
*
* @Column(name="`IdLanguage`", type="integer", nullable=false)
*/
private $idLanguage;
/**
* @var integer $idCondition
*
* @Column(name="`IdCondition`", type="integer", nullable=true)
*/
private $idCondition;
/**
* @var integer $idHealthProvider
*
* @Column(name="`IdHealthProvider`", type="integer", nullable=true)
*/
private $idHealthProvider;
/**
* @var integer $idSpeciality
*
* @Column(name="`IdSpeciality`", type="integer", nullable=true)
*/
private $idSpeciality;
/**
* @var integer $idMedicineType
*
* @Column(name="`IdMedicineType`", type="integer", nullable=true)
*/
private $idMedicineType;
/**
* @var integer $idTreatment
*
* @Column(name="`IdTreatment`", type="integer", nullable=true)
*/
private $idTreatment;
/**
* @var string $title
*
* @Column(name="`Title`", type="string", nullable=true)
*/
private $title;
/**
* @var string $smallText
*
* @Column(name="`SmallText`", type="string", nullable=true)
*/
private $smallText;
/**
* @var string $longText
*
* @Column(name="`LongText`", type="string", nullable=true)
*/
private $longText;
/**
* @var datetimetz $publishDate
*
* @Column(name="`PublishDate`", type="datetimetz", nullable=true)
*/
private $publishDate;
/**
* @var tsvector $idxNews
*
* @Column(name="`IdxNews`", type="tsvector", nullable=true)
*/
private $idxNews;
/**
* @var boolean $highlight
*
* @Column(name="`Highlight`", type="boolean", nullable=false)
*/
private $highlight;
/**
* @var integer $order
*
* @Column(name="`Order`", type="integer", nullable=false)
*/
private $order;
/**
* @var boolean $deleted
*
* @Column(name="`Deleted`", type="boolean", nullable=false)
*/
private $deleted;
/**
* @var boolean $active
*
* @Column(name="`Active`", type="boolean", nullable=false)
*/
private $active;
/**
* @var boolean $updateToHighlighted
*
* @Column(name="`UpdateToHighlighted`", type="boolean", nullable=true)
*/
private $updateToHighlighted;
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
/**
* @group DDC-1778
*/
class DDC1778Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
private $user;
private $phone;
public function setUp()
{
$this->useModelSet('cms');
parent::setUp();
$this->user = new CmsUser();
$this->user->username = "beberlei";
$this->user->name = "Benjamin";
$this->user->status = "active";
$this->phone = new CmsPhoneNumber();
$this->phone->phonenumber = '0123456789';
$this->user->addPhoneNumber($this->phone);
$this->_em->persist($this->user);
$this->_em->persist($this->phone);
$this->_em->flush();
$this->_em->clear();
$this->user = $this->_em->find('Doctrine\\Tests\\Models\\CMS\\CmsUser', $this->user->getId());
$this->phone = $this->_em->find('Doctrine\\Tests\\Models\\CMS\\CmsPhonenumber', $this->phone->phonenumber);
}
public function testClear()
{
$clonedNumbers = clone $this->user->getPhonenumbers();
$clonedNumbers->clear();
$this->_em->flush();
$this->_em->clear();
$this->user = $this->_em->find('Doctrine\\Tests\\Models\\CMS\\CmsUser', $this->user->getId());
$this->assertCount(1, $this->user->getPhonenumbers());
}
public function testRemove()
{
$clonedNumbers = clone $this->user->getPhonenumbers();
$clonedNumbers->remove(0);
$this->_em->flush();
$this->_em->clear();
$this->user = $this->_em->find('Doctrine\\Tests\\Models\\CMS\\CmsUser', $this->user->getId());
$this->assertCount(1, $this->user->getPhonenumbers());
}
public function testRemoveElement()
{
$clonedNumbers = clone $this->user->getPhonenumbers();
$clonedNumbers->removeElement($this->phone);
$this->_em->flush();
$this->_em->clear();
$this->user = $this->_em->find('Doctrine\\Tests\\Models\\CMS\\CmsUser', $this->user->getId());
$this->assertCount(1, $this->user->getPhonenumbers());
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
require_once __DIR__ . '/../../../TestInit.php';
use Doctrine\Tests\Models\Generic\DateTimeModel;
/**
* @group DDC-657
*/
class DDC657Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
const NS = 'Doctrine\Tests\Models\Generic';
protected function setUp()
{
$this->useModelSet('generic');
parent::setUp();
$this->loadFixtures();
}
public function testEntitySingleResult()
{
$query = $this->_em->createQuery('SELECT d FROM ' . self::NS . '\DateTimeModel d');
$datetime = $query->setMaxResults(1)->getSingleResult();
$this->assertTrue($datetime instanceof DateTimeModel);
$this->assertTrue($datetime->datetime instanceof \DateTime);
$this->assertTrue($datetime->time instanceof \DateTime);
$this->assertTrue($datetime->date instanceof \DateTime);
}
public function testScalarResult()
{
$query = $this->_em->createQuery('SELECT d.id, d.time, d.date, d.datetime FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC');
$result = $query->getScalarResult();
$this->assertCount(2,$result);
$this->assertContains('11:11:11', $result[0]['time']);
$this->assertContains('2010-01-01', $result[0]['date']);
$this->assertContains('2010-01-01 11:11:11', $result[0]['datetime']);
$this->assertContains('12:12:12', $result[1]['time']);
$this->assertContains('2010-02-02', $result[1]['date']);
$this->assertContains('2010-02-02 12:12:12', $result[1]['datetime']);
}
public function testaTicketEntityArrayResult()
{
$query = $this->_em->createQuery('SELECT d FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC');
$result = $query->getArrayResult();
$this->assertCount(2,$result);
$this->assertTrue($result[0]['datetime'] instanceof \DateTime);
$this->assertTrue($result[0]['time'] instanceof \DateTime);
$this->assertTrue($result[0]['date'] instanceof \DateTime);
$this->assertTrue($result[1]['datetime'] instanceof \DateTime);
$this->assertTrue($result[1]['time'] instanceof \DateTime);
$this->assertTrue($result[1]['date'] instanceof \DateTime);
}
public function testTicketSingleResult()
{
$query = $this->_em->createQuery('SELECT d.id, d.time, d.date, d.datetime FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC');
$datetime = $query->setMaxResults(1)->getSingleResult();
$this->assertTrue(is_array($datetime));
$this->assertTrue($datetime['datetime'] instanceof \DateTime);
$this->assertTrue($datetime['time'] instanceof \DateTime);
$this->assertTrue($datetime['date'] instanceof \DateTime);
}
public function testTicketResult()
{
$query = $this->_em->createQuery('SELECT d.id, d.time, d.date, d.datetime FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC');
$result = $query->getResult();
$this->assertCount(2,$result);
$this->assertTrue($result[0]['time'] instanceof \DateTime);
$this->assertTrue($result[0]['date'] instanceof \DateTime);
$this->assertTrue($result[0]['datetime'] instanceof \DateTime);
$this->assertEquals('2010-01-01 11:11:11', $result[0]['datetime']->format('Y-m-d G:i:s'));
$this->assertTrue($result[1]['time'] instanceof \DateTime);
$this->assertTrue($result[1]['date'] instanceof \DateTime);
$this->assertTrue($result[1]['datetime'] instanceof \DateTime);
$this->assertEquals('2010-02-02 12:12:12', $result[1]['datetime']->format('Y-m-d G:i:s'));
}
public function loadFixtures()
{
$timezone = new \DateTimeZone('America/Sao_Paulo');
$dateTime1 = new DateTimeModel();
$dateTime2 = new DateTimeModel();
$dateTime1->date = new \DateTime('2010-01-01', $timezone);
$dateTime1->time = new \DateTime('2010-01-01 11:11:11', $timezone);
$dateTime1->datetime= new \DateTime('2010-01-01 11:11:11', $timezone);
$dateTime2->date = new \DateTime('2010-02-02', $timezone);
$dateTime2->time = new \DateTime('2010-02-02 12:12:12', $timezone);
$dateTime2->datetime= new \DateTime('2010-02-02 12:12:12', $timezone);
$this->_em->persist($dateTime1);
$this->_em->persist($dateTime2);
$this->_em->flush();
}
}

View File

@@ -8,6 +8,9 @@ require_once __DIR__ . '/../../../TestInit.php';
class DDC742Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
private $userCm;
private $commentCm;
protected function setUp()
{
parent::setUp();
@@ -15,6 +18,7 @@ class DDC742Test extends \Doctrine\Tests\OrmFunctionalTestCase
if (\extension_loaded('memcache')) {
$memcache = new \Memcache();
$memcache->addServer('localhost');
$memcache->flush();
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache($memcache);
@@ -123,4 +127,4 @@ class DDC742Comment
* @var string
*/
public $content;
}
}

View File

@@ -377,6 +377,7 @@ class ClassMetadataBuilderTest extends \Doctrine\Tests\OrmTestCase
array(
'user_id' => 'id',
),
'orphanRemoval' => false,
),
), $this->cm->associationMappings);
}

View File

@@ -9,6 +9,9 @@ require_once __DIR__ . '/../../TestInit.php';
class ClassMetadataLoadEventTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @group DDC-1610
*/
public function testEvent()
{
$em = $this->_getTestEntityManager();
@@ -17,6 +20,8 @@ class ClassMetadataLoadEventTest extends \Doctrine\Tests\OrmTestCase
$evm->addEventListener(Events::loadClassMetadata, $this);
$classMetadata = $metadataFactory->getMetadataFor('Doctrine\Tests\ORM\Mapping\LoadEventTestEntity');
$this->assertTrue($classMetadata->hasField('about'));
$this->assertArrayHasKey('about', $classMetadata->reflFields);
$this->assertInstanceOf('ReflectionProperty', $classMetadata->reflFields['about']);
}
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
@@ -48,4 +53,4 @@ class LoadEventTestEntity
private $name;
private $about;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\ORM\Proxy;
use Doctrine\Tests\OrmTestCase;
use Doctrine\ORM\Proxy\Autoloader;
/**
* @group DDC-1698
*/
class AutoloaderTest extends OrmTestCase
{
static public function dataResolveFile()
{
return array(
array('/tmp', 'MyProxy', 'MyProxy\__CG__\RealClass', '/tmp/__CG__RealClass.php'),
array('/tmp', 'MyProxy\Subdir', 'MyProxy\Subdir\__CG__\RealClass', '/tmp/__CG__RealClass.php'),
array('/tmp', 'MyProxy', 'MyProxy\__CG__\Other\RealClass', '/tmp/__CG__OtherRealClass.php'),
);
}
/**
* @dataProvider dataResolveFile
*/
public function testResolveFile($proxyDir, $proxyNamespace, $className, $expectedProxyFile)
{
$actualProxyFile = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className);
$this->assertEquals($expectedProxyFile, $actualProxyFile);
}
public function testAutoload()
{
if (file_exists(sys_get_temp_dir() ."/AutoloaderTestClass.php")) {
unlink(sys_get_temp_dir() ."/AutoloaderTestClass.php");
}
$autoloader = Autoloader::register(sys_get_temp_dir(), 'ProxyAutoloaderTest', function($proxyDir, $proxyNamespace, $className) {
file_put_contents(sys_get_temp_dir() . "/AutoloaderTestClass.php", "<?php namespace ProxyAutoloaderTest; class AutoloaderTestClass {} ");
});
$this->assertTrue(class_exists('ProxyAutoloaderTest\AutoloaderTestClass', true));
unlink(sys_get_temp_dir() ."/AutoloaderTestClass.php");
}
}

View File

@@ -52,7 +52,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testReferenceProxyDelegatesLoadingToThePersister()
{
$identifier = array('id' => 42);
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
@@ -69,7 +69,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testReferenceProxyExecutesLoadingOnlyOnce()
{
$identifier = array('id' => 42);
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
@@ -108,7 +108,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne()
{
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$this->assertTrue(is_subclass_of($proxyClass, 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'));
}
@@ -125,7 +125,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
require_once dirname(__FILE__)."/fixtures/NonNamespacedProxies.php";
$className = "\DoctrineOrmTestEntity";
$proxyName = "DoctrineOrmTestEntityProxy";
$proxyName = "DoctrineOrmTestEntity";
$classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className);
$classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer'));
@@ -133,16 +133,16 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->_proxyFactory->generateProxyClasses(array($classMetadata));
$classCode = file_get_contents(dirname(__FILE__)."/generated/".$proxyName.".php");
$classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php");
$this->assertNotContains("class DoctrineOrmTestEntityProxy extends \\\\DoctrineOrmTestEntity", $classCode);
$this->assertContains("class DoctrineOrmTestEntityProxy extends \\DoctrineOrmTestEntity", $classCode);
$this->assertNotContains("class DoctrineOrmTestEntity extends \\\\DoctrineOrmTestEntity", $classCode);
$this->assertContains("class DoctrineOrmTestEntity extends \\DoctrineOrmTestEntity", $classCode);
}
public function testClassWithSleepProxyGeneration()
{
$className = "\Doctrine\Tests\ORM\Proxy\SleepClass";
$proxyName = "DoctrineTestsORMProxySleepClassProxy";
$proxyName = "DoctrineTestsORMProxySleepClass";
$classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className);
$classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer'));
@@ -150,11 +150,25 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->_proxyFactory->generateProxyClasses(array($classMetadata));
$classCode = file_get_contents(dirname(__FILE__)."/generated/".$proxyName.".php");
$classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php");
$this->assertEquals(1, substr_count($classCode, 'function __sleep'));
}
/**
* @group DDC-1771
*/
public function testSkipAbstractClassesOnGeneration()
{
$cm = new \Doctrine\ORM\Mapping\ClassMetadata(__NAMESPACE__ . '\\AbstractClass');
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$this->assertNotNull($cm->reflClass);
$num = $this->_proxyFactory->generateProxyClasses(array($cm));
$this->assertEquals(0, $num, "No proxies generated.");
}
public function testNoConfigDir_ThrowsException()
{
$this->setExpectedException('Doctrine\ORM\Proxy\ProxyException');
@@ -183,3 +197,8 @@ class SleepClass
return array('id');
}
}
abstract class AbstractClass
{
}

View File

@@ -336,4 +336,13 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase
$orExpr = $this->_expr->orx();
$orExpr->add($this->_expr->quot(5, 2));
}
}
/**
* @group DDC-1683
*/
public function testBooleanLiteral()
{
$this->assertEquals('true', $this->_expr->literal(true));
$this->assertEquals('false', $this->_expr->literal(false));
}
}

View File

@@ -101,4 +101,28 @@ class QueryTest extends \Doctrine\Tests\OrmTestCase
$q->useResultCache(true);
$this->assertSame($this->_em->getConfiguration()->getResultCacheImpl(), $q->getQueryCacheProfile()->getResultCacheDriver());
}
/**
* @expectedException Doctrine\ORM\Query\QueryException
**/
public function testIterateWithNoDistinctAndWrongSelectClause()
{
$q = $this->_em->createQuery("select u, a from Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a");
$q->iterate();
}
/**
* @expectedException Doctrine\ORM\Query\QueryException
**/
public function testIterateWithNoDistinctAndWithValidSelectClause()
{
$q = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a");
$q->iterate();
}
public function testIterateWithDistinct()
{
$q = $this->_em->createQuery("SELECT DISTINCT u from Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a");
$q->iterate();
}
}

View File

@@ -893,6 +893,20 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
);
}
/**
* @group DDC-1693
* @group locking
*/
public function testLockModeNoneQueryHint()
{
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'",
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ".
"FROM cms_users c0_ WHERE c0_.username = 'gblanco'",
array(Query::HINT_LOCK_MODE => \Doctrine\DBAL\LockMode::NONE)
);
}
/**
* @group DDC-430
*/
@@ -1059,7 +1073,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
{
$this->assertSqlGeneration(
"SELECT t, s, l FROM Doctrine\Tests\Models\DDC117\DDC117Link l INNER JOIN l.target t INNER JOIN l.source s",
"SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id"
"SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3, d2_.source_id AS source_id4, d2_.target_id AS target_id5 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id"
);
}

View File

@@ -732,4 +732,17 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase
$this->assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u', $qb->getDQL());
}
}
/**
* @group DDC-1619
*/
public function testAddDistinct()
{
$qb = $this->_em->createQueryBuilder()
->select('u')
->distinct()
->from('Doctrine\Tests\Models\CMS\CmsUser', 'u');
$this->assertEquals('SELECT DISTINCT u FROM Doctrine\Tests\Models\CMS\CmsUser u', $qb->getDQL());
}
}

View File

@@ -47,6 +47,8 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
$metadata->customRepositoryClassName = $this->_namespace . '\EntityGeneratorBookRepository';
$metadata->table['name'] = 'book';
$metadata->table['uniqueConstraints']['name_uniq'] = array('columns' => array('name'));
$metadata->table['indexes']['status_idx'] = array('columns' => array('status'));
$metadata->mapField(array('fieldName' => 'name', 'type' => 'string'));
$metadata->mapField(array('fieldName' => 'status', 'type' => 'string', 'default' => 'published'));
$metadata->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true));
@@ -99,7 +101,8 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'setAuthor'), "EntityGeneratorBook::setAuthor() missing.");
$this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getAuthor'), "EntityGeneratorBook::getAuthor() missing.");
$this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getComments'), "EntityGeneratorBook::getComments() missing.");
$this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'addEntityGeneratorComment'), "EntityGeneratorBook::addEntityGeneratorComment() missing.");
$this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'addComment'), "EntityGeneratorBook::addComment() missing.");
$this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'removeComment'), "EntityGeneratorBook::removeComment() missing.");
$this->assertEquals('published', $book->getStatus());
@@ -111,9 +114,11 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->assertEquals($author, $book->getAuthor());
$comment = new EntityGeneratorComment();
$book->addEntityGeneratorComment($comment);
$book->addComment($comment);
$this->assertInstanceOf('Doctrine\Common\Collections\ArrayCollection', $book->getComments());
$this->assertEquals(new \Doctrine\Common\Collections\ArrayCollection(array($comment)), $book->getComments());
$book->removeComment($comment);
$this->assertEquals(new \Doctrine\Common\Collections\ArrayCollection(array()), $book->getComments());
}
public function testEntityUpdatingWorks()
@@ -219,6 +224,39 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->assertEquals($classes, array_keys($p->getValue($this->_generator)));
}
/**
* @group DDC-1784
*/
public function testGenerateEntityWithSequenceGenerator()
{
$metadata = new ClassMetadataInfo($this->_namespace . '\DDC1784Entity');
$metadata->namespace = $this->_namespace;
$metadata->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true));
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE);
$metadata->setSequenceGeneratorDefinition(array(
'sequenceName' => 'DDC1784_ID_SEQ',
'allocationSize' => 1,
'initialValue' => 2
));
$this->_generator->writeEntityClass($metadata, $this->_tmpDir);
$filename = $this->_tmpDir . DIRECTORY_SEPARATOR
. $this->_namespace . DIRECTORY_SEPARATOR . 'DDC1784Entity.php';
$this->assertFileExists($filename);
require_once $filename;
$reflection = new \ReflectionProperty($metadata->name, 'id');
$docComment = $reflection->getDocComment();
$this->assertContains('@Id', $docComment);
$this->assertContains('@Column(name="id", type="integer")', $docComment);
$this->assertContains('@GeneratedValue(strategy="SEQUENCE")', $docComment);
$this->assertContains('@SequenceGenerator(sequenceName="DDC1784_ID_SEQ", allocationSize=1, initialValue=2)', $docComment);
}
public function getParseTokensInEntityFileData()
{
return array(

View File

@@ -68,10 +68,10 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
protected function _createMetadataDriver($type, $path)
{
$mappingDriver = array(
'php' => 'PHPDriver',
'php' => 'PHPDriver',
'annotation' => 'AnnotationDriver',
'xml' => 'XmlDriver',
'yaml' => 'YamlDriver',
'xml' => 'XmlDriver',
'yaml' => 'YamlDriver',
);
$this->assertArrayHasKey($type, $mappingDriver, "There is no metadata driver for the type '" . $type . "'.");
$driverName = $mappingDriver[$type];
@@ -190,7 +190,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
* @depends testIdentifierIsExported
* @param ClassMetadataInfo $class
*/
public function testFieldsAreExpored($class)
public function testFieldsAreExported($class)
{
$this->assertTrue(isset($class->fieldMappings['id']['id']) && $class->fieldMappings['id']['id'] === true);
$this->assertEquals('id', $class->fieldMappings['id']['fieldName']);
@@ -211,13 +211,12 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
}
/**
* @depends testFieldsAreExpored
* @depends testFieldsAreExported
* @param ClassMetadataInfo $class
*/
public function testOneToOneAssociationsAreExported($class)
{
$this->assertTrue(isset($class->associationMappings['address']));
//$this->assertInstanceOf('Doctrine\ORM\Mapping\OneToOneMapping', $class->associationMappings['address']);
$this->assertEquals('Doctrine\Tests\ORM\Tools\Export\Address', $class->associationMappings['address']['targetEntity']);
$this->assertEquals('address_id', $class->associationMappings['address']['joinColumns'][0]['name']);
$this->assertEquals('id', $class->associationMappings['address']['joinColumns'][0]['referencedColumnName']);
@@ -233,6 +232,15 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
return $class;
}
/**
* @depends testFieldsAreExported
*/
public function testManyToOneAssociationsAreExported($class)
{
$this->assertTrue(isset($class->associationMappings['mainGroup']));
$this->assertEquals('Doctrine\Tests\ORM\Tools\Export\Group', $class->associationMappings['mainGroup']['targetEntity']);
}
/**
* @depends testOneToOneAssociationsAreExported
* @param ClassMetadataInfo $class

View File

@@ -28,6 +28,11 @@ class User
*/
public $address;
/**
* @ManyToOne(targetEntity="Doctrine\Tests\ORM\Tools\Export\Group")
*/
public $mainGroup;
/**
*
* @OneToMany(targetEntity="Doctrine\Tests\ORM\Tools\Export\Phonenumber", mappedBy="user", cascade={"persist", "merge"}, orphanRemoval=true)
@@ -65,4 +70,4 @@ class User
public function doStuffOnPostPersist()
{
}
}
}

View File

@@ -31,6 +31,10 @@ $metadata->mapField(array(
'columnDefinition' => 'CHAR(32) NOT NULL',
));
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
$metadata->mapManyToOne(array(
'fieldName' => 'mainGroup',
'targetEntity' => 'Doctrine\\Tests\\ORM\Tools\\Export\\Group',
));
$metadata->mapOneToOne(array(
'fieldName' => 'address',
'targetEntity' => 'Doctrine\\Tests\\ORM\\Tools\\Export\\Address',
@@ -102,4 +106,4 @@ $metadata->mapManyToMany(array(
),
),
'orderBy' => NULL,
));
));

View File

@@ -4,9 +4,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\ORM\Tools\Export\User" table="cms_users">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersistToo"/>
@@ -16,15 +16,17 @@
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
<one-to-one field="address" target-entity="Doctrine\Tests\ORM\Tools\Export\Address" inversed-by="user" orphan-removal="true">
<cascade><cascade-persist /></cascade>
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
</one-to-one>
<many-to-one field="mainGroup" target-entity="Doctrine\Tests\ORM\Tools\Export\Group" />
<one-to-many field="phonenumbers" target-entity="Doctrine\Tests\ORM\Tools\Export\Phonenumber" mapped-by="user" orphan-removal="true">
<cascade>
<cascade-persist/>
@@ -34,7 +36,7 @@
<order-by-field name="number" direction="ASC" />
</order-by>
</one-to-many>
<one-to-many field="interests" target-entity="Doctrine\Tests\ORM\Tools\Export\Interests" mapped-by="user" orphan-removal="true">
<cascade>
<cascade-refresh/>
@@ -44,7 +46,7 @@
<cascade-remove/>
</cascade>
</one-to-many>
<many-to-many field="groups" target-entity="Doctrine\Tests\ORM\Tools\Export\Group">
<cascade>
<cascade-all/>
@@ -58,7 +60,7 @@
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
</doctrine-mapping>

View File

@@ -26,6 +26,9 @@ Doctrine\Tests\ORM\Tools\Export\User:
cascade: [ persist ]
inversedBy: user
orphanRemoval: true
manyToOne:
mainGroup:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Group
oneToMany:
phonenumbers:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Phonenumber
@@ -57,4 +60,4 @@ Doctrine\Tests\ORM\Tools\Export\User:
- all
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
postPersist: [ doStuffOnPostPersist ]

View File

@@ -0,0 +1,78 @@
<?php
namespace Doctrine\Tests\ORM\Tools\Pagination;
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\CountWalker;
/**
* @group DDC-1613
*/
class CountWalkerTest extends PaginationTestCase
{
public function testCountQuery()
{
$query = $this->entityManager->createQuery(
'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN p.category c JOIN p.author a');
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
$query->setHint(CountWalker::HINT_DISTINCT, true);
$query->setFirstResult(null)->setMaxResults(null);
$this->assertEquals(
"SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ INNER JOIN Category c1_ ON b0_.category_id = c1_.id INNER JOIN Author a2_ ON b0_.author_id = a2_.id", $query->getSql()
);
}
public function testCountQuery_MixedResultsWithName()
{
$query = $this->entityManager->createQuery(
'SELECT a, sum(a.name) as foo FROM Doctrine\Tests\ORM\Tools\Pagination\Author a');
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
$query->setHint(CountWalker::HINT_DISTINCT, true);
$query->setFirstResult(null)->setMaxResults(null);
$this->assertEquals(
"SELECT count(DISTINCT a0_.id) AS sclr0 FROM Author a0_", $query->getSql()
);
}
public function testCountQuery_KeepsGroupBy()
{
$query = $this->entityManager->createQuery(
'SELECT b FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost b GROUP BY b.id');
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
$query->setHint(CountWalker::HINT_DISTINCT, true);
$query->setFirstResult(null)->setMaxResults(null);
$this->assertEquals(
"SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ GROUP BY b0_.id", $query->getSql()
);
}
public function testCountQuery_RemovesOrderBy()
{
$query = $this->entityManager->createQuery(
'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN p.category c JOIN p.author a ORDER BY a.name');
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
$query->setHint(CountWalker::HINT_DISTINCT, true);
$query->setFirstResult(null)->setMaxResults(null);
$this->assertEquals(
"SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ INNER JOIN Category c1_ ON b0_.category_id = c1_.id INNER JOIN Author a2_ ON b0_.author_id = a2_.id", $query->getSql()
);
}
public function testCountQuery_RemovesLimits()
{
$query = $this->entityManager->createQuery(
'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN p.category c JOIN p.author a');
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
$query->setHint(CountWalker::HINT_DISTINCT, true);
$query->setFirstResult(null)->setMaxResults(null);
$this->assertEquals(
"SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ INNER JOIN Category c1_ ON b0_.category_id = c1_.id INNER JOIN Author a2_ ON b0_.author_id = a2_.id", $query->getSql()
);
}
}

Some files were not shown because too many files have changed in this diff Show More