Compare commits

...

609 Commits
2.0.7 ... 2.1.6

Author SHA1 Message Date
Benjamin Eberlei
e1647229cd Release 2.1.6 2012-01-29 17:53:01 +01:00
Benjamin Eberlei
9111d794d5 Bump build commons 2012-01-29 17:52:52 +01:00
Benjamin Eberlei
34ec1e3248 Bump dev version to 2.1.6 2012-01-29 17:51:45 +01:00
Benjamin Eberlei
3c52fa5ea6 Release 2.1.6 2012-01-29 17:51:45 +01:00
Benjamin Eberlei
b3ed478fb4 Merge remote branch 'origin/2.1.x' into 2.1.x 2012-01-29 16:49:39 +00:00
Benjamin Eberlei
c5973f8019 Bump dependencies 2012-01-29 16:48:50 +00:00
Benjamin Eberlei
6512902eae Maximum version in PEAR packages. 2012-01-29 17:39:28 +01:00
Benjamin Eberlei
798a8bca92 Merge branch 'DDC-1526' into 2.1.x 2012-01-28 12:26:02 +01:00
Benjamin Eberlei
7bca37e827 [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:25:01 +01:00
armetiz
818a5d5904 Update lib/Doctrine/ORM/Tools/SchemaTool.php 2012-01-25 22:40:59 +01:00
Benjamin Eberlei
20ac6f383c Merge branch 'DDC-1619' into 2.1.x 2012-01-25 10:29:13 +01:00
Benjamin Eberlei
60d97e068c [DDC-1619] Add QueryBuilder#distinct 2012-01-25 10:28:24 +01:00
Benjamin Eberlei
94162d1c03 Merge pull request #271 from sonata-project/2.1.x
DDC-1618 - Query::Iterate() with fetch join exception but no associaiton selected
2012-01-24 14:59:31 -08:00
Thomas Rabaix
1c042b5b8d Add SqlWalker::HINT_DISTINCT constant 2012-01-24 23:34:20 +01:00
Thomas Rabaix
b770a6687a Fix DDC-1618 - add more check before throwing an iterateWithFetchJoinNotAllowed exception 2012-01-24 23:16:51 +01:00
Benjamin Eberlei
0abf2484ce Merge branch 'DDC-1603' into 2.1.x 2012-01-21 15:37:48 +01:00
armetiz
7d8aa2213d Unique key name isn't correctly set - DDC-1603 2012-01-21 15:37:20 +01:00
Benjamin Eberlei
12a639d4a2 Merge branch 'DDC-1594' into 2.1.x 2012-01-15 17:46:41 +01:00
Benjamin Eberlei
432a035baa DDC-1594 - Fix problem with merge and an existing managed proxy instance. 2012-01-15 17:46:19 +01:00
Benjamin Eberlei
97df2f8749 Fix notice when using regenerate if exists and file is not new. 2012-01-12 11:21:16 +01:00
Benjamin Eberlei
61aec8fdcd DDC-1589 - Remove mbstring dependency from Composite 2012-01-09 14:34:31 +01:00
Adrien BRAULT
292fbd9756 Fix some PHPDoc @return type. 2011-12-28 08:50:33 +01:00
Benjamin Eberlei
c2112c0584 Fix PEAR Path 2011-12-20 21:56:37 +01:00
Benjamin Eberlei
05c7e0055a Bump dev version to 2.1.6 2011-12-19 19:27:40 +01:00
Benjamin Eberlei
fb7b5c6fb1 Bump dev version to 2.1.5 2011-12-19 19:27:12 +01:00
Benjamin Eberlei
da0e3439ab Release 2.1.5 2011-12-19 19:27:12 +01:00
Benjamin Eberlei
a1df81e18c Merge build-commons back into 2.1.x 2011-12-19 19:19:43 +01:00
Guilherme Blanco
1dea57013c Cherry-pick fix for DDC-1548 2011-12-19 18:49:14 +01:00
Benjamin Eberlei
91b1a25bb2 Add DDC-1545 Test 2011-12-19 18:13:11 +01:00
Benjamin Eberlei
473ba2f04c DDC-1545 - Fix issue with changing values from null to something new.
This issue was introduced by a side-effect in 2.1.3 with
d9f9228d95. In this commit read-only
objects where prevented to be updated. This lead to an invalid check not
being performed in UnitOfWork#computeChangeSet which was present before
where an association that was null would be injected into the
originalEntityData using the UnitOfWork#setOriginalEntityProperty()
method in the AbstractHydrator.

This commit now explicitly sets this field to null using the same API so
that is present during UnitOfWork#computeChangeSet.
2011-12-19 18:10:33 +01:00
Benjamin Eberlei
e80eae4c98 DDC-1514 - Fix complex self-referencing + proxy hydration problem. 2011-12-15 23:00:34 +01:00
Benjamin Eberlei
68184a3803 DDC-1519 - Merge fix 2011-12-15 20:59:11 +01:00
Deni
3412ef935a Fixed typo in the XmlDriver 2011-12-15 20:16:40 +01:00
Benjamin Eberlei
4215a24969 Merge branch 'DDC-1520' into 2.1.x 2011-12-13 21:34:13 +01:00
Benjamin Eberlei
02d8802570 Bugfix in UnitOfWorklib/Doctrine/ORM/UnitOfWork.phptriggerEagerLoads() 2011-12-13 21:32:10 +01:00
Benjamin Eberlei
0e107880cf DDC-1527 - Fix bug with complex type handling in repositories 2011-12-12 16:20:46 +01:00
Guilherme Blanco
3b259dcb42 Merge pull request #215 from andrewmackrodt/2.1.x
2.1.x
2011-12-11 13:00:41 -08:00
Andrew Mackrodt
d87e90d392 Added absolute namespace paths to phpdoc annotations - issue DDC-1025. 2011-12-11 20:52:29 +00:00
Benjamin Eberlei
c1ad170bed DDC-1515 - Now the real bugfix 2011-12-11 20:22:11 +01:00
Benjamin Eberlei
565962ec8b Revert "DDC-1515 - Bugfix in hydration of dependant entities without additional fields."
This reverts commit d9b39f6fce.
2011-12-11 16:34:34 +01:00
Benjamin Eberlei
eb9812feb5 Merge branch 'DDC-1515' into 2.1.x 2011-12-11 16:02:13 +01:00
Benjamin Eberlei
d9b39f6fce DDC-1515 - Bugfix in hydration of dependant entities without additional fields. 2011-12-11 16:01:36 +01:00
Benjamin Eberlei
b1db2ad0d8 Merge branch 'DDC-1512' into 2.1.x 2011-11-28 11:18:45 +01:00
Benjamin Eberlei
92b3d0a383 DDC-1512 - Adjustment for 2.1.x branch 2011-11-28 11:18:30 +01:00
Benjamin Eberlei
fc8997a935 DDC-1512 - Make ClassMetadataFactory::isTransient() entity namespace aware. 2011-11-28 11:17:30 +01:00
Benjamin Eberlei
88d937cc89 Bump Dev Version to 2.1.5-DEV 2011-11-23 19:44:51 +00:00
Benjamin Eberlei
bff55577cd Release 2.1.4 2011-11-23 19:44:26 +00:00
Benjamin Eberlei
7ff30e273a Merge branch 'DDC-1509' into 2.1.x 2011-11-23 20:38:58 +01:00
Benjamin Eberlei
9c81989349 DDC-1509 - Fix regression in doMerge() introduced with the DDC-1383 bugfix 2011-11-23 20:37:37 +01:00
Benjamin Eberlei
e0bb84e1da Bump Dev Version to 2.1.4-DEV 2011-11-21 15:08:18 +00:00
Benjamin Eberlei
692f423047 Release 2.1.3 2011-11-21 15:08:06 +00:00
Benjamin Eberlei
0814e8abc7 Bump dependencies to Common 2.1.3 and DBAL 2.1.5 2011-11-21 16:05:25 +01:00
Benjamin Eberlei
d4a1ea8995 Merge branch 'DDC-1500' into 2.1.x 2011-11-21 15:06:15 +01:00
Benjamin Eberlei
c2febabdd0 DDC-1500 - Fix potential security problem in EntityRepository ORDER BY orientations 2011-11-21 15:05:43 +01:00
Benjamin Eberlei
633e342d82 Make 2.1.x forwards compatible with Common 2.2-DEV 2011-11-19 13:06:01 +01:00
Guilherme Blanco
2e7318dcc2 Fixed DDC-1492. 2011-11-19 08:17:02 +01:00
Benjamin Eberlei
6b3edbbbf9 Merge branch 'DBAL-171' into 2.1.x 2011-11-18 17:45:14 +01:00
Benjamin Eberlei
fdcb46e780 DBAL-171 - Fix bug where params where resorted but types where not in DQL Query 2011-11-18 17:44:42 +01:00
Benjamin Eberlei
6da0314deb Merge branch 'DDC-1496' into 2.1.x 2011-11-18 15:45:04 +01:00
Benjamin Eberlei
b61d109fe2 DDC-1496 - Fix bug with OneToMany collections having orphanRemoval=true and Collection#clear() being called. 2011-11-18 15:44:36 +01:00
Benjamin Eberlei
960c94f661 Fix side-effect test failures in functional tests on PostgreSQL 2011-11-18 14:11:08 +01:00
Benjamin Eberlei
515b0205c6 Merge branch 'DDC-1452' into 2.1.x 2011-11-18 14:04:15 +01:00
Benjamin Eberlei
d76c5b11f6 onUpdate doesnt exist anymore 2011-11-18 14:04:05 +01:00
Benjamin Eberlei
88043577a4 DDC-1452 - Backport fix 2011-11-18 14:02:21 +01:00
Benjamin Eberlei
d0b9f7cb2b Generalize config files 2011-11-13 22:42:46 +01:00
Benjamin Eberlei
10a3f5e10e Each branch needs a .travis.yml 2011-11-13 22:39:27 +01:00
Alexander
eeddaecd26 Fixed failing tests in PHPUnit 3.6.2 (expecting \Exception was deprecated) 2011-11-13 22:27:15 +01:00
Benjamin Eberlei
50f7f3a48e Fix assertType() to assertInstanceOf() 2011-11-13 22:26:51 +01:00
Benjamin Eberlei
e94a293446 Remove merge artifact (funny this went into the docblock, not affecting the tests at all) 2011-11-13 17:28:36 +01:00
Benjamin Eberlei
08e5e47780 Merge branch 'thiagofesta-master' into 2.1.x 2011-11-13 17:19:58 +01:00
Benjamin Eberlei
e3a5ffbb55 DDC-1477 - Adjust patch to really fix bug in Proxy generation 2011-11-13 17:19:29 +01:00
Benjamin Eberlei
bf06c3418b Merge branch 'DDC-1458' into 2.1.x 2011-11-11 08:19:48 +01:00
Alexander
6a8172902a Cleanup 2011-11-11 08:16:44 +01:00
Benjamin Eberlei
d9328ea5e7 Autoload to composer.json 2011-11-01 15:06:42 +01:00
Benjamin Eberlei
8cd8c993c8 Merge branch 'DBAL-1420' into 2.1.x 2011-10-31 23:13:59 +01:00
Benjamin Eberlei
f45d8f488b DBAL-1420 - Use safe mode for schema validation. Dropping stuff isnt necessary here. 2011-10-31 23:13:51 +01:00
Benjamin Eberlei
6257daf097 Fix xml fix again 2011-10-31 22:14:46 +01:00
Benjamin Eberlei
d46910f6bb Merge branch 'XML-IndexBy-Fix' into 2.1.x 2011-10-31 22:11:51 +01:00
Guilherme Blanco
5c4e3d9bd0 Fixed bug in XMLDriver where relation indexes are treathed as elements but documented as attributes. 2011-10-31 22:11:21 +01:00
Benjamin Eberlei
7d0d06cd9a Merge branch 'DDC-1346' into 2.1.x 2011-10-31 21:56:04 +01:00
Guilherme Blanco
e4f49231bf Added support for ResultVariable referencing in ArithmeticPrimary. Fixes DDC-1346. 2011-10-31 21:55:27 +01:00
Benjamin Eberlei
ca600f466e Merge branch 'DDC-1316' into 2.1.x 2011-10-31 21:48:23 +01:00
Guilherme Blanco
01d6b73f78 Small changes for code readability. Added type binding in JoinedSubclassPersister, which was missing. Fixes DDC-1316. 2011-10-31 21:47:45 +01:00
Benjamin Eberlei
c36b1f1d00 Merge branch 'DDC-1462' into 2.1.x 2011-10-31 21:35:38 +01:00
Benjamin Eberlei
588f7fc765 DDC-1462 - Fix bug in slice when calling on a dirty collection that is marked extra lazy 2011-10-31 21:34:53 +01:00
Benjamin Eberlei
1becb8dfd5 Merge branch 'DDC-1399' into 2.1.x 2011-10-31 21:17:56 +01:00
Benjamin Eberlei
394447d92d DDC-1399 - Fix extra lazy collections when inner collection contains values but persistent collection is marked not dirty because of flush() 2011-10-31 21:17:24 +01:00
Benjamin Eberlei
f834c61a2b Merge branch 'DDC-1435' into 2.1.x 2011-10-29 12:08:00 +02:00
Guilherme Blanco
67cf5b7160 Implemented support to entities with association marked as @Id support in many situations. Fixed DDC-1435. 2011-10-29 12:07:32 +02:00
Benjamin Eberlei
e3a04c3458 Merge branch 'DQLFixes' into 2.1.x 2011-10-29 11:56:55 +02:00
Benjamin Eberlei
cb7e27460e DDC-1079 - SimpleSelectExpression related fixes and refactorings in SqlWalker 2011-10-29 11:55:16 +02:00
Benjamin Eberlei
9dc5c5ddce Merge branch 'DDC-1410' into 2.1.x 2011-10-28 23:47:25 +02:00
Benjamin Eberlei
bbd01ea457 DDC-1410 - Remove code-inlining that caused problems 2011-10-28 23:45:57 +02:00
Benjamin Eberlei
360c578c5d DDC-1454 - Fix exists() with Joined table inheritance 2011-10-28 00:59:25 +02:00
Benjamin Eberlei
ca289bf1ea Merge branch 'DDC-1358' into 2.1.x 2011-10-16 11:17:53 +02:00
Benjamin Eberlei
17341cf051 DDC-1358 - Fix bug where multiple NULL root entity combined with scalar results will break the object and array hydrator.
This case likeli only occurs when doing native queries. A guard clause that prevents hydration from breaking
when RIGHT JOIN queries with null root entities appear has been added aswell.
2011-10-16 11:17:25 +02:00
Benjamin Eberlei
5638b8c5c0 Add tests for DDC-1389 to 2.1.x 2011-10-16 09:37:22 +02:00
Benjamin Eberlei
ccaf00cfab Merge remote-tracking branch 'origin/2.1.x' into 2.1.x 2011-10-15 19:58:54 +02:00
Benjamin Eberlei
b57f4ecb52 Merge pull request #123 from lenar/2.1.x
Fix couple of bugs with composite keys
2011-10-15 10:58:11 -07:00
Benjamin Eberlei
7ae96dd9c7 Merge branch 'DDC-1383' into 2.1.x 2011-10-15 19:16:04 +02:00
Benjamin Eberlei
0b3b4ecef6 DDC-1383 - Proxy Generation in merge was flawed with inheritance 2011-10-15 19:15:43 +02:00
Benjamin Eberlei
8e3fc30477 Merge branch 'DDC-1411' into 2.1.x 2011-10-15 17:53:47 +02:00
Benjamin Eberlei
db2c51d860 DDC-1411 - Fixed onDelete handling in EntityGenerator 2011-10-15 17:53:25 +02:00
Benjamin Eberlei
b6ac1b4962 Merge branch 'DDC-1414' into 2.1.x 2011-10-15 17:47:48 +02:00
Benjamin Eberlei
7734e3e2ec DDC-1414 - Missing push to $newNodes 2011-10-15 17:47:41 +02:00
Benjamin Eberlei
45494fd6a0 Merge branch 'DDC-1421' into 2.1.x 2011-10-15 17:01:54 +02:00
Benjamin Eberlei
efad33a70e DDC-1421 - Fix potential bug and code-smells 2011-10-15 17:01:39 +02:00
Benjamin Eberlei
ba9feb1519 Fix notice 2011-10-15 16:02:32 +02:00
Benjamin Eberlei
4683d76c52 Remove unncessary line 2011-10-15 15:59:17 +02:00
Benjamin Eberlei
4ddd4a68ef Merge branch 'DDC-1386' into 2.1.x 2011-10-15 15:53:53 +02:00
Benjamin Eberlei
d9f9228d95 Add way to keep track of read only objects in the UnitOfWork which are never updated during flush.
Changed the behavior of EntityManager#getPartialReference to be read-only. No changes are ever
done to this entities. Changed UnitOfWork#computeChangeSet to never create a changeset for
fields that are partially omitted from a DQL or NativeQuery.

To check if an entity is read only use the new API:

    if ($entityManager->getUnitOfWork()->isReadOnly($entity))
2011-10-15 15:53:32 +02:00
Benjamin Eberlei
ef4d8410f6 Merge branch 'DDC-1402' into 2.1.x 2011-10-10 17:55:25 +02:00
Benjamin Eberlei
a62cd45476 Fix DDC-1402 - No caching for SingleTablePersister::_getSelectColumnList 2011-10-10 17:53:53 +02:00
Benjamin Eberlei
7369a2de68 Add composer.json 2011-10-10 17:22:51 +02:00
Benjamin Eberlei
623ab9cee9 Bump Dev Version to 2.1.3-DEV 2011-09-25 18:24:26 +00:00
Benjamin Eberlei
144d0de0ab Release 2.1.2 2011-09-25 18:24:18 +00:00
Benjamin Eberlei
5c6164ce07 Bump Dev Version to 2.1.3-DEV 2011-09-25 18:23:05 +00:00
Benjamin Eberlei
e960ba24eb Release 2.1.2 2011-09-25 18:21:30 +00:00
Benjamin Eberlei
e55b700670 Update dependencies of Common and ORM 2011-09-25 20:20:17 +02:00
Benjamin Eberlei
e4dd5e83e4 Merge branch 'DDC-1381' into 2.1.x 2011-09-25 19:11:00 +02:00
Fabien Pennequin
082c2ded8d Fixed php notice in ClassMetadataFactory 2011-09-25 19:10:32 +02:00
Benjamin Eberlei
7f9cb9567a DDC-1337 - Fix MultiTableDeleteExecutor and MultiTableUpdateExecutor in MySQL transaction case 2011-09-25 19:08:16 +02:00
Benjamin Eberlei
90c50b013d Merge branch 'DDC-1392' into 2.1.x 2011-09-25 18:09:43 +02:00
Benjamin Eberlei
6a05e7f393 DDC-1392 - Fix bug with merging unitialized proxies 2011-09-25 18:09:13 +02:00
Benjamin Eberlei
a8e4d8f52d Merge branch 'DDC-1367' into 2.1.x 2011-09-25 16:41:12 +02:00
Benjamin Eberlei
47314c116c DDC-1367 - Bugfix 2011-09-25 16:40:14 +02:00
Benjamin Eberlei
c2eb6e6104 Merge branch 'DDC-1346' into 2.1.x 2011-09-25 15:11:58 +02:00
Guilherme Blanco
3b6a1e11f3 Added support for ResultVariable referencing in ArithmeticPrimary. Fixes DDC-1346. 2011-09-25 15:11:36 +02:00
Benjamin Eberlei
87650428b2 Merge branch 'DDC-1321' into 2.1.x 2011-09-25 15:07:12 +02:00
Guilherme Blanco
1de27437b5 Fixed bug with orphanRemoval not removing associated Entity on OneToMany and OneToOne relationships. As defined in ClassMatedataInfo, in these situations, when orphanRemoval=true, cascade=remove is implicit. This fixes DDC-1321. 2011-09-25 15:07:00 +02:00
lenar
52c75d1633 use the correct targetEntity 2011-09-14 11:43:37 +03:00
lenar
8875d549eb identifier referencing foreign entity can be defined in parent class too 2011-09-14 11:43:25 +03:00
Benjamin Eberlei
cf10ee4d02 Merge branch 'DDC-1356' into 2.1.x 2011-09-04 14:33:00 +02:00
Guilherme Blanco
1faa3a945e Added support to user provide an array of Entities as a DQL parameter. Fixes DDC-1356. 2011-09-04 14:32:45 +02:00
Benjamin Eberlei
22f37d4178 Merge branch 'DDC-1354' into 2.1.x 2011-09-04 14:30:38 +02:00
Guilherme Blanco
6ad21462fe Fixes DDC-1354. 2011-09-04 14:30:16 +02:00
Benjamin Eberlei
e9c29135f7 Merge branch 'DDC-1350' into 2.1.x 2011-08-30 20:41:10 +02:00
Benjamin Eberlei
12af7d24c0 DDC-1350 - Bugfixes in Doctrine\ORM\Tools\Setup 2011-08-30 20:41:02 +02:00
Benjamin Eberlei
4f3282d328 Bump DBAL dependency to 2.1.2 2011-08-29 23:00:27 +02:00
Benjamin Eberlei
60e7efa5b9 Merge branch 'DDC-1225' into 2.1.x 2011-08-29 21:55:51 +02:00
Guilherme Blanco
0a4c1028a1 Fixed issue with duplicated commas if Entity has no fields. 2011-08-29 21:54:41 +02:00
Benjamin Eberlei
2c3757d087 Merge branch 'DDC-1341' into 2.1.x 2011-08-28 21:50:04 +02:00
Benjamin Eberlei
bf892a8ece Namespace shortcut for repository was not merged into 2.1.x yet 2011-08-28 21:49:53 +02:00
Guilherme Blanco
878710d013 Fixed issue with CTI during DQL update that was incorrectly setting parameter types during multi table execution. Fixes DDC-1341. 2011-08-28 21:48:24 +02:00
Benjamin Eberlei
be800a991c Merge branch 'FixPostgresFailures' into 2.1.x 2011-08-28 15:57:44 +02:00
Benjamin Eberlei
e295168c19 DDC-1348 - Fix bug with UnitOfWork::getEntityState() 2011-08-28 15:57:33 +02:00
Benjamin Eberlei
c03d7dd086 Merge branch 'DDC-1306' into 2.1.x 2011-08-27 20:46:09 +02:00
Benjamin Eberlei
70938fa7f9 DDC-1306, DDC-1113 - Fix issues with inheritance and commit order 2011-08-27 20:44:47 +02:00
Benjamin Eberlei
83dbde9cda Bump Dev Version to 2.1.2-DEV 2011-08-26 06:11:43 +00:00
Benjamin Eberlei
05d12e20be Release 2.1.1 2011-08-26 06:07:58 +00:00
Benjamin Eberlei
9b3b6052da Fix tests after updating to latest DBAL 2.1.1 release 2011-08-26 08:03:25 +02:00
Benjamin Eberlei
ffdc5e8d32 Merge remote-tracking branch 'origin/2.1.x' into 2.1.x 2011-08-24 20:45:45 +02:00
Benjamin Eberlei
6380795827 Merge branch 'DDC-1333' into 2.1.x 2011-08-21 15:06:57 +02:00
Benjamin Eberlei
6e9575b121 DDC-1333 - Fix bug in xsd 2011-08-21 15:06:50 +02:00
Benjamin Eberlei
099e2c1bd3 Merge branch 'DDC-1340' into 2.1.x 2011-08-21 15:02:47 +02:00
Benjamin Eberlei
90c47a0510 DDC-1340 - Fix bug with merge() and optimistic lock exception 2011-08-21 15:02:39 +02:00
Alexander
67133f8886 [DDC-1301] Fixed tests teardown for mysql suite 2011-08-14 19:28:15 +02:00
Benjamin Eberlei
4b4efe5483 Merge branch 'DDC-1300' into 2.1.x 2011-08-06 20:25:35 +02:00
Benjamin Eberlei
6291139dd1 DDC-1300 - Fix bug in fetch join hydration of entities with foreign key identifier 2011-08-06 20:23:47 +02:00
Benjamin Eberlei
90f96c4fba Add phar packaging target and distribute phar into download folder 2011-08-01 23:09:17 +02:00
Benjamin Eberlei
23de4a0fe4 DDC-1313 - Optimize behavior of DriverChain::getAllClassNames() 2011-08-01 21:46:41 +02:00
Benjamin Eberlei
68faa589f5 Merge branch 'DDC-1302' into 2.1.x 2011-07-31 11:35:25 +02:00
Benjamin Eberlei
7633f7b7ae DDC-1302 - Fix bug in XmlDriver not handling orphan removal 2011-07-31 11:34:56 +02:00
Benjamin Eberlei
5f665a9f3c Merge branch 'DDC-1301' into 2.1.x 2011-07-28 23:27:04 +02:00
Alexander
4819594a71 [DDC-1301] Prefixed all Legacy models properties with _ 2011-07-28 23:26:43 +02:00
Alexander
64d3715e79 [DDC-1301] Fixed count() for fetch="EXTRA_LAZY" on OneToMany association 2011-07-28 23:26:43 +02:00
Alexander
b22f692406 [DDC-1301] Added tests for fetch="EXTRA_LAZY" count() on a "legacy" database 2011-07-28 23:26:43 +02:00
Benjamin Eberlei
a1ed28a39b Merge branch 'DDC-1275' into 2.1.x 2011-07-28 23:24:38 +02:00
Michael Ridgway
9b41b6106c F[DDC-1275] ixed check for owning side of a toOne relationship 2011-07-28 23:23:28 +02:00
Michael Ridgway
a24557bc27 Removing debug comment 2011-07-28 23:23:28 +02:00
Michael Ridgway
295281c890 DDC-1275: Added join columns to result set mapping 2011-07-28 23:23:28 +02:00
Benjamin Eberlei
57c5491494 Merge branch 'DDC-1298' into 2.1.x 2011-07-27 23:29:06 +02:00
Benjamin Eberlei
b43fffce8b DDC-1298 - Fix bug in SQLWalker with derived entities that have no fields of their own. 2011-07-27 23:28:40 +02:00
Benjamin Eberlei
e426c26ade Merge branch 'DDC-1238' into 2.1.x 2011-07-27 20:52:39 +02:00
Benjamin Eberlei
e23f5bc825 DDC-1238 - Fixed a bug introduced when refactoring persisters hydration. This occurs when you call $em->clear() and you start accessing a proxy. 2011-07-27 20:52:16 +02:00
Benjamin Eberlei
ce4f98aaab Update tests 2011-07-27 20:52:16 +02:00
Benjamin Eberlei
16d18c7f83 DDC-1238 - Reproducible case, its correct through 2011-07-27 20:52:16 +02:00
Benjamin Eberlei
6d62484065 Started trying to reproduce this issue 2011-07-27 20:52:15 +02:00
Benjamin Eberlei
6d1067b90c Merge branch 'DDC-1215' into 2.1.x 2011-07-26 23:01:07 +02:00
Benjamin Eberlei
3c3e5cbf41 [DDC-1215] Fix EntityGenerator inheritance regenerating properties and methods 2011-07-26 23:00:53 +02:00
Benjamin Eberlei
aabf39940a Merge branch 'DDC-1280' into 2.1.x 2011-07-26 22:31:16 +02:00
Benjamin Eberlei
1fedd0e7d3 [DDC-1280] Only generate linefeeds in proxies for consistency. 2011-07-26 22:31:06 +02:00
Benjamin Eberlei
a0a03947a3 Merge branch 'DDC-1276' into 2.1.x 2011-07-26 22:16:31 +02:00
Benjamin Eberlei
420da54620 DDC-1276 - Fix bug where merge managed and new entitiy share the same collection that is cascaded, cleared during the process and then empty afterwards. 2011-07-26 22:16:16 +02:00
Benjamin Eberlei
7bb77fc437 Merge branch 'EntityGenerator' into 2.1.x 2011-07-12 23:47:01 +02:00
Benjamin Eberlei
bb3f6957d4 DDC-1244 - Fix bug with entities without namespace 2011-07-12 23:46:53 +02:00
Benjamin Eberlei
5ef63c1c0d DDC-1254 - Dont throw exception about missing id in disconnected metadata factory 2011-07-12 23:46:53 +02:00
Benjamin Eberlei
09c957fdc0 DDC-1268 - Singular add*() method name through using targetEntity shortname 2011-07-12 23:46:53 +02:00
Benjamin Eberlei
64bc782bdb Merge branch 'DDC-1240' into 2.1.x 2011-07-12 22:51:48 +02:00
Benjamin Eberlei
8fb5b40fc1 DDC-1240 - Fix optimistic lock exception loosing the message 2011-07-12 22:51:40 +02:00
Benjamin Eberlei
448811eb6a Merge branch 'DDC-1250' into 2.1.x 2011-07-09 22:14:02 +02:00
Benjamin Eberlei
ecc79c8ca0 DDC-1250 - Fix bug with inverse one to one loading and ambigious column names in certain scenarios 2011-07-09 22:13:54 +02:00
Benjamin Eberlei
d2320128cf Merge branch 'DDC-1257' into 2.1.x 2011-07-09 15:15:05 +02:00
Benjamin Eberlei
e5df347ea3 DDC-1257 - Fix bug where validation callbacks are added multiple times in EntityGenerator 2011-07-09 15:14:55 +02:00
Benjamin Eberlei
9e2b98ca16 Merge branch 'DDC-1251' into 2.1.x 2011-07-09 14:54:27 +02:00
Benjamin Eberlei
515b44126b DDC-1251 - Fix bug in token parsing of EntityGenerator 2011-07-09 14:54:10 +02:00
Benjamin Eberlei
1feac7ae9e DDC-1022 - Call __wakeup() with the same semantics then ClassMetadata::newInstance() does inside UnitOfWork 2011-07-09 12:24:47 +02:00
Benjamin Eberlei
1929ab6a75 Set DEV Version 2011-07-04 21:37:08 +00:00
Benjamin Eberlei
dfdb735306 Release 2.1.0 2011-07-04 21:34:47 +00:00
Benjamin Eberlei
cb49648eed Bump Common and DBAL to 2.1 2011-07-04 21:33:36 +00:00
Guilherme Blanco
438dd9141f Merge branch 'master' of github.com:doctrine/doctrine2 2011-07-04 11:38:45 -03:00
Guilherme Blanco
db37d974c8 Increasing visibility of AnnotationReader inside AnnotationDriver from private to protected. 2011-07-04 11:33:44 -03:00
Benjamin Eberlei
6b54cceed7 Moved AnnotationRegistry::registerFile() call to Configuration#newDefaultAnnotationDriver() and documented the migration in UPGRADE_TO_2_1 2011-07-03 12:21:04 +02:00
Benjamin Eberlei
73f908f25c Merge branch 'master' of github.com:doctrine/doctrine2 2011-07-03 12:07:40 +02:00
Guilherme Blanco
550fcbc17f [DDC-1237] Fixed issue with QueryBuilder where user may have includes nested complex expression in a string format while consuming a composite expression (AND or OR). 2011-07-03 01:48:18 -03:00
Benjamin Eberlei
ffca455788 Bump Dev Version to 2.1.0RC4-DEV 2011-07-02 20:29:02 +00:00
Benjamin Eberlei
e4f2a56277 Release 2.1.0RC3 2011-07-02 20:28:37 +00:00
Benjamin Eberlei
cbe14a694a Update Common dependency to 2.1 RC3 2011-07-02 20:28:04 +00:00
Benjamin Eberlei
f589cd0d9f Update common version 2011-07-02 20:30:35 +02:00
Benjamin Eberlei
43d8466fa9 Update annotation handling in AnnotationDriver to work with AnnotationRegistry and bump common dependency 2011-07-02 19:48:43 +02:00
Benjamin Eberlei
5299bd788f DDC-1239 - Fix missing AND in Eager LEFT JOIN of entity persister when multiple join columns are used 2011-06-30 21:04:46 +02:00
Benjamin Eberlei
a0a81db045 DDC-1204, DDC-1203 - No need to throw this exception for abstract classes anymore 2011-06-30 20:57:29 +02:00
Benjamin Eberlei
5362206297 Revert "Fixed ClassMetadataFactory which was throwing an exception if parent class on inheritance hierarchy is an abstract class and also extends from a mapped superclass (so it contains an inheritance already), but is not in the discriminatorMap."
This reverts commit 4603e94fe9.

Making an exception go away is not a fix for something. :)
2011-06-30 20:12:22 +02:00
Benjamin Eberlei
e32e141012 Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-30 20:10:05 +02:00
Guilherme Blanco
4603e94fe9 Fixed ClassMetadataFactory which was throwing an exception if parent class on inheritance hierarchy is an abstract class and also extends from a mapped superclass (so it contains an inheritance already), but is not in the discriminatorMap. 2011-06-30 15:04:57 -03:00
Benjamin Eberlei
8b7e2a9f32 Merge pull request #81 from schmittjoh/annotation
added @Annotation to annotations
2011-06-30 08:02:08 -07:00
Johannes Schmitt
5701036068 added @Annotation to annotations 2011-06-30 11:03:32 +02:00
Guilherme Blanco
0f68355ce0 Merge pull request #80 from pkruithof/patch-2
Changed order of elements
2011-06-29 06:09:36 -07:00
Peter Kruithof
9395eeed3d Changed order of elements 2011-06-29 03:15:05 -07:00
Benjamin Eberlei
6d035be3e3 Bump Dev Version to 2.1.0-DEV 2011-06-28 21:11:14 +00:00
Benjamin Eberlei
01935e6661 Release 2.1.0RC2 2011-06-28 21:11:02 +00:00
Benjamin Eberlei
379584fb26 Bump dependencies of Common and DBAL to 2.1.0RC2 2011-06-28 21:10:43 +00:00
Benjamin Eberlei
f1c073e080 Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-28 22:30:24 +02:00
Benjamin Eberlei
66e92b147d Minor spelling mistake, thanks Alexandre Mathieu for reporting 2011-06-28 22:30:17 +02:00
Benjamin Eberlei
53c799987d Merge pull request #78 from mweimerskirch/patch-1
Removed superfluous variable name in "@return" documentation
2011-06-28 12:49:37 -07:00
Benjamin Eberlei
8850efb0eb Merge branch 'DDC-1230' 2011-06-28 21:38:06 +02:00
Benjamin Eberlei
551f6d05d9 DDC-1230 - Fix bug where UnitOfWork does not set STATE_REMOVE when calling EntityManager#remove() on an entity 2011-06-28 21:37:53 +02:00
Michel Weimerskirch
e899205300 Removed superfluous variable name in "@return" documentation 2011-06-28 12:24:24 -07:00
Benjamin Eberlei
5afc097527 Bump DBAL dependency to latest master 2011-06-26 19:06:52 +02:00
Benjamin Eberlei
ed516edf90 Fix discriminator casing problem in Oracle 2011-06-26 17:49:34 +02:00
Benjamin Eberlei
52431251cb Fix some of the problems with Oracle testsuite 2011-06-26 17:20:03 +02:00
Benjamin Eberlei
69944017d2 Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-26 10:11:29 +02:00
Benjamin Eberlei
ca0bea1d8a Merge branch 'DDC-1224' 2011-06-26 10:11:11 +02:00
Benjamin Eberlei
7efe071ac4 DDC-1224 - Bugfix with temporary table ids and tables in schema (in postgresql) 2011-06-26 10:10:57 +02:00
Guilherme Blanco
ebe95af30c Merge pull request #77 from rubensayshi/master
Fixed wrong keyname
2011-06-25 08:30:30 -07:00
Ruben de Vries
a607e2ec7a fixed wrong keyname 2011-06-25 17:08:56 +02:00
Benjamin Eberlei
a73a1e8437 DDC-1226, DDC-1228 - Bugfix with refereshing proxy references not setting the originalEntityData. 2011-06-25 14:38:44 +02:00
Benjamin Eberlei
07f568e2b4 Add test for DDC-1227 regression 2011-06-25 10:27:06 +02:00
Benjamin Eberlei
0dd1dc20c8 DDC-1227 - Fix regression in QueryBuilder::add() due to Expr\From refactoring. 2011-06-25 10:25:22 +02:00
Benjamin Eberlei
7367e255ae Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-25 10:24:06 +02:00
Benjamin Eberlei
10b70df1af DDC-1218, DDC-1156 - Fixed bugs with mapped superclasses in inheritance hierachies 2011-06-25 10:20:37 +02:00
Benjamin Eberlei
fe8b28a09f Add test for DDC-1156, DDC-1218 2011-06-25 09:57:15 +02:00
Guilherme Blanco
db80b2b135 Fixed phpunit tests which was failing due to a duplicate use declaration. 2011-06-21 12:38:08 -03:00
Benjamin Eberlei
a5cddb0c11 Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-20 21:26:33 +02:00
Benjamin Eberlei
3717ae3c53 strtolower() on cascade information avoids problem with case-sensitivity in YAML and annotations mapping driver. 2011-06-20 21:26:12 +02:00
Guilherme Blanco
2caf0fff60 Merge pull request #75 from joshiausdemwald/DatabaseDriver_Corrections
Suppressed an "undefined variable" notice by adding initialization code
2011-06-20 10:24:37 -07:00
Johannes Heinen
c05fffcc93 Suppressed php undefined variable notice adding initialization code to Doctrine\ORM\Mapping\Driver\DatabaseDriver.php 2011-06-20 19:07:03 +02:00
Benjamin Eberlei
fff0204e6d Merge branch 'DDC-1211' 2011-06-19 10:25:42 +02:00
Benjamin Eberlei
c7c430032c DDC-1211 - Fix bug with empty numeric literal 2011-06-19 10:17:35 +02:00
Benjamin Eberlei
1c2ade61ab DDC-1214 - Fix UpdateCommand::getFullName() 2011-06-19 10:05:30 +02:00
Benjamin Eberlei
c62e27898c Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-19 09:42:45 +02:00
Benjamin Eberlei
6f8ac21273 Merge branch 'DDC-1189' 2011-06-19 09:40:38 +02:00
Benjamin Eberlei
82f0c244e8 DDC-1189 - Bugfix with PersistentCollection#clear() in combination with lazy loading 2011-06-19 09:39:34 +02:00
Benjamin Eberlei
197744a57f Bump Dev Version to 2.1.0RC2-DEV 2011-06-18 22:08:52 +00:00
Benjamin Eberlei
054ac220ac Release 2.1.0RC1 2011-06-18 22:08:37 +00:00
Benjamin Eberlei
c6746d4a4a Merge 2.0.x build.xml changes into master build.xml 2011-06-18 22:07:14 +00:00
Benjamin Eberlei
f0bc3d925d Bump dependencies of Common and DBAL to 2.1.0RC1 2011-06-18 22:02:40 +00:00
Benjamin Eberlei
7810f5fe69 Add convenience helper for running tests against multiple different databases 2011-06-18 23:23:43 +02:00
Benjamin Eberlei
decd1482de Fix bug in ChangeTrackingNotify code 2011-06-18 23:06:07 +02:00
Benjamin Eberlei
fa7574b2ba Removed AllTests files and Suites 2011-06-18 22:49:25 +02:00
Benjamin Eberlei
32b146ea8a Switch testsuite to run with phpunit.ini.dist from main folder, not using AllTests approach. Fixed global state problem in tests that was caused by EventManager being reused. Significantly enhanced error message about cascade persist 2011-06-18 22:47:21 +02:00
Benjamin Eberlei
1aa90dc872 Add UPGRADE note about annotations parser changes 2011-06-18 21:25:29 +02:00
Benjamin Eberlei
989d375be5 Some more tests for the Setup helper 2011-06-18 08:47:10 +02:00
Benjamin Eberlei
ede68ec87b Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-18 01:05:48 +02:00
Benjamin Eberlei
02f06b6d52 Add convenience Setup Tool to register autoloaders and create configuration objects 2011-06-18 01:05:30 +02:00
Guilherme Blanco
699ccfddb6 Implemented COALESCE and NULLIF support in DQL. 2011-06-17 16:16:22 -03:00
Guilherme Blanco
626e467a17 Implemented COALESCE and NULLIF support in DQL. 2011-06-17 16:15:19 -03:00
Guilherme Blanco
1fed340793 Optimized AnnotationDriver to filter found files during getAllClassnames(). 2011-06-16 19:54:50 -03:00
Benjamin Eberlei
0cd0ae49a1 Fix regression introduced with DDC-1203,DDC-1204 patch 2011-06-16 23:00:59 +02:00
Benjamin Eberlei
713f4654fd Merge remote-tracking branch 'origin/master' 2011-06-16 22:49:29 +02:00
Benjamin Eberlei
22bdb1520a Merge branch 'DDC-1172' 2011-06-16 22:34:16 +02:00
Benjamin Eberlei
42c5382a03 DDC-1172 - Handle sequence dropping in SchemaTool. 2011-06-16 22:34:04 +02:00
Guilherme Blanco
98bc3c4e40 Merge pull request #74 from mridgway/DDC-1209
[DDC-1209] Fixed custom object types as @Id
2011-06-16 07:15:40 -07:00
Michael Ridgway
d1106a730b Made DDC-1209 test pass 2011-06-16 08:55:09 -04:00
Michael Ridgway
da2d83fc7d DDC-1209 tests 2011-06-15 17:15:46 -04:00
Benjamin Eberlei
ec748b2a16 Merge branch 'DDC-1203' 2011-06-15 22:27:32 +02:00
Benjamin Eberlei
5ff44b5ec7 DDC-1203, DDC-1204 - Fix problems with mapped superclasses in midth of inheritance hierachy and entities not mapped in discriminator map. 2011-06-15 22:27:24 +02:00
Benjamin Eberlei
dd329c903d Merge branch 'DDC-1208' 2011-06-15 18:32:06 +02:00
Benjamin Eberlei
3be6218341 DDC-1208 - Allow namespace separator in <discriminator-mapping /> 2011-06-15 18:31:59 +02:00
Benjamin Eberlei
71082bdba8 Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-15 18:29:01 +02:00
Benjamin Eberlei
866534334f Merge pull request #71 from fabpot/master
Updated Symfony2 vendors
2011-06-14 08:08:16 -07:00
Fabien Potencier
b37c8f6a23 Update Symfony2 vendors 2011-06-14 17:01:33 +02:00
Benjamin Eberlei
df500033bf Merge pull request #70 from pkruithof/patch-1
Change attribute 'columns' of type 'index' to 'xs:string'
2011-06-14 06:27:07 -07:00
Peter Kruithof
05bf8477a3 The columns attribute of the index type was xs:NMTOKENS, therefore not allowing a comma, which is needed when multiple columns need to be specified. (I've checked the XmlDriver code for this)
It should be the same as the `columns` attribute of the `unique-constraints` type, namely `xs:string`.
2011-06-14 02:36:49 -07:00
Benjamin Eberlei
2b52eff4eb Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-13 08:54:39 +02:00
Benjamin Eberlei
d5daf161c6 Merge pull request #64 from mvrhov/schema_extension
Add extension points into the xml schema
2011-06-12 13:14:19 -07:00
Benjamin Eberlei
12dfb5ee38 Merge pull request #67 from Nico-B/master
joinTable error with DB to YML generator
2011-06-12 13:09:32 -07:00
Benjamin Eberlei
39ee9e5e5b Merge pull request #69 from Garfield-fr/master
Renamed function getFullName with getName
2011-06-12 13:08:43 -07:00
Bertrand Zuchuat
c7eaf77d15 Renamed function getFullName with getName to match with last change on Symfony Console 2011-06-12 14:46:02 +02:00
NicoB
524c799e37 Merge remote-tracking branch 'remotes/upstream/master' 2011-06-11 17:56:50 +07:00
Benjamin Eberlei
fe527fbf1a Merge pull request #59 from asm89/databasedriver-patch
Implemented tableName -> className and columnName -> fieldName mapping in DatabaseDriver.
2011-06-11 03:15:16 -07:00
Benjamin Eberlei
037daff891 Merge pull request #65 from weaverryan/update_command
[Tools][Console] Refactoring the UpdateCommand
2011-06-11 01:16:25 -07:00
Benjamin Eberlei
9945296472 Merge pull request #61 from stof/EntityGenerator_3
Updated the EntityGenerator to be compatible with Common 3.0.x
2011-06-11 01:13:05 -07:00
Guilherme Blanco
fdbc909bde Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-09 15:43:37 -03:00
Guilherme Blanco
1f6b49d236 Added getRootEntities to QueryBuilder. 2011-06-09 15:42:40 -03:00
NicoB
3cdb4e007d joinTable can be undefined because ManyToMAny generation is bidirectional with inverse sides 2011-06-07 18:55:52 +07:00
Benjamin Eberlei
a4cbb23fc8 Slight adjustment to build.xml 2011-06-05 17:21:37 +02:00
Benjamin Eberlei
97573425f9 Merge branch 'DDC-1163' 2011-06-05 16:21:35 +02:00
Benjamin Eberlei
4371e8fab0 DDC-1163 - Fix nasty bug with inheritance in UnitOfWork::executeUpdates() and executeRemovals() 2011-06-05 16:21:23 +02:00
Benjamin Eberlei
22826ac10d DDC-1156 - Do not throw exception for mapped superclass in middle of inheritance hierachy anymore. 2011-06-05 15:00:49 +02:00
Benjamin Eberlei
543432bf53 Merge branch 'DDC-1192' 2011-06-05 14:50:05 +02:00
Benjamin Eberlei
d17d0f5452 DDC-1192 - Fix notice in XmlDriver, removed unnecessary code. 2011-06-05 14:49:54 +02:00
Benjamin Eberlei
d3ab9b51fa DDC-1181 - Add test that verifies cascade remove works for entities with foreign identifiers 2011-06-05 13:57:44 +02:00
Benjamin Eberlei
ddb647f39f DDC-1173 - Fix bug when calling UnitOfWork::clearEntityChangeSet() in listener 2011-06-05 13:34:07 +02:00
Benjamin Eberlei
70d756d59c DDC-1184 - Improve error handling in AssignedIdGenerator 2011-06-05 12:54:29 +02:00
Benjamin Eberlei
f3677554e8 Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-05 12:52:05 +02:00
Benjamin Eberlei
3cdff65761 Merge pull request #63 from chesteroni/master
Added missing checks for associatation indexes
2011-06-05 03:28:52 -07:00
Benjamin Eberlei
ff30f86082 Merge pull request #62 from mvrhov/yml_export_notice_fix
Fixing Notice: Undefined index in yaml export driver
2011-06-05 03:28:31 -07:00
Benjamin Eberlei
1038a866a4 DDC-1194 - Improve error handling for DQL INSTANCE OF 2011-06-05 10:48:21 +02:00
Benjamin Eberlei
2c9a12771b Merge branch 'DDC-1193' 2011-06-05 10:03:04 +02:00
Benjamin Eberlei
bda4165bf8 DDC-1193 - Fix previous commit. 2011-06-05 10:02:57 +02:00
Benjamin Eberlei
84aefd6340 Merge branch 'DDC-1193' 2011-06-05 09:59:57 +02:00
Benjamin Eberlei
acaf08d4b7 DDC-1193 - Fix bug with cascade remove and proxy classes. 2011-06-05 09:59:16 +02:00
Benjamin Eberlei
875912bffd DDC-733 - Add UnitOfWork::initializeObject() method. 2011-06-05 08:44:38 +02:00
Fabien Potencier
86c3744b8c Made orm:convert-mapping command more configurable (allow to change the extension of the generated files for instance) 2011-06-05 08:23:08 +02:00
Benjamin Eberlei
875f5c1fa8 Merge branch 'master' of github.com:doctrine/doctrine2 2011-06-05 08:19:25 +02:00
Ryan Weaver
6468740915 [Tools][Console] Reworking changes to be more backwards compatible
This keeps the --dump-sql and --force options, but adds an exception if you try to use them both (which previously, only dumped the SQL but didn't tell you that it was *not* in fact also executing the queries).

One additional change is the introduction of a `$name` property, which was the only way that a parent task could allow a child task to override the task's name early enough that the task's overridden name is taken to account when the parent class references it for its help message.
2011-06-03 15:09:18 -05:00
Ryan Weaver
79643e32ed [Tools][Console] Refactoring the UpdateCommand
There are two basic changes:

  1) Changed --force and --dump-sql from options to a single argument. Prior, you couldn't pass both options simultaneously anyways, so making them an argument is more accurate.

  2) Changed the language and formatting of the task to be more user-friendly.
2011-06-03 08:02:51 -05:00
Miha Vrhovnik
bf0775fbb6 Add extension points into the xml schema 2011-06-03 09:12:32 +02:00
Alexander
7ee8dc4e44 DDC-1179 - Make it possible to specify a namespace when mapping with --from-database 2011-06-02 21:45:03 +02:00
chesteroni
23540c17f1 Added checking for existing indexes in associatation mapping array. 2011-05-28 20:57:19 -07:00
Miha Vrhovnik
bb873826ca Fixing Notice: Undefined index: orderBy in ...Doctrine/ORM/Tools/Export/Driver/YamlExporter 2011-05-27 08:43:29 +02:00
Guilherme Blanco
93521217a6 Moved getQuoted* from ClassMetadata to ClassMetadataInfo, since SchemaTool relies on them, making impossible to work with DisconnectedClassMetadataFactory. 2011-05-26 02:18:29 -03:00
Christophe Coevoet
693fc090b5 Updated the EntityGenerator to be compatible with Common 3.0.x 2011-05-25 12:35:54 +02:00
Benjamin Eberlei
ca6ea65b1f Merge branch 'NewAnnotations' 2011-05-25 00:36:10 +02:00
Benjamin Eberlei
3adbf0de39 Add forward compatibility with Doctrine Common 3.0 2011-05-25 00:35:57 +02:00
Johannes Schmitt
a0d79b03e7 [AnnotationDriver] compatibility with Doctrine Common 3.x 2011-05-25 00:32:55 +02:00
Benjamin Eberlei
2b3d0a209c Merge branch 'NewAnnotations' 2011-05-25 00:26:29 +02:00
Benjamin Eberlei
6d724ad9ff Make ORM forward compatible with new Doctrine Annotations library version 2.1 2011-05-25 00:26:20 +02:00
Benjamin Eberlei
0bb0937372 Started UPGRADE_TO_2_1 document 2011-05-20 21:13:25 +02:00
Alexander
cec62db2d8 Removed _ prefix from private functions. 2011-05-20 16:53:35 +02:00
Alexander
262ae7c942 Implemented tableName -> className and columnName -> fieldName mapping in
DatabaseDriver.
2011-05-20 16:36:43 +02:00
Benjamin Eberlei
85fb1a3ebb Merge branch 'master' of github.com:doctrine/doctrine2 2011-05-18 19:37:32 +02:00
Benjamin Eberlei
a979852ee6 Merge branch 'DDC-1080' 2011-05-17 23:42:39 +02:00
Benjamin Eberlei
9ea03de84f DDC-1080 - Fix bug with hydration of derived entities/foreign key as primary key. 2011-05-17 23:42:24 +02:00
Benjamin Eberlei
9f01e9563f Bump Dev Version to 2.1.0BETA2-DEV 2011-05-16 19:35:00 +00:00
Benjamin Eberlei
f4021e7469 Update Doctrine Common to latest version and add new methods to ClassMetadataInfo 2011-05-16 20:42:38 +02:00
Benjamin Eberlei
5d81e867be Add some logic to keep backwards compatibility in QueryBuilder 2011-05-15 23:16:44 +02:00
Benjamin Eberlei
85d40847ac Reintroduce QueryBuilder::getRootAlias() for backwards compatibility reasons, mark as @deprecated 2011-05-15 22:11:10 +02:00
Benjamin Eberlei
5e938b3147 Revert "Implemented auto-inference of isCascadeRemove when orphanRemoval is defined (marked as todo)."
This reverts commit 551247d11a.
2011-05-15 20:39:55 +02:00
Benjamin Eberlei
2cfdf2b05d Merge branch 'master' of github.com:doctrine/doctrine2 2011-05-15 20:18:47 +02:00
Benjamin Eberlei
d45f7c1302 DDC-694 - Add info command 2011-05-15 20:18:31 +02:00
Benjamin Eberlei
34ad2ccbf1 Merge pull request #23 from kertz/master
LifecycleCallbackMethods Beautification
2011-05-15 10:40:23 -07:00
Benjamin Eberlei
83372bd144 Merge pull request #23 from kertz/master
LifecycleCallbackMethods Beautification
2011-05-15 10:39:44 -07:00
Benjamin Eberlei
1b4f0a5e1f DDC-1080 - Add failing testcase 2011-05-14 13:32:12 +02:00
Benjamin Eberlei
14d630ae1c Merge branch 'DDC-1151' 2011-05-14 08:44:49 +02:00
Benjamin Eberlei
cb3615ab47 DDC-1151 - Fix missing table quotes when adding foreign keys in SchemaTool 2011-05-14 08:44:19 +02:00
Benjamin Eberlei
e66970b78b Merge branch 'master' of github.com:doctrine/doctrine2 2011-05-14 08:16:21 +02:00
Guilherme Blanco
2a7364bb18 We now support @Id on @ManyToOne fields. Removed TODO and associated code. 2011-05-14 00:53:22 -03:00
Guilherme Blanco
f4d62b317e Fixed endless recursion of DDC-719 test. 2011-05-14 00:49:46 -03:00
Guilherme Blanco
551247d11a Implemented auto-inference of isCascadeRemove when orphanRemoval is defined (marked as todo). 2011-05-14 00:43:33 -03:00
Guilherme Blanco
08f2af489e Changed QueryBuilder to allow retrieval of all defined root alises. 2011-05-14 00:40:23 -03:00
Guilherme Blanco
e538128645 [DDC-1029] renaming "load()" in proxy to "__load()" 2011-05-13 00:23:27 -03:00
Guilherme Blanco
1e9e2de737 [DDC-1122] Added coverage for bug report. 2011-05-13 00:12:05 -03:00
Guilherme Blanco
8e3fdc5adc [DDC-1148] Implement auto-inference of types in setParameter. 2011-05-12 23:05:45 -03:00
Benjamin Eberlei
814473c27d Merge pull request #35 from brikou/master
[DDC-1136] prevent backup file to be recognized as valid Entity file
2011-05-12 05:41:31 -07:00
Guilherme Blanco
a45a02fd64 Merge branch 'master' of github.com:doctrine/doctrine2 2011-05-11 21:40:43 -03:00
Guilherme Blanco
905e05cd36 [DDC-1067][DDC-1145] Fixed bug with multiple froms and inclusion of joins. Added support for index by in QueryBuilder. This break BC only if users are using base support (->add). 2011-05-11 21:40:27 -03:00
Benjamin Eberlei
75b69f946d Merge pull request #57 from stof/DDC-1146
[DDC-1146] Updated the Symfony2 vendors
2011-05-11 16:14:25 -07:00
Christophe Coevoet
a112be79ef [DDC-1146] Updated the Symfony2 vendors 2011-05-12 01:10:34 +02:00
Guilherme Blanco
54a0109d5d [DDC-1147] Allowed usage of 0-based input parameters in DQL. 2011-05-11 17:30:42 -03:00
Guilherme Blanco
0d0d61935f Merge pull request #53 from Garfield-fr/master
Fixed typo
2011-05-08 05:18:46 -07:00
Bertrand Zuchuat
b959ab37bf Typo 2011-05-08 13:57:08 +02:00
Guilherme Blanco
b025b2b343 Fixed where componentes (ie. MEMBER OF) that that are sensitive to parenthesis presence. Made OR and AND expressions smarter. Fixed related unit tests. 2011-05-07 20:14:04 -03:00
Guilherme Blanco
bffca232e2 Merge branch 'master' of github.com:doctrine/doctrine2 2011-05-06 01:42:01 -03:00
Guilherme Blanco
19dfe7b891 Added method to allow retrieve all registered entity namespace aliases. 2011-05-06 01:41:34 -03:00
Benjamin Eberlei
ce1e446227 Merge branch 'master' of github.com:doctrine/doctrine2 2011-05-03 17:06:42 +02:00
Benjamin Eberlei
5d1905de13 DDC-1120 - Fix comment 2011-05-01 12:17:09 +02:00
Benjamin Eberlei
23131d6860 Merge branch 'DDC-1129' 2011-05-01 11:45:39 +02:00
Benjamin Eberlei
d4569baa11 [DDC-1129] Fix bug in version changeset computation aswell as inline ClassMetadata::isCollectionValuedAssociation to increase performance by 2-5% 2011-05-01 11:44:31 +02:00
Benjamin Eberlei
b5520aa304 Merge branch 'DDC-1091' 2011-05-01 11:01:39 +02:00
Benjamin Eberlei
c53baa9935 [DDC-1091] Fix bug with custom string functions in StringPrimary 2011-05-01 11:01:30 +02:00
Benjamin Eberlei
d0b95bb31c Merge branch 'DDC-1043' 2011-05-01 10:21:59 +02:00
Benjamin Eberlei
7a068c206e DDC-1043 - Make computeChangeSet() algorithm more strict, possible leading to more updates to to values that are not exactly the same. However this is necessary to avoid bugs with certain PHP casting rules, i.e. +44 = 44 2011-05-01 10:21:47 +02:00
Benjamin Eberlei
5a6ac2fb56 Merge branch 'DDC-1102' 2011-05-01 10:01:49 +02:00
Benjamin Eberlei
6b3dfaccfc DDC-1102 - Typo in EntityGenerator 2011-05-01 10:01:38 +02:00
Benjamin Eberlei
e3d2a0e293 Merge branch 'PR39' 2011-05-01 00:18:22 +02:00
Francis Besset
a141aaf663 [PR-39] Throw exception when hydrating joined entity without existing parent alias (NativeQuery problem only) 2011-05-01 00:17:40 +02:00
Benjamin Eberlei
85d6b9fd39 Merge pull request #42 from mridgway/DDC-1059.
DDC-1059: RSM Helper
2011-04-30 15:01:25 -07:00
Benjamin Eberlei
e9067eb4a3 Merge branch 'DDC-1149' 2011-04-30 23:52:10 +02:00
Fabien Potencier
0c955fe54f Fix namespace/class parsing in the entity generator 2011-04-30 23:51:22 +02:00
Benjamin Eberlei
ea5a623c88 Merge branch 'DDC-1094' 2011-04-30 23:18:48 +02:00
Benjamin Eberlei
73c7605a5c [DDC-1094] Add support for limit, offset and orderby in EntityRepository::findBy(). 2011-04-30 23:18:24 +02:00
Benjamin Eberlei
0b1077a3b3 Merge branch 'DBAL-115' 2011-04-30 17:16:47 +02:00
Benjamin Eberlei
5179ff921b [DBAL-115] REALLY fix issues with SchemaTool::getDropSchemaSQL(). 2011-04-30 17:16:34 +02:00
Benjamin Eberlei
abbb3e8656 Merge branch 'DBAL-115' 2011-04-30 16:20:32 +02:00
Benjamin Eberlei
1f665e6ba8 [DBAL-115] Bugfix in SchemaTool not quoting table names when dropping schema. 2011-04-30 16:20:14 +02:00
Benjamin Eberlei
d72217a00a Merge branch 'DDC-1133' 2011-04-30 14:23:59 +02:00
Benjamin Eberlei
261d3c892e DDC-1133 - Ducktype AnnotationReader in AnnotationDriver 2011-04-30 14:23:46 +02:00
Benjamin Eberlei
2c50faf5b2 Merge branch 'DDC-1109' 2011-04-30 12:49:52 +02:00
Benjamin Eberlei
7dd0dd273e [DDC-1109] ltrim discriminator map for convenience. 2011-04-30 12:49:37 +02:00
Benjamin Eberlei
b462cfbe2b Merge branch 'DDC-1108' 2011-04-30 12:27:35 +02:00
Benjamin Eberlei
67b89eaa4f [DDC-1108] Fix bug with single char named input parameters in DQL lexer. 2011-04-30 12:27:16 +02:00
Benjamin Eberlei
ed355d2eb6 Merge branch 'master' of github.com:doctrine/doctrine2 2011-04-30 11:19:58 +02:00
Benjamin Eberlei
c6d725d6e8 Merge branch 'DDC-1132' 2011-04-30 11:16:41 +02:00
Benjamin Eberlei
42230a4c51 [DDC-1132] Fix many to many table detection. 2011-04-30 11:16:30 +02:00
Benjamin Eberlei
7929aea45a Merge branch 'DDC-1132' 2011-04-30 11:15:56 +02:00
Benjamin Eberlei
f09d299660 [DDC-1132] Fix many to many table detection. 2011-04-30 11:15:45 +02:00
Benjamin Eberlei
41b3a372d3 Add new performance test checking compute changeset performance. 2011-04-30 10:47:56 +02:00
Guilherme Blanco
7aaecacc5b Merged pull request #36 from ajessu/fixTypo.
Fix typos on the help text of the schema commands
2011-04-26 18:12:32 -07:00
Guilherme Blanco
237a05b302 Merged pull request #44 from Chekote/parser_match_phpdoc_fix.
Parser::match phpdoc fix
2011-04-26 18:09:27 -07:00
Guilherme Blanco
f73c7f3be0 Merged pull request #48 from rdohms/master.
Fixing docblocks in SchemaTool
2011-04-26 18:05:59 -07:00
Guilherme Blanco
fe66d8bc04 Fixed SchemaTool which was failing to dropSchema due to foreignKeyContraint checks. Fixes DDC-1126 2011-04-26 12:32:04 -03:00
Guilherme Blanco
26bd3e3811 Implemented support for closure return on EntityManager::transactional. Fixes DDC-1125 2011-04-25 18:32:43 -03:00
Rafael Dohms
0b7feb359d Fixing outdated docblocks for SchemaTool 2011-04-17 23:39:59 -03:00
Michael Ridgway
af4cf0d0ba Replaced prefix parameter with renamedColumns; Added exception when duplicate columns found 2011-04-14 20:55:03 -04:00
Guilherme Blanco
4d561651a1 Merge branch 'master' of github.com:doctrine/doctrine2 2011-04-13 00:11:08 -03:00
Benjamin Eberlei
7e3265e7c7 Merge branch 'DDC-1040' 2011-04-03 23:06:15 +02:00
Benjamin Eberlei
822481d360 [DDC-1040] Add regression tests for entity as multiple named/positional parameters. 2011-04-03 23:06:03 +02:00
Benjamin Eberlei
7905f2a972 [DDC-1040] Bugfix with named parameters and multiple entities passed as parameter. 2011-04-03 23:03:39 +02:00
Benjamin Eberlei
88dc18f88a Merge branch 'DDC-1093' 2011-04-03 20:29:15 +02:00
Benjamin Eberlei
e685d59604 [DDC-1093] Fix docblock type hint 2011-04-03 20:29:07 +02:00
Benjamin Eberlei
300df03743 Merge branch 'DDC-1087' 2011-04-03 09:05:20 +02:00
Benjamin Eberlei
a329007526 [DDC-1087] Add missing resolution to IS NULL in EntityRepository when passing a null value as a criteria. 2011-04-03 09:03:43 +02:00
Chekote
5784c7bacd Fixed phpdoc on Parser::match incorrectly stating that the token parameter can be a string value 2011-04-01 12:54:12 -05:00
Benjamin Eberlei
db82ef3e61 Merge branch 'DDC-991' 2011-03-31 23:35:12 +02:00
Benjamin Eberlei
ea52b3cc8f [DDC-991] Rename method to AbstractQuery::getOneOrNullResult(). 2011-03-31 23:35:01 +02:00
Benjamin Eberlei
24a7a72f59 [DDC-991] add AbstractQuery::getOneResult() method that returns null instead of throwing an exception as getSingleResult() does. 2011-03-31 23:32:49 +02:00
Michael Ridgway
b1b17376ff Removing left over class import 2011-03-31 17:22:13 -04:00
Michael Ridgway
c46d835146 Moved new functions to ResultSetMappingBuilder class 2011-03-30 10:27:31 -04:00
Michael Ridgway
20dc72ef9a First pass on RSM helper functions for adding entities 2011-03-29 20:35:01 -04:00
Benjamin Eberlei
e9c4f612cf Merge branch 'DDC-692' 2011-03-29 20:17:56 +02:00
Benjamin Eberlei
34ad308599 [DDC-692] Add respective metadata mapping possiblities for read-only entities and a test. 2011-03-29 20:17:44 +02:00
Benjamin Eberlei
9a75277dd4 [DDC-692] Add ClassMetadataInfo::isReadOnly flag and ignore these entities in Change Tracking. 2011-03-29 20:04:14 +02:00
Benjamin Eberlei
003ab06465 Merge branch 'DDC-696' 2011-03-29 19:43:41 +02:00
Benjamin Eberlei
36985ee704 Merge remote branch 'mridgway/DDC-696' into DDC-696 2011-03-29 19:42:38 +02:00
Benjamin Eberlei
21acb67b01 Merge branch 'DDC-1077' 2011-03-27 21:11:30 +02:00
Benjamin Eberlei
bda15231da [DDC-1077] Bugfix in not handling literals in Select Expressions. 2011-03-27 21:10:50 +02:00
Benjamin Eberlei
5022b04ff9 Merge branch 'DDC-1079' 2011-03-27 14:05:08 +02:00
Benjamin Eberlei
6ed0ff0a12 [DDC-1079] Bugfix for shortcut for ArithmeticExpressions in SimpleSelectExpression that lead to literals not being valid. Problem was that ScalarExpression() did not handle AggregateExpressions() at all, which is now fixed. 2011-03-27 14:04:53 +02:00
Benjamin Eberlei
789739f3e4 Merge branch 'DDC-1014' 2011-03-27 12:19:31 +02:00
Benjamin Eberlei
4f1af0114f [DDC-1014] Add DATE_ADD(), DATE_SUB(), DATE_DIFF() functions for DQL. 2011-03-27 12:18:47 +02:00
Benjamin Eberlei
5d1b4f98de [DDC-1014] Update DBAL remote to include date arithmetics related functionality. 2011-03-27 11:34:14 +02:00
Michael Ridgway
706cc838e5 Removed svn variable 2011-03-22 08:54:33 -04:00
Michael Ridgway
17cbb34952 Clean up of test case 2011-03-21 23:30:10 -04:00
Michael Ridgway
1f50dee8a8 DDC-696: Added onClear event 2011-03-21 23:17:08 -04:00
Benjamin Eberlei
e126315c1b Bump DBAL dependency to include Security Fix for AbstractPlatform::modifyLimitQuery() 2011-03-21 23:54:40 +01:00
Benjamin Eberlei
ccb5c57784 Merge branch 'DDC-992' 2011-03-20 17:07:31 +01:00
Benjamin Eberlei
7a41a205ee [DDC-992] Fix criteria usage of column names clashing with field or associations by prefixing with table names or alias. 2011-03-20 17:07:19 +01:00
Benjamin Eberlei
8430aefe21 Merge branch 'DDC-1053' 2011-03-20 14:07:44 +01:00
Benjamin Eberlei
edfdbe10a0 [DDC-1053] Fix bug with usage of identification variables in GroupByItem. 2011-03-20 14:07:33 +01:00
Benjamin Eberlei
108ceae313 Merge branch 'DDC-1052' 2011-03-20 13:07:55 +01:00
Benjamin Eberlei
e42a227a7c [DDC-1052] Fix bug with versioning and inheritance 2011-03-20 13:07:47 +01:00
Benjamin Eberlei
7e262dd42c Merge branch 'DDC-1068' 2011-03-20 12:36:03 +01:00
Benjamin Eberlei
ac175d2c40 [DDC-1068] Fix case-sensitivity problems of first loading of Metadata. 2011-03-20 12:35:52 +01:00
Benjamin Eberlei
c77dbd859b [DDC-1070] Fix global test state problem introduced with test. 2011-03-20 12:25:27 +01:00
Benjamin Eberlei
efe26d00a1 Merge branch 'DDC-1070' 2011-03-20 12:19:12 +01:00
Benjamin Eberlei
62755cc647 [DDC-1070] Fix in AbstractQuery::iterate() method not respecting hydrator and parameters. 2011-03-20 12:19:01 +01:00
Guilherme Blanco
8c8a658dfd Merge branch 'master' of github.com:doctrine/doctrine2 2011-03-19 14:36:29 -03:00
Benjamin Eberlei
53ca54ced6 Merge branch 'master' of github.com:doctrine/doctrine2 2011-03-16 23:01:17 +01:00
Benjamin Eberlei
e757e3beaf Merge branch 'DDC-952' 2011-03-16 23:00:57 +01:00
Benjamin Eberlei
5192306d39 [DDC-952] One last commit with some refactorings, additional comments and two new tests. Also added convenience method Query::setFetchMode($className, $assocName) 2011-03-16 22:51:32 +01:00
Benjamin Eberlei
4b98e3ea8e DDC-952 - Remove unnecessary instance variable and comment on one feature. 2011-03-16 00:03:43 +01:00
Benjamin Eberlei
b7e522d7a7 DDC-952 - This nasty inheritance hydration bug slipped in again, fixed again now. 2011-03-15 23:39:19 +01:00
Benjamin Eberlei
6d27b4760f [DDC-952] Add Persister hydration performance tests. 2011-03-15 23:22:37 +01:00
Benjamin Eberlei
1b46208aa5 [DDC-952] More fixes 2011-03-15 21:34:47 +01:00
Benjamin Eberlei
b3c01903b4 DDC-952 - Optimization 2011-03-15 20:03:05 +01:00
Benjamin Eberlei
a04ba44874 [DDC-952] Introduced SimpleObjectHydrator again for performance reasons. 2011-03-15 19:48:04 +01:00
brikou
9a8e8ce35d Edited lib/Doctrine/ORM/Tools/EntityGenerator.php via GitHub 2011-03-15 05:39:38 -07:00
Albert Jessurum
234d2e5f0f Fix typo on schema help messages 2011-03-15 12:22:53 +01:00
Guilherme Blanco
180078d0f6 Added namedQueries as optional during serialization of ClassMetadata. 2011-03-14 01:04:50 -03:00
Benjamin Eberlei
38ad25ad4c [DDC-952] Remove all the unnecessary hydration code from all Persisters. 2011-03-13 00:23:46 +01:00
Benjamin Eberlei
1bc4b62805 [DDC-952] Make collection loading work with hydrators also. 2011-03-13 00:15:50 +01:00
Benjamin Eberlei
7c7106b1c1 DDC-952 - Fix bug in inverse one-to-one eager loading sql code. 2011-03-12 19:11:37 +01:00
Benjamin Eberlei
4677883acd [DDC-952] Added modelset and tests for Eager Loading, detected a bug with inverse one-to-one eager fetching that needs to be addressed. 2011-03-12 14:01:51 +01:00
Benjamin Eberlei
8794d35867 DDC-952 - Woah this still needs tons of tests. 2011-03-09 23:30:35 +01:00
Benjamin Eberlei
595c19207c [DDC-914] Always fetch joining inverse side one-to-one associations breaks a ton of DDC-117 tests, investigate why to make this working also. 2011-03-09 23:21:33 +01:00
Benjamin Eberlei
077ae9cee9 [DDC-914] Fetch join many-to-one/one-to-one associations configured as FETCH_EAGER inside the persisters. 2011-03-09 23:14:54 +01:00
Guilherme Blanco
925f1c281c Merge branch 'NamedQueries' 2011-03-09 14:44:56 -03:00
Guilherme Blanco
7d1fca1ca2 Added support to NamedQueries through ClassMetadata. 2011-03-09 14:43:42 -03:00
Benjamin Eberlei
fd502631c7 DDC-734 - REname query hint to fetchEager. 2011-03-08 22:28:55 +01:00
Benjamin Eberlei
60eb755fe9 DDC-952, DDC-734 Add DQL query hint to switch associations from lazy to eager for deferred initialization optimizations. 2011-03-08 22:22:54 +01:00
Guilherme Blanco
a31289b9d7 Added support to NamedQueries through ClassMetadata. 2011-03-06 18:45:09 -03:00
Benjamin Eberlei
112f9d1480 [DDC-1050] Change refresh of collection back 2011-03-06 21:49:02 +01:00
Benjamin Eberlei
851f44a066 [DDC-952] [DDC-1050] Use ObjectHydrator inside Persisters, removing a bunch of duplicate code (step1, more necessary) 2011-03-06 21:26:54 +01:00
Benjamin Eberlei
d9c8a9eecb [DDC-952] Fix merge/rebase mistake. 2011-03-06 15:28:26 +01:00
Benjamin Eberlei
03630df20d Add support for IN(?) queries in repositories using the DBAL support for parameter lists. 2011-03-06 11:15:56 +01:00
Benjamin Eberlei
3d37e436dd DDC-952 - Refactor eager loading entities, it is only allowed for non composite primary key entities. 2011-03-05 11:09:38 +01:00
Benjamin Eberlei
32df9451fd DDC-952 - Implemented first approach for batching eager loads of ToOne associations. 2011-03-05 11:08:41 +01:00
Benjamin Eberlei
e0b835178b Bump dependency of ORM master to DBAL 2.0.2 2011-03-05 10:09:16 +01:00
Benjamin Eberlei
78aa893efd Adjust tests to changes in DBAL dependency with regard to automatic foreign key and index naming. 2011-03-05 10:08:30 +01:00
Benjamin Eberlei
8c7261e7c3 Optimize build process even more, now generating PEAR Packages for Symfony YAML and Console. 2011-03-05 00:18:10 +01:00
Benjamin Eberlei
ae61272c13 Merge branch 'DDC-1034' 2011-03-04 23:01:02 +01:00
Benjamin Eberlei
67ae22b911 DDC-1034 - Fix bug where callbacks where registered multiple times in inheritance hierachies. 2011-03-04 23:00:54 +01:00
Benjamin Eberlei
c0d26f2308 Merge branch 'DDC-1056' 2011-03-04 22:22:19 +01:00
Benjamin Eberlei
d58ae2ecda DDC-1056 - Fixed notice in StaticPHPDriver. 2011-03-04 22:22:07 +01:00
Benjamin Eberlei
1d5fef4144 Merge branch 'DDC-1041' 2011-03-03 23:11:24 +01:00
Benjamin Eberlei
49195ebe17 [DDC-1041] You could retrieve instances of the wrong type in inheritance hierachies because the identity map aggregates them by rootEntityName. 2011-03-03 23:11:09 +01:00
Benjamin Eberlei
9a33bd083d Merge branch 'DDC-1050' 2011-03-03 22:52:13 +01:00
Benjamin Eberlei
b2c7a9c7fc [DDC-1050] Throw exception when trying to define inheritance information on a mapped superclass. It is not a valid use-case. 2011-03-03 22:51:53 +01:00
Benjamin Eberlei
8b9f12d924 Remove copied code of Symfony Console/Yaml and added submodules. 2011-02-28 21:51:56 +01:00
Benjamin Eberlei
521705a0f2 Merge branch 'DDC-1033' 2011-02-26 12:48:10 +01:00
Benjamin Eberlei
c144df9be3 DDC-1033 - Fix cloning of not initialized proxies. 2011-02-26 12:47:59 +01:00
Benjamin Eberlei
1eb7f92956 DDC-1026 - Fix Result Cache Seperate chaining implementation that was wrong since DDC-892 was applied. 2011-02-26 00:39:54 +01:00
Romain Pouclet
9125413786 Fixed typo in AbstractQuery::execute() doc 2011-02-25 10:22:50 -06:00
Benjamin Eberlei
afc9495b3f Revert "Merge branch 'DDC-884'"
This reverts commit 3eea19dcfa, reversing
changes made to b13c29944b.
2011-02-21 18:52:49 +01:00
Guilherme Blanco
3eea19dcfa Merge branch 'DDC-884' 2011-02-21 00:51:00 -03:00
Guilherme Blanco
04ab0cd8fc [DDC-884] Allow subclassing EntityManager. 2011-02-21 00:50:51 -03:00
Guilherme Blanco
b13c29944b Merge branch 'DDC-1035' 2011-02-21 00:38:03 -03:00
Guilherme Blanco
68bb0c1ae1 [DDC-1035] Fixed orphanRemoval on YAML mapping driver. 2011-02-21 00:37:32 -03:00
Guilherme Blanco
a18a7bb678 Merge branch 'DDC-1012' 2011-02-20 01:54:05 -03:00
Guilherme Blanco
834203d868 [DDC-1012] Implemented Expr isNull and isNotNull. 2011-02-20 01:53:55 -03:00
Guilherme Blanco
99c7924292 Merge branch 'DDC-1036' 2011-02-20 01:44:27 -03:00
Guilherme Blanco
505d9e2154 [DDC-1036] Modified the AggregateExpressions to support SimpleArithmeticExpression instead of StateFieldPathExpression. 2011-02-20 01:44:05 -03:00
Guilherme Blanco
bca927f861 Merge branch 'DDC-982' 2011-02-20 01:31:12 -03:00
Guilherme Blanco
70d2cbe857 [DDC-982] Implemented more unique sql table alias. 2011-02-20 01:30:58 -03:00
Guilherme Blanco
dcf358f154 Fixed some warnings from phpunit. 2011-02-19 19:50:58 -02:00
Guilherme Blanco
2b2d9e7a1d Fixed wrong test. It was failing if you have memcache extension loaded. 2011-02-19 17:20:37 -02:00
Jonathan H. Wage
328a5fe49a Updating to reverted version of common. 2011-02-16 10:25:29 -06:00
Jonathan H. Wage
839b6dd5e4 Revert "Removing old Driver interface in favor of the new one in Common\Persistence. Also changed to use fully qualified class name for interfaces in common to avoid weird aliases."
This reverts commit c988a99d55.
2011-02-16 10:24:42 -06:00
Jonathan H. Wage
c988a99d55 Removing old Driver interface in favor of the new one in Common\Persistence. Also changed to use fully qualified class name for interfaces in common to avoid weird aliases. 2011-02-16 10:06:39 -06:00
Jonathan H. Wage
a4a184b27c Implementing ClassMetadataFactory interface. 2011-02-15 21:00:48 -06:00
Jonathan H. Wage
68a4099684 Implementing initial Doctrine\Common\Persistence interfaces. 2011-02-15 20:02:45 -06:00
Benjamin Eberlei
64088fce5d Merge branch 'DDC-1030' 2011-02-13 10:02:34 +01:00
Benjamin Eberlei
35a152318e DDC-1030 - Fix Static Reflection with namespace levels deeper than one. 2011-02-13 10:02:18 +01:00
Benjamin Eberlei
3d0d31ddf8 Merge branch 'DDC-1024' 2011-02-12 17:40:16 +01:00
Benjamin Eberlei
c456f27f60 DDC-1024 - Do not generate setter/getter for inherited fields. 2011-02-12 17:40:07 +01:00
Benjamin Eberlei
4ecb582c76 Update dependencies to Common 2.0.1 and DBAL 2.0.1 2011-02-05 11:46:35 +01:00
Benjamin Eberlei
7390030854 Merge branch 'DDC-250' 2011-02-05 11:44:00 +01:00
Benjamin Eberlei
da2dee03e2 [DDC-250] Small typo fix in xsd 2011-02-05 11:43:50 +01:00
Benjamin Eberlei
9768d08458 [DDC-250] Add tests and fix some glitches and finalized index-by patch. 2011-02-05 11:42:10 +01:00
Benjamin Eberlei
a9fe9f43e9 Merge branch 'DDC-1018' into DDC-250 2011-02-05 10:04:36 +01:00
Benjamin Eberlei
61e2cdc6b0 [DDC-1018] Bugfix: INDEX BY was not working in JOIN Declarations, only in FROM. 2011-02-05 10:04:18 +01:00
Benjamin Eberlei
60203af9b2 Merge branch 'DDC-1018' 2011-02-05 10:02:48 +01:00
Benjamin Eberlei
4532c2255a [DDC-1018] Bugfix: INDEX BY was not working in JOIN Declarations, only in FROM. 2011-02-05 10:02:37 +01:00
Benjamin Eberlei
17c1ed948e [DDC-250] Initial untested support for @ManyToMany(indexBy) and @OneToMany(indexBy) option. 2011-02-05 09:31:40 +01:00
Benjamin Eberlei
266d85e917 Merge branch 'DDC-1006' 2011-02-02 23:30:39 +01:00
Benjamin Eberlei
4122abf558 DDC-1008, DDC-1002 - Create constructor and id setter if necessary. 2011-02-02 23:30:16 +01:00
Benjamin Eberlei
f9c1464879 DDC-1006, DDC-953 - Fix EntityGenerator creating empty classes 2011-02-02 23:21:42 +01:00
Benjamin Eberlei
277e0aee8c Merge remote branch 'origin/master' 2011-02-02 19:49:23 +01:00
Benjamin Eberlei
957aefe69e Merge branch 'DDC-892' 2011-01-23 20:54:36 +01:00
Benjamin Eberlei
3515df913f DDC-892 - Implement separate chaining approach for result caches to prevent hash colissions. 2011-01-23 20:54:29 +01:00
Benjamin Eberlei
8869678c0f Merge branch 'GenerationTools' 2011-01-23 20:26:37 +01:00
Benjamin Eberlei
05f41278a6 Significantly updated the Help of the ConvertMapping and GenerateEntities Commands to help people using and understanding their scope. Added an additional --force flag to ConvertMapping command. 2011-01-23 20:25:59 +01:00
Benjamin Eberlei
961cb6e9a3 Merge branch 'DDC-958' 2011-01-23 17:26:59 +01:00
Benjamin Eberlei
ed53f8aa74 DDC-958 - Fire postLoad event when calling refresh(). 2011-01-23 17:26:11 +01:00
Benjamin Eberlei
be2e00c991 Merge branch 'DDC-968' 2011-01-23 16:47:20 +01:00
Benjamin Eberlei
f1809ce180 DDC-968 - Add AbstractQuery::getHints() method 2011-01-23 16:47:07 +01:00
Benjamin Eberlei
549965b4b2 Merge branch 'DDC-997' 2011-01-23 16:42:06 +01:00
Benjamin Eberlei
f70ee3a038 DDC-997 - Fix bug in hydration that came up with DDC-117 2011-01-23 16:41:40 +01:00
Benjamin Eberlei
975d6ada66 Merge branch 'DDC-969' 2011-01-23 16:12:36 +01:00
Benjamin Eberlei
a6e63d2676 DDC-969 - Use of field instead of column when accessing a table leads to error when both differ. 2011-01-23 16:12:26 +01:00
Benjamin Eberlei
c5593be7c9 Merge branch 'DDC-978' 2011-01-23 15:40:34 +01:00
Benjamin Eberlei
65bbdc30de DDC-978 - Fix bug where Collection gets cleared (again) when calling flush multiple times and replacing a PersistentCollection with a new one. 2011-01-23 15:40:16 +01:00
Benjamin Eberlei
482ff2d009 Merge branch 'DDC-996' 2011-01-23 14:21:23 +01:00
Benjamin Eberlei
fd44894e9a DDC-996 - Throw more useful exception if fieldName is empty in a mapped field or association. 2011-01-23 14:20:15 +01:00
Benjamin Eberlei
92ed05fc84 Merge branch 'DDC-960' 2011-01-23 12:49:20 +01:00
Benjamin Eberlei
5d333045b9 DDC-960 - Bugfix in how Persisters generate Fetch last version of Entity SQL. 2011-01-23 12:48:28 +01:00
Benjamin Eberlei
bcafa19386 Merge branch 'DDC-975' 2011-01-13 21:43:47 +01:00
Benjamin Eberlei
03698e4068 DDC-975 - Fix notice in SchemaTool in combination with XML mapping driver. 2011-01-13 21:43:33 +01:00
Benjamin Eberlei
fdee7a9ae0 Merge branch 'DDC-980' 2011-01-13 21:16:24 +01:00
Benjamin Eberlei
078e19d1c7 DDC-980 - Fix Update and Delete statements reference of the root table when doing subselects. 2011-01-13 21:16:08 +01:00
Benjamin Eberlei
7a2c99353a Merge branch 'DDC-546' 2011-01-02 15:14:40 +01:00
Benjamin Eberlei
3539b32629 DDC-546 - Found some more code that needs DDC-117 compliance. 2011-01-02 15:14:12 +01:00
Benjamin Eberlei
247fc43cef DDC-546 - Rename ClassMetadataInfo::FETCH_EXTRALAZY to ClassMetadataInfo::FETCH_EXTRA_LAZY 2011-01-02 15:10:47 +01:00
Benjamin Eberlei
a3cab174ca DDC-546 - Updated with support for DDC-117. 2011-01-02 14:04:52 +01:00
Benjamin Eberlei
89e7e8623c DDC-546 - Remove dynamic public property approach in PersistentCollection::count() EXTRA_LAZY. 2011-01-02 13:43:49 +01:00
Benjamin Eberlei
cbfdf61976 DDC-546 - Bugfix for PersistentCollection::count() in EXTRA LAZY special case. 2011-01-02 13:41:18 +01:00
Benjamin Eberlei
3acc05d953 DDC-546 - Fix bug in inverse many-to-many contains. 2011-01-02 13:37:29 +01:00
Benjamin Eberlei
685e327b43 DDC-546 - Fix some rebasing issues. 2011-01-02 12:54:55 +01:00
Benjamin Eberlei
f572be92e2 DDC-546 - Add EXTRALAZY to doctrine-mapping.xsd enumeration of fetch-types. 2011-01-02 12:46:08 +01:00
Benjamin Eberlei
75d59d8695 DDC-546 - Added functionality for extra-lazy PersistentCollection::contains(). 2011-01-02 12:46:08 +01:00
Benjamin Eberlei
7c567b305a Refactor DDC-546 persister approach. 2011-01-02 12:46:08 +01:00
Benjamin Eberlei
d3d9957fd4 DDC-546 - Fix some minor glitches in patch. 2011-01-02 12:44:16 +01:00
Benjamin Eberlei
c998797c55 DDC-546 - Add Extra Lazy Collection prototype. 2011-01-02 12:44:16 +01:00
Benjamin Eberlei
78d4277e4b Merge branch DDC-117 into master 2011-01-02 12:01:05 +01:00
Benjamin Eberlei
f3abf9a30a Merge branch 'DDC-965' 2011-01-02 10:24:38 +01:00
Benjamin Eberlei
9177dc3d52 DDC-965 - Defer ID check after loadMetata event is fired. 2011-01-02 10:24:23 +01:00
Benjamin Eberlei
34b303845f Merge branch 'DDC-966' 2011-01-02 10:18:23 +01:00
Benjamin Eberlei
c1edd5848f DDC-966 - Fix NOT NULL constraint SingleTableInheritance Generation using SchemaTool. 2011-01-02 10:18:02 +01:00
Benjamin Eberlei
ed7ec261d0 Merge branch 'DDC-949' 2011-01-02 09:42:15 +01:00
Benjamin Eberlei
a2cc9f0f6d DDC-949 - Bugfix for BasicEntityPersister not using $types for select clauses. This fixes the issue for PostgreSQL however it still occurs on Oracle. DBAL change is necessary for this. 2011-01-02 09:38:32 +01:00
Benjamin Eberlei
2a005019bf DDC-117 - Add XML and YML Driver support for associated identifier. 2011-01-01 21:47:04 +01:00
Benjamin Eberlei
c2bbaa9ead DDC-117 - Slight changes in the patch and fixing inline comments. 2011-01-01 18:53:22 +01:00
Benjamin Eberlei
194a90923d DDC-117 - Finalize patch, fix all the problems of different use-cases by hugely expanding the test-model. 2011-01-01 18:17:19 +01:00
Benjamin Eberlei
e1ed0bf52a Merge branch 'DDC-945-2' 2010-12-31 14:39:13 +01:00
Benjamin Eberlei
7112b551e2 DDC-945 - Fix regression, ManyToMany unidirectional owning side assocations should be allowed. 2010-12-31 14:39:01 +01:00
Benjamin Eberlei
3498f4d6ee Merge branch 'DDC-929' 2010-12-30 23:18:14 +01:00
Benjamin Eberlei
8658376713 DDC-929 - Fix bug with DatabaseDriver not detecting indexes that are not called primary. 2010-12-30 23:18:00 +01:00
Benjamin Eberlei
7a40e3f6f2 Merge branch 'DDC-961' 2010-12-30 22:30:59 +01:00
Benjamin Eberlei
58019fbac0 DDC-961 - Bugfix with missing first letter in automatic join table names in global namespace entities. 2010-12-30 22:30:51 +01:00
Benjamin Eberlei
2d27a99a0b DDC-117 - Began to fix some issues surrounding the DDC-881 report and references to composite fk entities. 2010-12-29 01:02:21 +01:00
Benjamin Eberlei
337e2fa043 Fix DDC-795 (subtask of DDC-117) and integrated a test for cascade (that only works with sequence id generators). 2010-12-28 19:05:46 +01:00
Benjamin Eberlei
e7b4dca611 Merge master into DDC-117 2010-12-28 17:27:47 +01:00
Benjamin Eberlei
1d5b24ecc5 Merge branch 'DDC-837' 2010-12-28 14:56:24 +01:00
Benjamin Eberlei
2d89ddfb1f DDC-837 - Fix bug with associations of the same name not being possible in inheritance hierachies. 2010-12-28 14:56:13 +01:00
Benjamin Eberlei
eeca184836 Merge branch 'DDC-928' 2010-12-28 12:19:16 +01:00
Benjamin Eberlei
1d2b2b2c8b DDC-928 - Fix undefined variable notice. 2010-12-28 12:18:42 +01:00
Benjamin Eberlei
26bb7978b5 Merge branch 'DDC-945' 2010-12-28 12:00:07 +01:00
Benjamin Eberlei
aa6ac3d6b0 DDC-945 - Throw exception in ClassMetadataFactory when mapped superclass has to many associations. 2010-12-28 11:59:51 +01:00
Benjamin Eberlei
1720527f81 Merge branch 'DDC-617' 2010-12-28 10:17:53 +01:00
Benjamin Eberlei
fe672d2f61 DDC-617 - Throw error if selecting identification variables without picking at least one root entity alias. 2010-12-28 10:17:33 +01:00
Benjamin Eberlei
d488e84d8d Merge branch 'DDC-931' 2010-12-22 22:06:02 +01:00
Benjamin Eberlei
a4f88407c2 DDC-931 - SchemaTool#dropSchema() should not stop on failure of a single query (as stated in docblocks). 2010-12-22 22:04:11 +01:00
Benjamin Eberlei
e46c65db09 Fix for DDC-944 2010-12-22 00:23:22 +01:00
Benjamin Eberlei
6988b55f50 Bump Dev Version to 2.1.0-DEV 2010-12-21 16:45:50 -05:00
Amal Raghav
b736c4216c _generateEntityLifecycleCallbackMethods Beautification 2010-12-17 16:51:51 +05:30
Benjamin Eberlei
5bd8ffa53c Merge master into DDC-117 2010-08-27 22:27:00 +02:00
Benjamin Eberlei
1496250833 Merge branch 'master' into DDC-117 2010-08-15 20:17:56 +02:00
Benjamin Eberlei
772e592489 Try Assoc-Id Mapping with Id that has its column renamed. 2010-08-15 19:15:34 +02:00
Benjamin Eberlei
fb44fa6b5a Fix hydration of Assoc-Id Entities, duplicate the hydration of the foreign key once for for use with the assoc-entity as a meta-column. Added isIdentifier capabilities to meta columns. 2010-08-15 18:58:25 +02:00
Benjamin Eberlei
5799e391c6 Fix bug with updating assoc-id entities 2010-08-15 14:40:06 +02:00
Benjamin Eberlei
e45c52b024 Merge Removal of association classes into DDC-117 branch, quite some merge efforts necessary to get it working again 2010-08-13 23:23:11 +02:00
Benjamin Eberlei
db936035e0 Added more tests for DQL joining the primary key entity and querying other fields 2010-08-08 19:46:45 +02:00
Benjamin Eberlei
7b07a17886 Merge branch 'master' into DDC-117 2010-08-08 14:07:24 +02:00
Benjamin Eberlei
e3a4c8ddeb Refactored TestCase and added several more use-cases 2010-08-08 10:46:01 +02:00
Benjamin Eberlei
013262a9b7 Add support for EntityManager::remove() of full or partial association primary keys 2010-08-07 21:09:19 +02:00
Benjamin Eberlei
10f47389ae Made single identifier One-To-One + Id work also and added a test-case 2010-08-07 20:07:10 +02:00
Benjamin Eberlei
c697a2d47f Prototype hack of @ManyToOne + @Id support with two test-scenarios, composite association key only composite key, and a mixed key scenario. I think single foreign association would work also 2010-08-07 19:33:54 +02:00
295 changed files with 15402 additions and 8063 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
build.properties
build/
logs/
reports/

9
.gitmodules vendored
View File

@@ -4,3 +4,12 @@
[submodule "lib/vendor/doctrine-dbal"]
path = lib/vendor/doctrine-dbal
url = git://github.com/doctrine/dbal.git
[submodule "lib/vendor/Symfony/Component/Console"]
path = lib/vendor/Symfony/Component/Console
url = git://github.com/symfony/Console.git
[submodule "lib/vendor/Symfony/Component/Yaml"]
path = lib/vendor/Symfony/Component/Yaml
url = git://github.com/symfony/Yaml.git
[submodule "lib/vendor/doctrine-build-common"]
path = lib/vendor/doctrine-build-common
url = https://github.com/doctrine/doctrine-build-common.git

19
.travis.yml Normal file
View File

@@ -0,0 +1,19 @@
language: php
php:
- 5.3
- 5.4
env:
- DB=mysql
- DB=pgsql
- DB=sqlite
before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- git submodule update --init
script: phpunit --configuration tests/travis/$DB.travis.xml

24
UPGRADE_TO_2_1 Normal file
View File

@@ -0,0 +1,24 @@
This document details all the possible changes that you should investigate when updating
your project from Doctrine 2.0.x to 2.1
## Interface for EntityRepository
The EntityRepository now has an interface Doctrine\Common\Persistence\ObjectRepository. This means that your classes that override EntityRepository and extend find(), findOneBy() or findBy() must be adjusted to follow this interface.
## AnnotationReader changes
The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way:
\Doctrine\Common\Annotations\AnnotationRegistry::registerFile('/doctrine-src/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
// new code necessary starting here
$reader->setIgnoreNotImportedAnnotations(true);
$reader->setEnableParsePhpImports(false);
$reader = new \Doctrine\Common\Annotations\CachedReader(
new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache()
);
This is already done inside the ``$config->newDefaultAnnotationDriver``, so everything should automatically work if you are using this method. You can verify if everything still works by executing a console command such as schema-validate that loads all metadata into memory.

11
build.properties Normal file
View File

@@ -0,0 +1,11 @@
# Project Name
project.name=DoctrineORM
# Dependency minimum versions
dependencies.common=2.1.0
dependencies.dbal=2.1.0
dependencies.sfconsole=2.0.0
# Version class and file
project.version_class = Doctrine\ORM\Version
project.version_file = lib/Doctrine/ORM/Version.php

View File

@@ -1,15 +0,0 @@
version=2.0.0BETA2
dependencies.common=2.0.0BETA4
dependencies.dbal=2.0.0BETA4
stability=beta
build.dir=build
dist.dir=dist
report.dir=reports
log.archive.dir=logs
project.pirum_dir=
project.download_dir=
test.phpunit_configuration_file=
test.phpunit_generate_coverage=0
test.pmd_reports=0
test.pdepend_exec=
test.phpmd_exec=

142
build.xml
View File

@@ -1,11 +1,7 @@
<?xml version="1.0"?>
<!--
Doctrine 2 build file.
-->
<project name="Doctrine2" default="build" basedir=".">
<project name="DoctrineORM" default="build" basedir=".">
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" />
<import file="${project.basedir}/lib/vendor/doctrine-build-common/packaging.xml" />
<property file="build.properties" />
@@ -14,6 +10,8 @@
-->
<fileset id="shared-artifacts" dir=".">
<include name="LICENSE"/>
<include name="UPGRADE*" />
<include name="doctrine-mapping.xsd" />
</fileset>
<!--
@@ -51,98 +49,34 @@
-->
<fileset id="symfony-sources" dir="./lib/vendor">
<include name="Symfony/Component/**"/>
<exclude name="**/.git/**" />
</fileset>
<!--
Clean the directory for the next build.
-->
<target name="clean">
<available file="./build.properties" property="build_properties_exist" value="true"/>
<fail unless="build_properties_exist" message="The build.properties file is missing." />
<delete dir="${build.dir}" includeemptydirs="true" />
<delete dir="${dist.dir}" includeemptydirs="true" />
<delete dir="${report.dir}" includeemptydirs="true" />
</target>
<!--
Prepare the new build directories after cleaning
-->
<target name="prepare" depends="clean">
<echo msg="Creating build directory: ${build.dir}" />
<mkdir dir="${build.dir}" />
<echo msg="Creating distribution directory: ${dist.dir}" />
<mkdir dir="${dist.dir}" />
<echo msg="Creating report directory: ${report.dir}" />
<mkdir dir="${report.dir}" />
<mkdir dir="${build.dir}/logs"/>
<mkdir dir="${report.dir}/tests"/>
</target>
<!--
Builds ORM package, preparing it for distribution.
-->
<target name="build-orm" depends="prepare">
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
<copy todir="${build.dir}/doctrine-orm">
<target name="copy-files" depends="prepare">
<copy todir="${build.dir}/${project.name}-${version}">
<fileset refid="shared-artifacts"/>
</copy>
<copy todir="${build.dir}/doctrine-orm">
<copy todir="${build.dir}/${project.name}-${version}">
<fileset refid="common-sources"/>
<fileset refid="dbal-sources"/>
<fileset refid="orm-sources"/>
</copy>
<copy todir="${build.dir}/doctrine-orm/Doctrine">
<copy todir="${build.dir}/${project.name}-${version}/Doctrine">
<fileset refid="symfony-sources"/>
</copy>
<copy todir="${build.dir}/doctrine-orm/bin">
<copy todir="${build.dir}/${project.name}-${version}/bin">
<fileset refid="bin-scripts"/>
</copy>
<exec command="sed 's/${version}-DEV/${version}/' ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php > ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php" passthru="true" />
<exec command="mv ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php" passthru="true" />
</target>
<target name="build" depends="test, build-orm"/>
<!--
Runs the full test suite.
-->
<target name="test" depends="prepare">
<if><equals arg1="${test.phpunit_generate_coverage}" arg2="1" />
<then>
<property name="test.phpunit_coverage_file" value="${build.dir}/logs/clover.xml" />
</then>
<else>
<property name="test.phpunit_coverage_file" value="false" />
</else>
</if>
<nativephpunit
testfile="./tests/Doctrine/Tests/AllTests.php" junitlogfile="${build.dir}/logs/testsuites.xml"
testdirectory="./tests" coverageclover="${test.phpunit_coverage_file}" configuration="${test.phpunit_configuration_file}"
/>
<phpunitreport infile="${build.dir}/logs/testsuites.xml" format="frames" todir="${report.dir}/tests" />
<nativephpunit testfile="./tests/Doctrine/Tests/ORM/Performance/AllTests.php" testdirectory="./tests" haltonfailure="false" haltonerror="false" />
<tstamp/>
<copy file="${build.dir}/logs/testsuites.xml" tofile="${log.archive.dir}/latest/log.xml" overwrite="true"/>
<if><equals arg1="${test.pmd_reports}" arg2="1" />
<then>
<exec command="${test.pdepend_exec} --jdepend-xml=${build.dir}/logs/jdepend.xml ./lib/Doctrine" />
<exec command="${test.phpmd_exec} ./lib/Doctrine xml codesize --reportfile ${build.dir}/logs/phpmd.xml" />
<copy file="${build.dir}/logs/jdepend.xml" tofile="${log.archive.dir}/latest/jdepend.xml" overwrite="true"/>
<copy file="${build.dir}/logs/phpmd.xml" tofile="${log.archive.dir}/latest/phpmd.xml" overwrite="true"/>
</then>
</if>
</target>
<!--
Builds distributable PEAR packages.
-->
<target name="build-packages" depends="build-orm">
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/doctrine-orm">
<target name="define-pear-package" depends="copy-files">
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/${project.name}-${version}">
<name>DoctrineORM</name>
<summary>Doctrine Object Relational Mapper</summary>
<channel>pear.doctrine-project.org</channel>
@@ -152,61 +86,29 @@
<lead user="romanb" name="Roman Borschel" email="roman@code-factory.org" />
<lead user="beberlei" name="Benjamin Eberlei" email="kontakt@beberlei.de" />
<license>LGPL</license>
<version release="${version}" api="${version}" />
<stability release="${stability}" api="${stability}" />
<version release="${pear.version}" api="${pear.version}" />
<stability release="${pear.stability}" api="${pear.stability}" />
<notes>-</notes>
<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.1.99" />
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" maximum_version="2.1.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>
<dirroles key="bin">script</dirroles>
<ignore>Doctrine/Common/</ignore>
<ignore>Doctrine/DBAL/</ignore>
<ignore>Symfony/Component/Yaml/</ignore>
<ignore>Symfony/Component/Console/</ignore>
<release>
<install as="doctrine" name="bin/doctrine" />
<install as="doctrine.php" name="bin/doctrine.php" />
<install as="doctrine.bat" name="bin/doctrine.bat" />
</release>
<replacement path="bin/doctrine.bat" type="pear-config" from="@php_bin@" to="php_bin" />
<replacement path="bin/doctrine" type="pear-config" from="@php_bin@" to="php_bin" />
<replacement path="bin/doctrine.bat" type="pear-config" from="@bin_dir@" to="bin_dir" />
</d51pearpkg2>
<exec command="pear package" dir="${build.dir}/doctrine-orm" passthru="true" />
<exec command="mv DoctrineORM-${version}.tgz ../../dist" dir="${build.dir}/doctrine-orm" passthru="true" />
<tar destfile="dist/DoctrineORM-${version}-full.tar.gz" compression="gzip" basedir="${build.dir}">
<fileset dir="${build.dir}">
<include name="**/**" />
<exclude name="logs/" />
<exclude name="doctrine-orm/package.xml" />
</fileset>
</tar>
</target>
<target name="git-tag">
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
<exec command="git tag -a ${version}" passthru="true" />
<exec command="git push origin ${version}" passthru="true" />
</target>
<target name="pirum-release">
<exec command="sudo pirum add ${project.pirum_dir} ${project.basedir}/dist/DoctrineORM-${version}.tgz" dir="." passthru="true" />
<exec command="sudo pirum build ${project.pirum_dir}" passthru="true" />
</target>
<target name="distribute-download">
<copy file="dist/DoctrineORM-${version}-full.tar.gz" todir="${project.download_dir}" />
</target>
<target name="update-dev-version">
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
<propertyprompt propertyName="next_version" defaultValue="${version}" promptText="Enter next version string (without -DEV)" />
<exec command="sed 's/${version}-DEV/${next_version}-DEV/' ${project.basedir}/lib/Doctrine/ORM/Version.php > ${project.basedir}/lib/Doctrine/ORM/Version2.php" passthru="true" />
<exec command="mv ${project.basedir}/lib/Doctrine/ORM/Version2.php ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
<exec command="git add ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
<exec command="git commit -m 'Bump Dev Version to ${next_version}-DEV'" passthru="true" />
<exec command="git push origin master" passthru="true" />
</target>
<target name="release" depends="git-tag,build-packages,distribute-download,pirum-release,update-dev-version" />
</project>
</project>

23
composer.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "doctrine/orm",
"type": "library","version":"2.1.6",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "http://www.doctrine-project.org",
"license": "LGPL",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"require": {
"php": ">=5.3.2",
"ext-pdo": "*",
"doctrine/common": "2.1.*",
"doctrine/dbal": "2.1.*"
},
"autoload": {
"psr-0": { "Doctrine\\ORM": "lib/" }
}
}

View File

@@ -17,11 +17,18 @@
<xs:sequence>
<xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="entity" type="orm:entity" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
</xs:element>
<xs:complexType name="emptyType"/>
<xs:complexType name="emptyType">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="cascade-type">
<xs:sequence>
@@ -30,7 +37,9 @@
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:simpleType name="lifecycle-callback-type">
@@ -46,13 +55,32 @@
</xs:simpleType>
<xs:complexType name="lifecycle-callback">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="type" type="orm:lifecycle-callback-type" use="required" />
<xs:attribute name="method" type="xs:NMTOKEN" use="required" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="lifecycle-callbacks">
<xs:sequence>
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="named-query">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="query" type="xs:string" use="required" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="named-queries">
<xs:sequence>
<xs:element name="named-query" type="orm:named-query" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
@@ -63,12 +91,14 @@
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/>
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="1" />
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="many-to-one" type="orm:many-to-one" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="many-to-many" type="orm:many-to-many" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="table" type="xs:NMTOKEN" />
@@ -76,11 +106,18 @@
<xs:attribute name="repository-class" type="xs:string"/>
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
<xs:attribute name="read-only" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="mapped-superclass" >
<xs:complexContent>
<xs:extension base="orm:entity"/>
<xs:extension base="orm:entity">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
@@ -121,10 +158,14 @@
<xs:restriction base="xs:token">
<xs:enumeration value="EAGER"/>
<xs:enumeration value="LAZY"/>
<xs:enumeration value="EXTRALAZY"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="field">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
<xs:attribute name="column" type="xs:NMTOKEN" />
@@ -135,75 +176,114 @@
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="precision" type="xs:integer" use="optional" />
<xs:attribute name="scale" type="xs:integer" use="optional" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="discriminator-column">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field-name" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="unique-constraint">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="unique-constraints">
<xs:sequence>
<xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="index">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:NMTOKENS" use="required"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="indexes">
<xs:sequence>
<xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="discriminator-mapping">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
<xs:attribute name="class" type="xs:NMTOKEN" use="required"/>
<xs:attribute name="class" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="discriminator-map">
<xs:sequence>
<xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="generator">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="strategy" type="orm:generator-strategy" use="optional" default="AUTO" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="id">
<xs:sequence>
<xs:element name="generator" type="orm:generator" minOccurs="0" />
<xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" />
<xs:attribute name="column" type="xs:NMTOKEN" />
<xs:attribute name="association-key" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="sequence-generator">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="sequence-name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="allocation-size" type="xs:integer" use="optional" default="1" />
<xs:attribute name="initial-value" type="xs:integer" use="optional" default="1" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="inverse-join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="join-column">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="optional" default="id" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
@@ -211,32 +291,43 @@
<xs:attribute name="on-delete" type="orm:fk-action" />
<xs:attribute name="on-update" type="orm:fk-action" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="join-table">
<xs:sequence>
<xs:element name="join-columns" type="orm:join-columns" />
<xs:element name="inverse-join-columns" type="orm:join-columns" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="schema" type="xs:NMTOKEN" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="order-by">
<xs:sequence>
<xs:element name="order-by-field" type="orm:order-by-field" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="order-by-field">
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="direction" type="orm:order-by-direction" default="ASC" />
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="direction" type="orm:order-by-direction" default="ASC" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:simpleType name="order-by-direction">
@@ -251,24 +342,30 @@
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<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:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="one-to-many">
<xs:sequence>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="index-by" type="xs:NMTOKEN" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="many-to-one">
@@ -277,13 +374,16 @@
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="join-column" type="orm:join-column"/>
<xs:element name="join-columns" type="orm:join-columns"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="one-to-one">
@@ -292,7 +392,9 @@
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="join-column" type="orm:join-column"/>
<xs:element name="join-columns" type="orm:join-columns"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
@@ -300,6 +402,7 @@
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
</xs:schema>

View File

@@ -1,7 +1,5 @@
<?php
/*
* $Id: Abstract.php 1393 2008-03-06 17:49:16Z guilhermeblanco $
*
* 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
@@ -57,6 +55,11 @@ abstract class AbstractQuery
*/
const HYDRATE_SINGLE_SCALAR = 4;
/**
* Very simple object hydrator (optimized for performance).
*/
const HYDRATE_SIMPLEOBJECT = 5;
/**
* @var array The parameter map of this query.
*/
@@ -73,7 +76,7 @@ abstract class AbstractQuery
protected $_resultSetMapping;
/**
* @var Doctrine\ORM\EntityManager The entity manager used by this query object.
* @var \Doctrine\ORM\EntityManager The entity manager used by this query object.
*/
protected $_em;
@@ -119,7 +122,7 @@ abstract class AbstractQuery
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
* @param Doctrine\ORM\EntityManager $entityManager
* @param \Doctrine\ORM\EntityManager $entityManager
*/
public function __construct(EntityManager $em)
{
@@ -129,7 +132,7 @@ abstract class AbstractQuery
/**
* Retrieves the associated EntityManager of this Query instance.
*
* @return Doctrine\ORM\EntityManager
* @return \Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
@@ -159,6 +162,16 @@ abstract class AbstractQuery
{
return $this->_params;
}
/**
* Get all defined parameter types.
*
* @return array The defined query parameter types.
*/
public function getParameterTypes()
{
return $this->_paramTypes;
}
/**
* Gets a query parameter.
@@ -171,6 +184,17 @@ abstract class AbstractQuery
return isset($this->_params[$key]) ? $this->_params[$key] : null;
}
/**
* Gets a query parameter type.
*
* @param mixed $key The key (index or name) of the bound parameter.
* @return mixed The parameter type of the bound parameter.
*/
public function getParameterType($key)
{
return isset($this->_paramTypes[$key]) ? $this->_paramTypes[$key] : null;
}
/**
* Gets the SQL query that corresponds to this query object.
* The returned SQL syntax depends on the connection driver that is used
@@ -188,14 +212,17 @@ abstract class AbstractQuery
* @param string $type The parameter type. If specified, the given value will be run through
* the type conversion of this type. This is usually not needed for
* strings and numeric types.
* @return Doctrine\ORM\AbstractQuery This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setParameter($key, $value, $type = null)
{
if ($type !== null) {
$this->_paramTypes[$key] = $type;
if ($type === null) {
$type = Query\ParameterTypeInferer::inferType($value);
}
$this->_paramTypes[$key] = $type;
$this->_params[$key] = $value;
return $this;
}
@@ -204,7 +231,7 @@ abstract class AbstractQuery
*
* @param array $params
* @param array $types
* @return Doctrine\ORM\AbstractQuery This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setParameters(array $params, array $types = array())
{
@@ -222,7 +249,7 @@ abstract class AbstractQuery
* Sets the ResultSetMapping that should be used for hydration.
*
* @param ResultSetMapping $rsm
* @return Doctrine\ORM\AbstractQuery
* @return \Doctrine\ORM\AbstractQuery
*/
public function setResultSetMapping(Query\ResultSetMapping $rsm)
{
@@ -233,8 +260,8 @@ abstract class AbstractQuery
/**
* Defines a cache driver to be used for caching result sets.
*
* @param Doctrine\Common\Cache\Cache $driver Cache driver
* @return Doctrine\ORM\AbstractQuery
* @param \Doctrine\Common\Cache\Cache $driver Cache driver
* @return \Doctrine\ORM\AbstractQuery
*/
public function setResultCacheDriver($resultCacheDriver = null)
{
@@ -251,7 +278,7 @@ abstract class AbstractQuery
/**
* Returns the cache driver used for caching result sets.
*
* @return Doctrine\Common\Cache\Cache Cache driver
* @return \Doctrine\Common\Cache\Cache Cache driver
*/
public function getResultCacheDriver()
{
@@ -269,7 +296,7 @@ abstract class AbstractQuery
* @param boolean $bool
* @param integer $timeToLive
* @param string $resultCacheId
* @return This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
{
@@ -287,7 +314,7 @@ abstract class AbstractQuery
* Defines how long the result cache will be active before expire.
*
* @param integer $timeToLive How long the cache entry is valid.
* @return Doctrine\ORM\AbstractQuery This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheLifetime($timeToLive)
{
@@ -313,7 +340,7 @@ abstract class AbstractQuery
* Defines if the result cache is active or not.
*
* @param boolean $expire Whether or not to force resultset cache expiration.
* @return Doctrine\ORM\AbstractQuery This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function expireResultCache($expire = true)
{
@@ -331,12 +358,32 @@ abstract class AbstractQuery
return $this->_expireResultCache;
}
/**
* Change the default fetch mode of an association for this query.
*
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
*
* @param string $class
* @param string $assocName
* @param int $fetchMode
* @return AbstractQuery
*/
public function setFetchMode($class, $assocName, $fetchMode)
{
if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
$fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
}
$this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
return $this;
}
/**
* Defines the processing mode to be used during hydration / result set transformation.
*
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
* @return Doctrine\ORM\AbstractQuery This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setHydrationMode($hydrationMode)
{
@@ -390,6 +437,31 @@ abstract class AbstractQuery
return $this->execute(array(), self::HYDRATE_SCALAR);
}
/**
* Get exactly one result or null.
*
* @throws NonUniqueResultException
* @param int $hydrationMode
* @return mixed
*/
public function getOneOrNullResult($hydrationMode = null)
{
$result = $this->execute(array(), $hydrationMode);
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
return null;
}
if (is_array($result)) {
if (count($result) > 1) {
throw new NonUniqueResultException;
}
return array_shift($result);
}
return $result;
}
/**
* Gets the single result of the query.
*
@@ -439,7 +511,7 @@ abstract class AbstractQuery
*
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
* @return Doctrine\ORM\AbstractQuery
* @return \Doctrine\ORM\AbstractQuery
*/
public function setHint($name, $value)
{
@@ -458,6 +530,16 @@ abstract class AbstractQuery
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
}
/**
* Return the key value map of query hints that are currently set.
*
* @return array
*/
public function getHints()
{
return $this->_hints;
}
/**
* Executes the query and returns an IterableResult that can be used to incrementally
* iterate over the result.
@@ -466,17 +548,27 @@ abstract class AbstractQuery
* @param integer $hydrationMode The hydration mode to use.
* @return IterableResult
*/
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
public function iterate(array $params = array(), $hydrationMode = null)
{
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
}
if ($params) {
$this->setParameters($params);
}
$stmt = $this->_doExecute();
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
$this->_doExecute($params, $hydrationMode), $this->_resultSetMapping, $this->_hints
$stmt, $this->_resultSetMapping, $this->_hints
);
}
/**
* Executes the query.
*
* @param string $params Any additional query parameters.
* @param array $params Any additional query parameters.
* @param integer $hydrationMode Processing mode to be used during the hydration process.
* @return mixed
*/
@@ -490,29 +582,25 @@ abstract class AbstractQuery
$this->setParameters($params);
}
if (isset($this->_params[0])) {
throw QueryException::invalidParameterPosition(0);
}
// Check result cache
if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
$id = $this->_getResultCacheId();
$cached = $this->_expireResultCache ? false : $cacheDriver->fetch($id);
list($key, $hash) = $this->getResultCacheId();
$cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash);
if ($cached === false) {
if ($cached === false || !isset($cached[$key])) {
// Cache miss.
$stmt = $this->_doExecute();
$result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
$stmt, $this->_resultSetMapping, $this->_hints
);
$cacheDriver->save($id, $result, $this->_resultCacheTTL);
$cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL);
return $result;
} else {
// Cache hit.
return $cached;
return $cached[$key];
}
}
@@ -533,7 +621,7 @@ abstract class AbstractQuery
* generated for you.
*
* @param string $id
* @return Doctrine\ORM\AbstractQuery This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheId($id)
{
@@ -546,12 +634,12 @@ abstract class AbstractQuery
* Will return the configured id if it exists otherwise a hash will be
* automatically generated for you.
*
* @return string $id
* @return array ($key, $hash)
*/
protected function _getResultCacheId()
protected function getResultCacheId()
{
if ($this->_resultCacheId) {
return $this->_resultCacheId;
return array($this->_resultCacheId, $this->_resultCacheId);
} else {
$params = $this->_params;
foreach ($params AS $key => $value) {
@@ -563,20 +651,23 @@ abstract class AbstractQuery
$idValues = $class->getIdentifierValues($value);
}
$params[$key] = $idValues;
} else {
$params[$key] = $value;
}
}
$sql = $this->getSql();
ksort($this->_hints);
return md5(implode(";", (array)$sql) . var_export($params, true) .
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode);
$key = implode(";", (array)$sql) . var_export($params, true) .
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode;
return array($key, md5($key));
}
}
/**
* Executes the query and returns a the resulting Statement object.
*
* @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
* @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
*/
abstract protected function _doExecute();

View File

@@ -20,7 +20,12 @@
namespace Doctrine\ORM;
use Doctrine\Common\Cache\Cache,
Doctrine\ORM\Mapping\Driver\Driver;
Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationRegistry,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\Common\Annotations\SimpleAnnotationReader,
Doctrine\ORM\Mapping\Driver\Driver,
Doctrine\ORM\Mapping\Driver\AnnotationDriver;
/**
* Configuration container for all configuration options of Doctrine.
@@ -81,7 +86,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the namespace where proxy classes reside.
*
*
* @return string
*/
public function getProxyNamespace()
@@ -92,7 +97,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the namespace where proxy classes reside.
*
*
* @param string $ns
*/
public function setProxyNamespace($ns)
@@ -114,16 +119,35 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Add a new default annotation driver with a correctly configured annotation reader.
*
*
* @param array $paths
* @return Mapping\Driver\AnnotationDriver
*/
public function newDefaultAnnotationDriver($paths = array())
{
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (array)$paths);
if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
// Register the ORM Annotations in the AnnotationRegistry
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
$reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache());
} else if (version_compare(\Doctrine\Common\Version::VERSION, '2.1.0-DEV', '>=')) {
// Register the ORM Annotations in the AnnotationRegistry
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
$reader = new AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
$reader->setIgnoreNotImportedAnnotations(true);
$reader->setEnableParsePhpImports(false);
$reader = new \Doctrine\Common\Annotations\CachedReader(
new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache()
);
} else {
$reader = new AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
}
return new AnnotationDriver($reader, (array)$paths);
}
/**
@@ -140,7 +164,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Resolves a registered namespace alias to the full namespace.
*
* @param string $entityNamespaceAlias
* @param string $entityNamespaceAlias
* @return string
* @throws MappingException
*/
@@ -164,6 +188,16 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->_attributes['entityNamespaces'] = $entityNamespaces;
}
/**
* Retrieves the list of registered entity namespace aliases.
*
* @return array
*/
public function getEntityNamespaces()
{
return $this->_attributes['entityNamespaces'];
}
/**
* Gets the cache driver implementation that is used for the mapping metadata.
*
@@ -328,7 +362,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the implementation class name of a registered custom string DQL function.
*
*
* @param string $name
* @return string
*/
@@ -371,7 +405,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the implementation class name of a registered custom numeric DQL function.
*
*
* @param string $name
* @return string
*/
@@ -414,7 +448,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the implementation class name of a registered custom date/time DQL function.
*
*
* @param string $name
* @return string
*/
@@ -465,7 +499,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Set a class metadata factory.
*
*
* @param string $cmf
*/
public function setClassMetadataFactoryName($cmfName)

View File

@@ -21,6 +21,7 @@ namespace Doctrine\ORM;
use Closure, Exception,
Doctrine\Common\EventManager,
Doctrine\Common\Persistence\ObjectManager,
Doctrine\DBAL\Connection,
Doctrine\DBAL\LockMode,
Doctrine\ORM\Mapping\ClassMetadata,
@@ -37,26 +38,26 @@ use Closure, Exception,
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class EntityManager
class EntityManager implements ObjectManager
{
/**
* The used Configuration.
*
* @var Doctrine\ORM\Configuration
* @var \Doctrine\ORM\Configuration
*/
private $config;
/**
* The database connection used by the EntityManager.
*
* @var Doctrine\DBAL\Connection
* @var \Doctrine\DBAL\Connection
*/
private $conn;
/**
* The metadata factory, used to retrieve the ORM metadata of entity classes.
*
* @var Doctrine\ORM\Mapping\ClassMetadataFactory
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
*/
private $metadataFactory;
@@ -70,14 +71,14 @@ class EntityManager
/**
* The UnitOfWork used to coordinate object-level transactions.
*
* @var Doctrine\ORM\UnitOfWork
* @var \Doctrine\ORM\UnitOfWork
*/
private $unitOfWork;
/**
* The event manager that is the central point of the event system.
*
* @var Doctrine\Common\EventManager
* @var \Doctrine\Common\EventManager
*/
private $eventManager;
@@ -91,17 +92,21 @@ class EntityManager
/**
* The proxy factory used to create dynamic proxies.
*
* @var Doctrine\ORM\Proxy\ProxyFactory
* @var \Doctrine\ORM\Proxy\ProxyFactory
*/
private $proxyFactory;
/**
* @var ExpressionBuilder The expression builder instance used to generate query expressions.
* The expression builder instance used to generate query expressions.
*
* @var \Doctrine\ORM\Query\Expr
*/
private $expressionBuilder;
/**
* Whether the EntityManager is closed or not.
*
* @var bool
*/
private $closed = false;
@@ -109,9 +114,9 @@ class EntityManager
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
*
* @param Doctrine\DBAL\Connection $conn
* @param Doctrine\ORM\Configuration $config
* @param Doctrine\Common\EventManager $eventManager
* @param \Doctrine\DBAL\Connection $conn
* @param \Doctrine\ORM\Configuration $config
* @param \Doctrine\Common\EventManager $eventManager
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
{
@@ -123,7 +128,7 @@ class EntityManager
$this->metadataFactory = new $metadataFactoryClassName;
$this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this,
$config->getProxyDir(),
@@ -134,7 +139,7 @@ class EntityManager
/**
* Gets the database connection object used by the EntityManager.
*
* @return Doctrine\DBAL\Connection
* @return \Doctrine\DBAL\Connection
*/
public function getConnection()
{
@@ -144,7 +149,7 @@ class EntityManager
/**
* Gets the metadata factory used to gather the metadata of classes.
*
* @return Doctrine\ORM\Mapping\ClassMetadataFactory
* @return \Doctrine\ORM\Mapping\ClassMetadataFactory
*/
public function getMetadataFactory()
{
@@ -163,7 +168,7 @@ class EntityManager
* ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
* </code>
*
* @return ExpressionBuilder
* @return \Doctrine\ORM\Query\Expr
*/
public function getExpressionBuilder()
{
@@ -198,13 +203,18 @@ class EntityManager
public function transactional(Closure $func)
{
$this->conn->beginTransaction();
try {
$func($this);
$return = $func($this);
$this->flush();
$this->conn->commit();
return $return ?: true;
} catch (Exception $e) {
$this->close();
$this->conn->rollback();
throw $e;
}
}
@@ -234,12 +244,12 @@ class EntityManager
*
* The class name must be the fully-qualified class name without a leading backslash
* (as it is returned by get_class($obj)) or an aliased class name.
*
*
* Examples:
* MyProject\Domain\User
* sales:PriceRequest
*
* @return Doctrine\ORM\Mapping\ClassMetadata
* @return \Doctrine\ORM\Mapping\ClassMetadata
* @internal Performance-sensitive method.
*/
public function getClassMetadata($className)
@@ -251,7 +261,7 @@ class EntityManager
* Creates a new Query object.
*
* @param string The DQL string.
* @return Doctrine\ORM\Query
* @return \Doctrine\ORM\Query
*/
public function createQuery($dql = "")
{
@@ -266,7 +276,7 @@ class EntityManager
* Creates a Query from a named query.
*
* @param string $name
* @return Doctrine\ORM\Query
* @return \Doctrine\ORM\Query
*/
public function createNamedQuery($name)
{
@@ -292,7 +302,7 @@ class EntityManager
* Creates a NativeQuery from a named native query.
*
* @param string $name
* @return Doctrine\ORM\NativeQuery
* @return \Doctrine\ORM\NativeQuery
*/
public function createNamedNativeQuery($name)
{
@@ -315,7 +325,7 @@ class EntityManager
* This effectively synchronizes the in-memory state of managed objects with the
* database.
*
* @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails.
*/
public function flush()
@@ -354,7 +364,7 @@ class EntityManager
// Check identity map first, if its already in there just return it.
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity;
return ($entity instanceof $class->name) ? $entity : null;
}
if ($class->subClasses) {
$entity = $this->find($entityName, $identifier);
@@ -394,7 +404,7 @@ class EntityManager
// Check identity map first, if its already in there just return it.
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity;
return ($entity instanceof $class->name) ? $entity : null;
}
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
@@ -403,6 +413,7 @@ class EntityManager
$entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
$this->unitOfWork->markReadOnly($entity);
return $entity;
}
@@ -439,7 +450,7 @@ class EntityManager
*
* The entity will be entered into the database at or before transaction
* commit or as a result of the flush operation.
*
*
* NOTE: The persist operation always considers entities that are not yet known to
* this EntityManager as NEW. Do not pass detached entities to the persist operation.
*
@@ -590,7 +601,7 @@ class EntityManager
/**
* Gets the EventManager used by the EntityManager.
*
* @return Doctrine\Common\EventManager
* @return \Doctrine\Common\EventManager
*/
public function getEventManager()
{
@@ -600,7 +611,7 @@ class EntityManager
/**
* Gets the Configuration used by the EntityManager.
*
* @return Doctrine\ORM\Configuration
* @return \Doctrine\ORM\Configuration
*/
public function getConfiguration()
{
@@ -621,7 +632,7 @@ class EntityManager
/**
* Check if the Entity manager is open or closed.
*
*
* @return bool
*/
public function isOpen()
@@ -632,7 +643,7 @@ class EntityManager
/**
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*
* @return Doctrine\ORM\UnitOfWork
* @return \Doctrine\ORM\UnitOfWork
*/
public function getUnitOfWork()
{
@@ -646,7 +657,7 @@ class EntityManager
* selectively iterate over the result.
*
* @param int $hydrationMode
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function getHydrator($hydrationMode)
{
@@ -661,7 +672,7 @@ class EntityManager
* Create a new instance for the given hydration mode.
*
* @param int $hydrationMode
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function newHydrator($hydrationMode)
{
@@ -678,6 +689,9 @@ class EntityManager
case Query::HYDRATE_SINGLE_SCALAR:
$hydrator = new Internal\Hydration\SingleScalarHydrator($this);
break;
case Query::HYDRATE_SIMPLEOBJECT:
$hydrator = new Internal\Hydration\SimpleObjectHydrator($this);
break;
default:
if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
$hydrator = new $class($this);
@@ -699,6 +713,14 @@ class EntityManager
return $this->proxyFactory;
}
/**
* {@inheritDoc}
*/
public function initializeObject($entity)
{
$this->unitOfWork->initializeObject($entity);
}
/**
* Factory method to create EntityManager instances.
*

View File

@@ -20,6 +20,7 @@
namespace Doctrine\ORM;
use Doctrine\DBAL\LockMode;
use Doctrine\Common\Persistence\ObjectRepository;
/**
* An EntityRepository serves as a repository for entities with generic as well as
@@ -34,7 +35,7 @@ use Doctrine\DBAL\LockMode;
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class EntityRepository
class EntityRepository implements ObjectRepository
{
/**
* @var string
@@ -47,7 +48,7 @@ class EntityRepository
protected $_em;
/**
* @var Doctrine\ORM\Mapping\ClassMetadata
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
protected $_class;
@@ -77,6 +78,17 @@ class EntityRepository
->from($this->_entityName, $alias);
}
/**
* Create a new Query instance based on a predefined metadata named query.
*
* @param string $queryName
* @return Query
*/
public function createNamedQuery($queryName)
{
return $this->_em->createQuery($this->_class->getNamedQuery($queryName));
}
/**
* Clears the repository, causing all managed entities to become detached.
*/
@@ -97,6 +109,10 @@ class EntityRepository
{
// Check identity map first
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
if (!($entity instanceof $this->_class->name)) {
return null;
}
if ($lockMode != LockMode::NONE) {
$this->_em->lock($entity, $lockMode, $lockVersion);
}
@@ -125,7 +141,7 @@ class EntityRepository
if (!$this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode);
}
}
@@ -144,11 +160,14 @@ class EntityRepository
* Finds entities by a set of criteria.
*
* @param array $criteria
* @return array
* @param array|null $orderBy
* @param int|null $limit
* @param int|null $offset
* @return array The objects.
*/
public function findBy(array $criteria)
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria);
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria, $orderBy, $limit, $offset);
}
/**
@@ -222,4 +241,4 @@ class EntityRepository
{
return $this->_class;
}
}
}

View File

@@ -0,0 +1,54 @@
<?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\Event;
/**
* Provides event arguments for the onClear event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class OnClearEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct($em)
{
$this->em = $em;
}
/**
* @return \Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->em;
}
}

View File

@@ -119,4 +119,12 @@ final class Events
* @var string
*/
const onFlush = 'onFlush';
/**
* The onClear event occurs when the EntityManager#clear() operation is invoked,
* after all references to entities have been removed from the unit of work.
*
* @var string
*/
const onClear = 'onClear';
}

View File

@@ -26,7 +26,7 @@ abstract class AbstractIdGenerator
/**
* Generates an identifier for an entity.
*
* @param Doctrine\ORM\Entity $entity
* @param \Doctrine\ORM\Entity $entity
* @return mixed
*/
abstract public function generate(EntityManager $em, $entity);

View File

@@ -47,9 +47,18 @@ class AssignedGenerator extends AbstractIdGenerator
if ($class->isIdentifierComposite) {
$idFields = $class->getIdentifierFieldNames();
foreach ($idFields as $idField) {
$value = $class->getReflectionProperty($idField)->getValue($entity);
$value = $class->reflFields[$idField]->getValue($entity);
if (isset($value)) {
$identifier[$idField] = $value;
if (isset($class->associationMappings[$idField])) {
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
throw ORMException::entityMissingForeignAssignedId($entity, $value);
}
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
} else {
$identifier[$idField] = $value;
}
} else {
throw ORMException::entityMissingAssignedId($entity);
}
@@ -58,7 +67,16 @@ class AssignedGenerator extends AbstractIdGenerator
$idField = $class->identifier[0];
$value = $class->reflFields[$idField]->getValue($entity);
if (isset($value)) {
$identifier[$idField] = $value;
if (isset($class->associationMappings[$idField])) {
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
throw ORMException::entityMissingForeignAssignedId($entity, $value);
}
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
} else {
$identifier[$idField] = $value;
}
} else {
throw ORMException::entityMissingAssignedId($entity);
}

View File

@@ -37,7 +37,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
/**
* Initializes a new sequence generator.
*
* @param Doctrine\ORM\EntityManager $em The EntityManager to use.
* @param \Doctrine\ORM\EntityManager $em The EntityManager to use.
* @param string $sequenceName The name of the sequence.
* @param integer $allocationSize The allocation size of the sequence.
*/

View File

@@ -58,7 +58,7 @@ abstract class AbstractHydrator
/**
* Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
*
* @param Doctrine\ORM\EntityManager $em The EntityManager to use.
* @param \Doctrine\ORM\EntityManager $em The EntityManager to use.
*/
public function __construct(EntityManager $em)
{
@@ -164,6 +164,11 @@ abstract class AbstractHydrator
* field names during this procedure as well as any necessary conversions on
* the values applied.
*
* @param array $data SQL Result Row
* @param array &$cache Cache for column to field result information
* @param array &$id Dql-Alias => ID-Hash
* @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
*
* @return array An array with all the fields (name => value) of the data row,
* grouped by their component alias.
*/
@@ -190,9 +195,12 @@ abstract class AbstractHydrator
continue;
} else {
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$fieldName = $this->_rsm->metaMappings[$key];
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$cache[$key]['dqlAlias']]);
$cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
}
}
@@ -203,15 +211,27 @@ abstract class AbstractHydrator
$dqlAlias = $cache[$key]['dqlAlias'];
if (isset($cache[$key]['isMetaColumn'])) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
continue;
}
if ($cache[$key]['isIdentifier']) {
$id[$dqlAlias] .= '|' . $value;
}
if (isset($cache[$key]['isMetaColumn'])) {
if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value !== null) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
if ($cache[$key]['isIdentifier']) {
$nonemptyComponents[$dqlAlias] = true;
}
}
continue;
}
// in an inheritance hierachy the same field could be defined several times.
// We overwrite this value so long we dont have a non-null value, that value we keep.
// Per definition it cannot be that a field is defined several times and has several values.
if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) {
continue;
}
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
@@ -275,4 +295,25 @@ abstract class AbstractHydrator
return $rowData;
}
protected function registerManaged($class, $entity, $data)
{
if ($class->isIdentifierComposite) {
$id = array();
foreach ($class->identifier as $fieldName) {
if (isset($class->associationMappings[$fieldName])) {
$id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']];
} else {
$id[$fieldName] = $data[$fieldName];
}
}
} else {
if (isset($class->associationMappings[$class->identifier[0]])) {
$id = array($class->identifier[0] => $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]);
} else {
$id = array($class->identifier[0] => $data[$class->identifier[0]]);
}
}
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
}
}

View File

@@ -92,6 +92,11 @@ class ArrayHydrator extends AbstractHydrator
$parent = $this->_rsm->parentAliasMap[$dqlAlias];
$path = $parent . '.' . $dqlAlias;
// missing parent data, skipping as RIGHT JOIN hydration is not supported.
if ( ! isset($nonemptyComponents[$parent]) ) {
continue;
}
// Get a reference to the right element in the result tree.
// This element will get the associated element attached.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
@@ -154,6 +159,17 @@ class ArrayHydrator extends AbstractHydrator
// It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) {
$result[] = array(0 => null);
} else {
$result[] = null;
}
++$this->_resultCounter;
continue;
}
// Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {

View File

@@ -29,7 +29,7 @@ namespace Doctrine\ORM\Internal\Hydration;
class IterableResult implements \Iterator
{
/**
* @var Doctrine\ORM\Internal\Hydration\AbstractHydrator
* @var \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
private $_hydrator;
@@ -49,7 +49,7 @@ class IterableResult implements \Iterator
private $_current = null;
/**
* @param Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator
* @param \Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator
*/
public function __construct($hydrator)
{

View File

@@ -24,7 +24,8 @@ use PDO,
Doctrine\ORM\PersistentCollection,
Doctrine\ORM\Query,
Doctrine\Common\Collections\ArrayCollection,
Doctrine\Common\Collections\Collection;
Doctrine\Common\Collections\Collection,
Doctrine\ORM\Proxy\Proxy;
/**
* The ObjectHydrator constructs an object graph out of an SQL result set.
@@ -39,9 +40,9 @@ class ObjectHydrator extends AbstractHydrator
* This local cache is maintained between hydration runs and not cleared.
*/
private $_ce = array();
/* The following parts are reinitialized on every hydration run. */
private $_identifierMap;
private $_resultPointers;
private $_idTemplate;
@@ -50,7 +51,7 @@ class ObjectHydrator extends AbstractHydrator
private $_initializedCollections = array();
private $_existingCollections = array();
//private $_createdEntities;
/** @override */
protected function _prepare()
@@ -59,7 +60,10 @@ class ObjectHydrator extends AbstractHydrator
$this->_resultPointers =
$this->_idTemplate = array();
$this->_resultCounter = 0;
if (!isset($this->_hints['deferEagerLoad'])) {
$this->_hints['deferEagerLoad'] = true;
}
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = '';
@@ -68,33 +72,29 @@ class ObjectHydrator extends AbstractHydrator
if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $class;
}
// Remember which associations are "fetch joined", so that we know where to inject
// collection stubs or proxies and where not.
if (isset($this->_rsm->relationMap[$dqlAlias])) {
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) {
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]);
}
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
$sourceClass = $this->_getClassMetadata($sourceClassName);
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true;
if ($sourceClass->subClasses) {
foreach ($sourceClass->subClasses as $sourceSubclassName) {
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true;
}
}
$this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true;
if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) {
// Mark any non-collection opposite sides as fetched, too.
if ($assoc['mappedBy']) {
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true;
$this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true;
} else {
if ($assoc['inversedBy']) {
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
$this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true;
if ($class->subClasses) {
foreach ($class->subClasses as $targetSubclassName) {
$this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
}
}
$this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true;
}
}
}
@@ -108,11 +108,17 @@ class ObjectHydrator extends AbstractHydrator
*/
protected function _cleanup()
{
$eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true;
parent::_cleanup();
$this->_identifierMap =
$this->_initializedCollections =
$this->_existingCollections =
$this->_resultPointers = array();
if ($eagerLoad) {
$this->_em->getUnitOfWork()->triggerEagerLoads();
}
}
/**
@@ -141,7 +147,7 @@ class ObjectHydrator extends AbstractHydrator
* @param object $entity The entity to which the collection belongs.
* @param string $name The name of the field on the entity that holds the collection.
*/
private function _initRelatedCollection($entity, $class, $fieldName)
private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
{
$oid = spl_object_hash($entity);
$relation = $class->associationMappings[$fieldName];
@@ -162,7 +168,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH]) ||
isset($this->_hints['fetched'][$class->name][$fieldName]) &&
isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
! $value->isInitialized()) {
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
$value->setDirty(false);
@@ -176,44 +182,59 @@ class ObjectHydrator extends AbstractHydrator
return $value;
}
/**
* Gets an entity instance.
*
*
* @param $data The instance data.
* @param $dqlAlias The DQL alias of the entity's class.
* @return object The entity.
*/
private function _getEntity(array $data, $dqlAlias)
{
$className = $this->_rsm->aliasMap[$dqlAlias];
$className = $this->_rsm->aliasMap[$dqlAlias];
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
unset($data[$discrColumn]);
}
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) {
$class = $this->_ce[$className];
$this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
}
$this->_hints['fetchAlias'] = $dqlAlias;
return $this->_uow->createEntity($className, $data, $this->_hints);
}
private function _getEntityFromIdentityMap($className, array $data)
{
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
$class = $this->_ce[$className];
/* @var $class ClassMetadata */
if ($class->isIdentifierComposite) {
$idHash = '';
foreach ($class->identifier as $fieldName) {
$idHash .= $data[$fieldName] . ' ';
if (isset($class->associationMappings[$fieldName])) {
$idHash .= $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] . ' ';
} else {
$idHash .= $data[$fieldName] . ' ';
}
}
return $this->_uow->tryGetByIdHash(rtrim($idHash), $class->rootEntityName);
} else if (isset($class->associationMappings[$class->identifier[0]])) {
return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName);
} else {
return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName);
}
}
/**
* Gets a ClassMetadata instance from the local cache.
* If the instance is not yet in the local cache, it is loaded into the
* local cache.
*
*
* @param string $className The name of the class.
* @return ClassMetadata
*/
@@ -227,21 +248,21 @@ class ObjectHydrator extends AbstractHydrator
/**
* Hydrates a single row in an SQL result set.
*
*
* @internal
* First, the data of the row is split into chunks where each chunk contains data
* that belongs to a particular component/class. Afterwards, all these chunks
* are processed, one after the other. For each chunk of class data only one of the
* following code paths is executed:
*
*
* Path A: The data chunk belongs to a joined/associated object and the association
* is collection-valued.
* Path B: The data chunk belongs to a joined/associated object and the association
* is single-valued.
* Path C: The data chunk belongs to a root result element/object that appears in the topmost
* level of the hydrated result. A typical example are the objects of the type
* specified by the FROM clause in a DQL query.
*
* specified by the FROM clause in a DQL query.
*
* @param array $data The data of the row to process.
* @param array $cache The cache to use.
* @param array $result The result array to fill.
@@ -266,7 +287,7 @@ class ObjectHydrator extends AbstractHydrator
// Hydrate the data chunks
foreach ($rowData as $dqlAlias => $data) {
$entityName = $this->_rsm->aliasMap[$dqlAlias];
if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
// It's a joined result
@@ -275,10 +296,16 @@ class ObjectHydrator extends AbstractHydrator
// seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias;
// We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
if (!isset($nonemptyComponents[$parentAlias])) {
// TODO: Add special case code where we hydrate the right join objects into identity map at least
continue;
}
// Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
$first = reset($this->_resultPointers);
$parentObject = $this->_resultPointers[$parentAlias][key($first)];
$first = reset($this->_resultPointers);
$parentObject = $first[key($first)];
} else if (isset($this->_resultPointers[$parentAlias])) {
$parentObject = $this->_resultPointers[$parentAlias];
} else {
@@ -294,19 +321,20 @@ 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;
if (isset($this->_initializedCollections[$collKey])) {
$reflFieldValue = $this->_initializedCollections[$collKey];
} else if ( ! isset($this->_existingCollections[$collKey])) {
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField);
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
}
$indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;
if ( ! $indexExists || ! $indexIsValid) {
if (isset($this->_existingCollections[$collKey])) {
// Collection exists, only look for the element in the identity map.
@@ -335,16 +363,18 @@ class ObjectHydrator extends AbstractHydrator
// Update result pointer
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
}
} else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
$coll->setOwner($parentObject, $relation);
$reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
} 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);
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH])) {
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) {
// we only need to take action if this value is null,
// we refresh the entity or its an unitialized proxy.
if (isset($nonemptyComponents[$dqlAlias])) {
$element = $this->_getEntity($data, $dqlAlias);
$reflField->setValue($parentObject, $element);
@@ -370,6 +400,8 @@ class ObjectHydrator extends AbstractHydrator
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
} else {
$this->_uow->setOriginalEntityProperty($oid, $relationField, null);
}
// else leave $reflFieldValue null for single-valued associations
} else {
@@ -381,6 +413,18 @@ class ObjectHydrator extends AbstractHydrator
// PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) {
$result[] = array(0 => null);
} else {
$result[] = null;
}
++$this->_resultCounter;
continue;
}
// check for existing result from the iterations before
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
@@ -395,6 +439,10 @@ class ObjectHydrator extends AbstractHydrator
$result[$key] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key;
}
if (isset($this->_hints['collection'])) {
$this->_hints['collection']->hydrateSet($key, $element);
}
} else {
if ($this->_rsm->isMixed) {
$element = array(0 => $element);
@@ -402,6 +450,10 @@ class ObjectHydrator extends AbstractHydrator
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
if (isset($this->_hints['collection'])) {
$this->_hints['collection']->hydrateAdd($element);
}
}
// Update result pointer

View File

@@ -0,0 +1,131 @@
<?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\Internal\Hydration;
use \PDO;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Query;
class SimpleObjectHydrator extends AbstractHydrator
{
/**
* @var ClassMetadata
*/
private $class;
private $declaringClasses = array();
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($row, $cache, $result);
}
$this->_em->getUnitOfWork()->triggerEagerLoads();
return $result;
}
protected function _prepare()
{
if (count($this->_rsm->aliasMap) == 1) {
$this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap));
if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
foreach ($this->_rsm->declaringClasses AS $column => $class) {
$this->declaringClasses[$column] = $this->_em->getClassMetadata($class);
}
}
} else {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping not containing exactly one object result.");
}
if ($this->_rsm->scalarMappings) {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.");
}
}
protected function _hydrateRow(array $sqlResult, array &$cache, array &$result)
{
$data = array();
if ($this->class->inheritanceType == ClassMetadata::INHERITANCE_TYPE_NONE) {
foreach ($sqlResult as $column => $value) {
if (!isset($cache[$column])) {
if (isset($this->_rsm->fieldMappings[$column])) {
$cache[$column]['name'] = $this->_rsm->fieldMappings[$column];
$cache[$column]['field'] = true;
} else {
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
}
}
if (isset($cache[$column]['field'])) {
$value = Type::getType($this->class->fieldMappings[$cache[$column]['name']]['type'])
->convertToPHPValue($value, $this->_platform);
}
$data[$cache[$column]['name']] = $value;
}
$entityName = $this->class->name;
} else {
$discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']);
$entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]];
unset($sqlResult[$discrColumnName]);
foreach ($sqlResult as $column => $value) {
if (!isset($cache[$column])) {
if (isset($this->_rsm->fieldMappings[$column])) {
$field = $this->_rsm->fieldMappings[$column];
$class = $this->declaringClasses[$column];
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
$cache[$column]['name'] = $field;
$cache[$column]['class'] = $class;
}
} else if (isset($this->_rsm->relationMap[$column])) {
if ($this->_rsm->relationMap[$column] == $entityName || is_subclass_of($entityName, $this->_rsm->relationMap[$column])) {
$cache[$column]['name'] = $field;
}
} else {
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
}
}
if (isset($cache[$column]['class'])) {
$value = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type'])
->convertToPHPValue($value, $this->_platform);
}
// the second and part is to prevent overwrites in case of multiple
// inheritance classes using the same property name (See AbstractHydrator)
if (isset($cache[$column]) && (!isset($data[$cache[$column]['name']]) || $value !== null)) {
$data[$cache[$column]['name']] = $value;
}
}
}
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
}
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints);
}
}

View File

@@ -63,10 +63,10 @@ class ClassMetadata extends ClassMetadataInfo
*/
public function __construct($entityName)
{
parent::__construct($entityName);
$this->reflClass = new ReflectionClass($entityName);
$this->namespace = $this->reflClass->getNamespaceName();
$this->table['name'] = $this->reflClass->getShortName();
parent::__construct($this->reflClass->getName()); // do not use $entityName, possible case-problems
}
/**
@@ -205,48 +205,6 @@ class ClassMetadata extends ClassMetadataInfo
$this->reflFields[$sourceFieldName] = $refProp;
}
/**
* Gets the (possibly quoted) column name of a mapped field for safe use
* in an SQL statement.
*
* @param string $field
* @param AbstractPlatform $platform
* @return string
*/
public function getQuotedColumnName($field, $platform)
{
return isset($this->fieldMappings[$field]['quoted']) ?
$platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) :
$this->fieldMappings[$field]['columnName'];
}
/**
* Gets the (possibly quoted) primary table name of this class for safe use
* in an SQL statement.
*
* @param AbstractPlatform $platform
* @return string
*/
public function getQuotedTableName($platform)
{
return isset($this->table['quoted']) ?
$platform->quoteIdentifier($this->table['name']) :
$this->table['name'];
}
/**
* Gets the (possibly quoted) name of the join table.
*
* @param AbstractPlatform $platform
* @return string
*/
public function getQuotedJoinTableName(array $assoc, $platform)
{
return isset($assoc['joinTable']['quoted'])
? $platform->quoteIdentifier($assoc['joinTable']['name'])
: $assoc['joinTable']['name'];
}
/**
* Creates a string representation of this instance.
*
@@ -317,6 +275,10 @@ class ClassMetadata extends ClassMetadataInfo
$serialized[] = 'isMappedSuperclass';
}
if ($this->containsForeignIdentifier) {
$serialized[] = 'containsForeignIdentifier';
}
if ($this->isVersioned) {
$serialized[] = 'isVersioned';
$serialized[] = 'versionField';
@@ -326,6 +288,14 @@ class ClassMetadata extends ClassMetadataInfo
$serialized[] = 'lifecycleCallbacks';
}
if ($this->namedQueries) {
$serialized[] = 'namedQueries';
}
if ($this->isReadOnly) {
$serialized[] = 'isReadOnly';
}
return $serialized;
}

View File

@@ -23,7 +23,8 @@ use ReflectionException,
Doctrine\ORM\ORMException,
Doctrine\ORM\EntityManager,
Doctrine\DBAL\Platforms,
Doctrine\ORM\Events;
Doctrine\ORM\Events,
Doctrine\Common\Persistence\Mapping\ClassMetadataFactory as ClassMetadataFactoryInterface;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
@@ -36,13 +37,13 @@ use ReflectionException,
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class ClassMetadataFactory
class ClassMetadataFactory implements ClassMetadataFactoryInterface
{
/**
* @var EntityManager
*/
private $em;
/**
* @var AbstractPlatform
*/
@@ -72,7 +73,7 @@ class ClassMetadataFactory
* @var bool
*/
private $initialized = false;
/**
* @param EntityManager $$em
*/
@@ -84,7 +85,7 @@ class ClassMetadataFactory
/**
* Sets the cache driver used by the factory to cache ClassMetadata instances.
*
* @param Doctrine\Common\Cache\Cache $cacheDriver
* @param \Doctrine\Common\Cache\Cache $cacheDriver
*/
public function setCacheDriver($cacheDriver)
{
@@ -94,22 +95,22 @@ class ClassMetadataFactory
/**
* Gets the cache driver used by the factory to cache ClassMetadata instances.
*
* @return Doctrine\Common\Cache\Cache
* @return \Doctrine\Common\Cache\Cache
*/
public function getCacheDriver()
{
return $this->cacheDriver;
}
public function getLoadedMetadata()
{
return $this->loadedMetadata;
}
/**
* Forces the factory to load the metadata of all classes known to the underlying
* mapping driver.
*
*
* @return array The ClassMetadata instances of all mapped classes.
*/
public function getAllMetadata()
@@ -142,7 +143,7 @@ class ClassMetadataFactory
* Gets the class metadata descriptor for a class.
*
* @param string $className The name of the class.
* @return Doctrine\ORM\Mapping\ClassMetadata
* @return \Doctrine\ORM\Mapping\ClassMetadata
*/
public function getMetadataFor($className)
{
@@ -187,7 +188,7 @@ class ClassMetadataFactory
/**
* Checks whether the factory has the metadata for a class loaded already.
*
*
* @param string $className
* @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
*/
@@ -198,7 +199,7 @@ class ClassMetadataFactory
/**
* Sets the metadata descriptor for a specific class.
*
*
* NOTE: This is only useful in very special cases, like when generating proxy classes.
*
* @param string $className
@@ -247,11 +248,13 @@ class ClassMetadataFactory
// Move down the hierarchy of parent classes, starting from the topmost class
$parent = null;
$rootEntityFound = false;
$visited = array();
foreach ($parentClasses as $className) {
if (isset($this->loadedMetadata[$className])) {
$parent = $this->loadedMetadata[$className];
if ( ! $parent->isMappedSuperclass) {
$rootEntityFound = true;
array_unshift($visited, $className);
}
continue;
@@ -260,19 +263,15 @@ class ClassMetadataFactory
$class = $this->newClassMetadataInstance($className);
if ($parent) {
if (!$parent->isMappedSuperclass) {
$class->setInheritanceType($parent->inheritanceType);
$class->setDiscriminatorColumn($parent->discriminatorColumn);
}
$class->setInheritanceType($parent->inheritanceType);
$class->setDiscriminatorColumn($parent->discriminatorColumn);
$class->setIdGeneratorType($parent->generatorType);
$this->addInheritedFields($class, $parent);
$this->addInheritedRelations($class, $parent);
$class->setIdentifier($parent->identifier);
$class->setVersioned($parent->isVersioned);
$class->setVersionField($parent->versionField);
if (!$parent->isMappedSuperclass) {
$class->setDiscriminatorMap($parent->discriminatorMap);
}
$class->setDiscriminatorMap($parent->discriminatorMap);
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
}
@@ -284,11 +283,10 @@ class ClassMetadataFactory
throw MappingException::reflectionFailure($className, $e);
}
// Verify & complete identifier mapping
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
throw MappingException::identifierRequired($className);
}
if ($parent && ! $parent->isMappedSuperclass) {
// If this class has a parent the id generator strategy is inherited.
// However this is only true if the hierachy of parents contains the root entity,
// if it consinsts of mapped superclasses these don't necessarily include the id field.
if ($parent && $rootEntityFound) {
if ($parent->isIdGeneratorSequence()) {
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
} else if ($parent->isIdGeneratorTable()) {
@@ -308,6 +306,10 @@ class ClassMetadataFactory
$class->setPrimaryTable($parent->table);
}
if ($parent && $parent->containsForeignIdentifier) {
$class->containsForeignIdentifier = true;
}
$class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
@@ -315,21 +317,14 @@ class ClassMetadataFactory
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
// verify inheritance
if (!$parent && !$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
if (count($class->discriminatorMap) == 0) {
throw MappingException::missingDiscriminatorMap($class->name);
}
if (!$class->discriminatorColumn) {
throw MappingException::missingDiscriminatorColumn($class->name);
}
}
$this->validateRuntimeMetadata($class, $parent);
$this->loadedMetadata[$className] = $class;
$parent = $class;
if ( ! $class->isMappedSuperclass) {
$rootEntityFound = true;
array_unshift($visited, $className);
}
@@ -339,11 +334,43 @@ class ClassMetadataFactory
return $loaded;
}
/**
* Validate runtime metadata is correctly defined.
*
* @param ClassMetadata $class
* @param ClassMetadata $parent
*/
protected function validateRuntimeMetadata($class, $parent)
{
// Verify & complete identifier mapping
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
throw MappingException::identifierRequired($class->name);
}
// verify inheritance
if (!$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
if (!$parent) {
if (count($class->discriminatorMap) == 0) {
throw MappingException::missingDiscriminatorMap($class->name);
}
if (!$class->discriminatorColumn) {
throw MappingException::missingDiscriminatorColumn($class->name);
}
} else if ($parent && !$class->reflClass->isAbstract() && !in_array($class->name, array_values($class->discriminatorMap))) {
// enforce discriminator map for all entities of an inheritance hierachy, otherwise problems will occur.
throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName);
}
} else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
// second condition is necessary for mapped superclasses in the middle of an inheritance hierachy
throw MappingException::noInheritanceOnMappedSuperClass($class->name);
}
}
/**
* Creates a new ClassMetadata instance for the given class name.
*
* @param string $className
* @return Doctrine\ORM\Mapping\ClassMetadata
* @return \Doctrine\ORM\Mapping\ClassMetadata
*/
protected function newClassMetadataInstance($className)
{
@@ -353,8 +380,8 @@ class ClassMetadataFactory
/**
* Adds inherited fields to the subclass mapping.
*
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
* @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
*/
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
{
@@ -375,13 +402,16 @@ class ClassMetadataFactory
/**
* Adds inherited association mappings to the subclass mapping.
*
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
* @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
*/
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->associationMappings as $field => $mapping) {
if ($parentClass->isMappedSuperclass) {
if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) {
throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field);
}
$mapping['sourceEntity'] = $subClass->name;
}
@@ -400,7 +430,7 @@ class ClassMetadataFactory
* Completes the ID generator mapping. If "auto" is specified we choose the generator
* most appropriate for the targeted database platform.
*
* @param Doctrine\ORM\Mapping\ClassMetadata $class
* @param \Doctrine\ORM\Mapping\ClassMetadata $class
*/
private function completeIdGeneratorMapping(ClassMetadataInfo $class)
{
@@ -452,4 +482,22 @@ class ClassMetadataFactory
throw new ORMException("Unknown generator type: " . $class->generatorType);
}
}
/**
* {@inheritDoc}
*/
public function isTransient($class)
{
if ( ! $this->initialized) {
$this->initialize();
}
// Check for namespace alias
if (strpos($class, ':') !== false) {
list($namespaceAlias, $simpleClassName) = explode(':', $class);
$class = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
}
return $this->driver->isTransient($class);
}
}

View File

@@ -19,6 +19,7 @@
namespace Doctrine\ORM\Mapping;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use ReflectionClass;
/**
@@ -39,7 +40,7 @@ use ReflectionClass;
* @author Jonathan H. Wage <jonwage@gmail.com>
* @since 2.0
*/
class ClassMetadataInfo
class ClassMetadataInfo implements ClassMetadata
{
/* The inheritance mapping types */
/**
@@ -118,9 +119,15 @@ class ClassMetadataInfo
const FETCH_LAZY = 2;
/**
* Specifies that an association is to be fetched when the owner of the
* association is fetched.
* association is fetched.
*/
const FETCH_EAGER = 3;
/**
* Specifies that an association is to be fetched lazy (on first access) and that
* commands such as Collection#count, Collection#slice are issued directly against
* the database if the collection is not yet initialized.
*/
const FETCH_EXTRA_LAZY = 4;
/**
* Identifies a one-to-one association.
*/
@@ -197,6 +204,13 @@ class ClassMetadataInfo
*/
public $subClasses = array();
/**
* READ-ONLY: The named queries allowed to be called directly from Repository.
*
* @var array
*/
public $namedQueries = array();
/**
* READ-ONLY: The field names of all fields that are part of the identifier/primary key
* of the mapped entity class.
@@ -255,7 +269,7 @@ class ClassMetadataInfo
* - <b>scale</b> (integer, optional, schema-only)
* The scale of a decimal column. Only valid if the column type is decimal.
*
* - <b>unique (string, optional, schema-only)</b>
[* - <b>'unique'] (string, optional, schema-only)</b>
* Whether a unique constraint should be generated for the column.
*
* @var array
@@ -321,7 +335,6 @@ class ClassMetadataInfo
* uniqueConstraints => array
*
* @var array
* @todo Rename to just $table
*/
public $table;
@@ -348,7 +361,7 @@ class ClassMetadataInfo
* - <b>mappedBy</b> (string, required for bidirectional associations)
* The name of the field that completes the bidirectional association on the owning side.
* This key must be specified on the inverse side of a bidirectional association.
*
*
* - <b>inversedBy</b> (string, required for bidirectional associations)
* The name of the field that completes the bidirectional association on the inverse side.
* This key must be specified on the owning side of a bidirectional association.
@@ -370,7 +383,12 @@ class ClassMetadataInfo
* Only valid for many-to-many mappings. Note that one-to-many associations can be mapped
* through a join table by simply mapping the association as many-to-many with a unique
* constraint on the join table.
*
*
* - <b>indexBy</b> (string, optional, to-many only)
* Specification of a field on target-entity that is used to index the collection by.
* This field HAS to be either the primary key or a unique column. Otherwise the collection
* does not contain all the entities that are actually related.
*
* A join table definition has the following structure:
* <pre>
* array(
@@ -392,6 +410,15 @@ class ClassMetadataInfo
*/
public $isIdentifierComposite = false;
/**
* READ-ONLY: Flag indicating wheather the identifier/primary key contains at least one foreign key association.
*
* This flag is necessary because some code blocks require special treatment of this cases.
*
* @var boolean
*/
public $containsForeignIdentifier = false;
/**
* READ-ONLY: The ID generator used for generating IDs for this class.
*
@@ -403,7 +430,7 @@ class ClassMetadataInfo
/**
* READ-ONLY: The definition of the sequence generator of this class. Only used for the
* SEQUENCE generation strategy.
*
*
* The definition has the following structure:
* <code>
* array(
@@ -456,6 +483,17 @@ class ClassMetadataInfo
*/
public $reflClass;
/**
* Is this entity marked as "read-only"?
*
* That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance
* optimization for entities that are immutable, either in your domain or through the relation database
* (coming from a view, or a history table for example).
*
* @var bool
*/
public $isReadOnly = false;
/**
* Initializes a new ClassMetadata instance that will hold the object-relational mapping
* metadata of the class with the given name.
@@ -634,6 +672,32 @@ class ClassMetadataInfo
$this->fieldNames[$columnName] : $columnName;
}
/**
* Gets the named query.
*
* @see ClassMetadataInfo::$namedQueries
* @throws MappingException
* @param string $queryName The query name
* @return string
*/
public function getNamedQuery($queryName)
{
if ( ! isset($this->namedQueries[$queryName])) {
throw MappingException::queryNotFound($this->name, $queryName);
}
return $this->namedQueries[$queryName];
}
/**
* Gets all named queries of the class.
*
* @return array
*/
public function getNamedQueries()
{
return $this->namedQueries;
}
/**
* Validates & completes the given field mapping.
*
@@ -643,8 +707,8 @@ class ClassMetadataInfo
protected function _validateAndCompleteFieldMapping(array &$mapping)
{
// Check mandatory fields
if ( ! isset($mapping['fieldName'])) {
throw MappingException::missingFieldName($this->name, $mapping);
if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) {
throw MappingException::missingFieldName($this->name);
}
if ( ! isset($mapping['type'])) {
// Default to string
@@ -702,22 +766,54 @@ class ClassMetadataInfo
}
$mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
// unset optional indexBy attribute if its empty
if (!isset($mapping['indexBy']) || !$mapping['indexBy']) {
unset($mapping['indexBy']);
}
// If targetEntity is unqualified, assume it is in the same namespace as
// the sourceEntity.
$mapping['sourceEntity'] = $this->name;
if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false
&& strlen($this->namespace) > 0) {
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
if (isset($mapping['targetEntity'])) {
if (strlen($this->namespace) > 0 && strpos($mapping['targetEntity'], '\\') === false) {
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
}
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
}
// Complete id mapping
if (isset($mapping['id']) && $mapping['id'] === true) {
if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) {
throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
}
if ( ! in_array($mapping['fieldName'], $this->identifier)) {
if (count($mapping['joinColumns']) >= 2) {
throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
$mapping['targetEntity'], $this->name, $mapping['fieldName']
);
}
$this->identifier[] = $mapping['fieldName'];
$this->containsForeignIdentifier = true;
}
// Check for composite key
if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
$this->isIdentifierComposite = true;
}
}
// Mandatory attributes for both sides
// Mandatory: fieldName, targetEntity
if ( ! isset($mapping['fieldName'])) {
throw MappingException::missingFieldName();
if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) {
throw MappingException::missingFieldName($this->name);
}
if ( ! isset($mapping['targetEntity'])) {
throw MappingException::missingTargetEntity($mapping['fieldName']);
}
// Mandatory and optional attributes for either side
if ( ! $mapping['mappedBy']) {
if (isset($mapping['joinTable']) && $mapping['joinTable']) {
@@ -729,14 +825,18 @@ class ClassMetadataInfo
} else {
$mapping['isOwningSide'] = false;
}
if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
throw MappingException::illegalToManyIdentifierAssoaction($this->name, $mapping['fieldName']);
}
// Fetch mode. Default fetch mode to LAZY, if not set.
if ( ! isset($mapping['fetch'])) {
$mapping['fetch'] = self::FETCH_LAZY;
}
// Cascades
$cascades = isset($mapping['cascade']) ? $mapping['cascade'] : array();
$cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array();
if (in_array('all', $cascades)) {
$cascades = array(
'remove',
@@ -752,7 +852,7 @@ class ClassMetadataInfo
$mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
$mapping['isCascadeMerge'] = in_array('merge', $cascades);
$mapping['isCascadeDetach'] = in_array('detach', $cascades);
return $mapping;
}
@@ -766,11 +866,11 @@ class ClassMetadataInfo
protected function _validateAndCompleteOneToOneMapping(array $mapping)
{
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
$mapping['isOwningSide'] = true;
}
if ($mapping['isOwningSide']) {
if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) {
// Apply default join column
@@ -779,9 +879,15 @@ class ClassMetadataInfo
'referencedColumnName' => 'id'
));
}
$uniqueContraintColumns = array();
foreach ($mapping['joinColumns'] as $key => &$joinColumn) {
if ($mapping['type'] === self::ONE_TO_ONE) {
$joinColumn['unique'] = true;
if (count($mapping['joinColumns']) == 1) {
$joinColumn['unique'] = true;
} else {
$uniqueContraintColumns[] = $joinColumn['name'];
}
}
if (empty($joinColumn['name'])) {
$joinColumn['name'] = $mapping['fieldName'] . '_id';
@@ -793,12 +899,25 @@ class ClassMetadataInfo
$mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName'])
? $joinColumn['fieldName'] : $joinColumn['name'];
}
if ($uniqueContraintColumns) {
if (!$this->table) {
throw new \RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
}
$this->table['uniqueConstraints'][$mapping['fieldName']."_uniq"] = array(
'columns' => $uniqueContraintColumns
);
}
$mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
}
//TODO: if orphanRemoval, cascade=remove is implicit!
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ?
(bool) $mapping['orphanRemoval'] : false;
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']);
}
return $mapping;
}
@@ -818,17 +937,16 @@ class ClassMetadataInfo
if ( ! isset($mapping['mappedBy'])) {
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
}
//TODO: if orphanRemoval, cascade=remove is implicit!
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ?
(bool) $mapping['orphanRemoval'] : false;
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
if (isset($mapping['orderBy'])) {
if ( ! is_array($mapping['orderBy'])) {
throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
}
}
return $mapping;
}
@@ -836,8 +954,17 @@ class ClassMetadataInfo
{
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
if ($mapping['isOwningSide']) {
$sourceShortName = strtolower(substr($mapping['sourceEntity'], strrpos($mapping['sourceEntity'], '\\') + 1));
$targetShortName = strtolower(substr($mapping['targetEntity'], strrpos($mapping['targetEntity'], '\\') + 1));
if (strpos($mapping['sourceEntity'], '\\') !== false) {
$sourceShortName = strtolower(substr($mapping['sourceEntity'], strrpos($mapping['sourceEntity'], '\\') + 1));
} else {
$sourceShortName = strtolower($mapping['sourceEntity']);
}
if (strpos($mapping['targetEntity'], '\\') !== false) {
$targetShortName = strtolower(substr($mapping['targetEntity'], strrpos($mapping['targetEntity'], '\\') + 1));
} else {
$targetShortName = strtolower($mapping['targetEntity']);
}
// owning side MUST have a join table
if ( ! isset($mapping['joinTable']['name'])) {
$mapping['joinTable']['name'] = $sourceShortName .'_' . $targetShortName;
@@ -943,6 +1070,16 @@ class ClassMetadataInfo
$this->isIdentifierComposite = (count($this->identifier) > 1);
}
/**
* Gets the mapped identifier field of this class.
*
* @return string $identifier
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* Checks whether the class has a (mapped) field with a certain name.
*
@@ -978,15 +1115,23 @@ class ClassMetadataInfo
*/
public function getIdentifierColumnNames()
{
if ($this->isIdentifierComposite) {
$columnNames = array();
foreach ($this->identifier as $idField) {
$columnNames[] = $this->fieldMappings[$idField]['columnName'];
$columnNames = array();
foreach ($this->identifier as $idProperty) {
if (isset($this->fieldMappings[$idProperty])) {
$columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
continue;
}
return $columnNames;
} else {
return array($this->fieldMappings[$this->identifier[0]]['columnName']);
// Association defined as Id field
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
$assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
$columnNames = array_merge($columnNames, $assocColumnNames);
}
return $columnNames;
}
/**
@@ -1093,7 +1238,7 @@ class ClassMetadataInfo
* Gets the type of a field.
*
* @param string $fieldName
* @return Doctrine\DBAL\Types\Type
* @return \Doctrine\DBAL\Types\Type
*/
public function getTypeOfField($fieldName)
{
@@ -1104,7 +1249,7 @@ class ClassMetadataInfo
/**
* Gets the type of a column.
*
* @return Doctrine\DBAL\Types\Type
* @return \Doctrine\DBAL\Types\Type
*/
public function getTypeOfColumn($columnName)
{
@@ -1128,7 +1273,8 @@ class ClassMetadataInfo
*/
public function getTemporaryIdTableName()
{
return $this->table['name'] . '_id_tmp';
// replace dots with underscores because PostgreSQL creates temporary tables in a special schema
return str_replace('.', '_', $this->table['name'] . '_id_tmp');
}
/**
@@ -1268,8 +1414,7 @@ class ClassMetadataInfo
* Adds an association mapping without completing/validating it.
* This is mainly used to add inherited association mappings to derived classes.
*
* @param AssociationMapping $mapping
* @param string $owningClassName The name of the class that defined this mapping.
* @param array $mapping
*/
public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
{
@@ -1285,7 +1430,6 @@ class ClassMetadataInfo
* This is mainly used to add inherited field mappings to derived classes.
*
* @param array $mapping
* @todo Rename: addInheritedFieldMapping
*/
public function addInheritedFieldMapping(array $fieldMapping)
{
@@ -1294,6 +1438,22 @@ class ClassMetadataInfo
$this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
}
/**
* INTERNAL:
* Adds a named query to this class.
*
* @throws MappingException
* @param array $queryMapping
*/
public function addNamedQuery(array $queryMapping)
{
if (isset($this->namedQueries[$queryMapping['name']])) {
throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
}
$query = str_replace('__CLASS__', $this->name, $queryMapping['query']);
$this->namedQueries[$queryMapping['name']] = $query;
}
/**
* Adds a one-to-one mapping.
*
@@ -1467,10 +1627,13 @@ class ClassMetadataInfo
public function setDiscriminatorMap(array $map)
{
foreach ($map as $value => $className) {
if (strpos($className, '\\') === false && strlen($this->namespace)) {
if (strlen($this->namespace) > 0 && strpos($className, '\\') === false) {
$className = $this->namespace . '\\' . $className;
}
$className = ltrim($className, '\\');
$this->discriminatorMap[$value] = $className;
if ($this->name == $className) {
$this->discriminatorValue = $value;
} else {
@@ -1484,6 +1647,17 @@ class ClassMetadataInfo
}
}
/**
* Checks whether the class has a named query with the given query name.
*
* @param string $fieldName
* @return boolean
*/
public function hasNamedQuery($queryName)
{
return isset($this->namedQueries[$queryName]);
}
/**
* Checks whether the class has a mapped association with the given field name.
*
@@ -1521,6 +1695,74 @@ class ClassMetadataInfo
! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
}
/**
* Is this an association that only has a single join column?
*
* @param string $fieldName
* @return bool
*/
public function isAssociationWithSingleJoinColumn($fieldName)
{
return (
isset($this->associationMappings[$fieldName]) &&
isset($this->associationMappings[$fieldName]['joinColumns'][0]) &&
!isset($this->associationMappings[$fieldName]['joinColumns'][1])
);
}
/**
* Return the single association join column (if any).
*
* @param string $fieldName
* @return string
*/
public function getSingleAssociationJoinColumnName($fieldName)
{
if (!$this->isAssociationWithSingleJoinColumn($fieldName)) {
throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
}
return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
}
/**
* Return the single association referenced join column name (if any).
*
* @param string $fieldName
* @return string
*/
public function getSingleAssociationReferencedJoinColumnName($fieldName)
{
if (!$this->isAssociationWithSingleJoinColumn($fieldName)) {
throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
}
return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
}
/**
* Used to retrieve a fieldname for either field or association from a given column,
*
* This method is used in foreign-key as primary-key contexts.
*
* @param string $columnName
* @return string
*/
public function getFieldForColumn($columnName)
{
if (isset($this->fieldNames[$columnName])) {
return $this->fieldNames[$columnName];
} else {
foreach ($this->associationMappings AS $assocName => $mapping) {
if ($this->isAssociationWithSingleJoinColumn($assocName) &&
$this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
return $assocName;
}
}
throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
}
}
/**
* Sets the ID generator used to generate IDs for instances of this class.
*
@@ -1592,4 +1834,150 @@ class ClassMetadataInfo
{
$this->versionField = $versionField;
}
}
/**
* Mark this class as read only, no change tracking is applied to it.
*
* @return void
*/
public function markReadOnly()
{
$this->isReadOnly = true;
}
/**
* A numerically indexed list of field names of this persistent class.
*
* This array includes identifier fields if present on this class.
*
* @return array
*/
public function getFieldNames()
{
return array_keys($this->fieldMappings);
}
/**
* A numerically indexed list of association names of this persistent class.
*
* This array includes identifier associations if present on this class.
*
* @return array
*/
public function getAssociationNames()
{
return array_keys($this->associationMappings);
}
/**
* Returns the target class name of the given association.
*
* @param string $assocName
* @return string
*/
public function getAssociationTargetClass($assocName)
{
if (!isset($this->associationMappings[$assocName])) {
throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
}
return $this->associationMappings[$assocName]['targetEntity'];
}
/**
* Get fully-qualified class name of this persistent class.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
*
* @param AbstractPlatform $platform
* @return array
*/
public function getQuotedIdentifierColumnNames($platform)
{
$quotedColumnNames = array();
foreach ($this->identifier as $idProperty) {
if (isset($this->fieldMappings[$idProperty])) {
$quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
: $this->fieldMappings[$idProperty]['columnName'];
continue;
}
// Association defined as Id field
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
$assocQuotedColumnNames = array_map(
function ($joinColumn) {
return isset($joinColumn['quoted'])
? $platform->quoteIdentifier($joinColumn['name'])
: $joinColumn['name'];
},
$joinColumns
);
$quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
}
return $quotedColumnNames;
}
/**
* Gets the (possibly quoted) column name of a mapped field for safe use
* in an SQL statement.
*
* @param string $field
* @param AbstractPlatform $platform
* @return string
*/
public function getQuotedColumnName($field, $platform)
{
return isset($this->fieldMappings[$field]['quoted']) ?
$platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) :
$this->fieldMappings[$field]['columnName'];
}
/**
* Gets the (possibly quoted) primary table name of this class for safe use
* in an SQL statement.
*
* @param AbstractPlatform $platform
* @return string
*/
public function getQuotedTableName($platform)
{
return isset($this->table['quoted']) ?
$platform->quoteIdentifier($this->table['name']) :
$this->table['name'];
}
/**
* Gets the (possibly quoted) name of the join table.
*
* @param AbstractPlatform $platform
* @return string
*/
public function getQuotedJoinTableName(array $assoc, $platform)
{
return isset($assoc['joinTable']['quoted'])
? $platform->quoteIdentifier($assoc['joinTable']['name'])
: $assoc['joinTable']['name'];
}
public function isAssociationInverseSide($fieldName)
{
return isset($this->associationMappings[$fieldName]) && !$this->associationMappings[$fieldName]['isOwningSide'];
}
public function getAssociationMappedByTargetField($fieldName)
{
return $this->associationMappings[$fieldName]['mappedBy'];
}
}

View File

@@ -21,11 +21,10 @@ namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\Common\Annotations\AnnotationRegistry,
Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException;
require __DIR__ . '/DoctrineAnnotations.php';
/**
* The AnnotationDriver reads the mapping metadata from docblock annotations.
*
@@ -42,7 +41,7 @@ class AnnotationDriver implements Driver
*
* @var AnnotationReader
*/
private $_reader;
protected $_reader;
/**
* The paths where to look for mapping files.
@@ -62,22 +61,22 @@ class AnnotationDriver implements Driver
* @param array
*/
protected $_classNames;
/**
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
* docblock annotations.
*
* @param $reader The AnnotationReader to use.
* @param string|array $paths One or multiple paths where mapping classes can be found.
*
* @param AnnotationReader $reader The AnnotationReader to use, duck-typed.
* @param string|array $paths One or multiple paths where mapping classes can be found.
*/
public function __construct(AnnotationReader $reader, $paths = null)
public function __construct($reader, $paths = null)
{
$this->_reader = $reader;
if ($paths) {
$this->addPaths((array) $paths);
}
}
/**
* Append lookup paths to metadata driver.
*
@@ -128,10 +127,21 @@ 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) {
$classAnnotations[get_class($annot)] = $annot;
}
}
// Evaluate Entity annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
$entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity'];
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
if ($entityAnnot->readOnly) {
$metadata->markReadOnly();
}
} else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) {
$metadata->isMappedSuperclass = true;
} else {
@@ -165,6 +175,18 @@ class AnnotationDriver implements Driver
$metadata->setPrimaryTable($primaryTable);
}
// Evaluate NamedQueries annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) {
$namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries'];
foreach ($namedQueriesAnnot->value as $namedQuery) {
$metadata->addNamedQuery(array(
'name' => $namedQuery->name,
'query' => $namedQuery->query
));
}
}
// Evaluate InheritanceType annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) {
$inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType'];
@@ -288,6 +310,10 @@ class AnnotationDriver implements Driver
throw MappingException::tableIdGeneratorNotImplemented($className);
}
} else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) {
if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) {
$mapping['id'] = true;
}
$mapping['targetEntity'] = $oneToOneAnnot->targetEntity;
$mapping['joinColumns'] = $joinColumns;
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy;
@@ -300,6 +326,7 @@ class AnnotationDriver implements Driver
$mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
$mapping['targetEntity'] = $oneToManyAnnot->targetEntity;
$mapping['cascade'] = $oneToManyAnnot->cascade;
$mapping['indexBy'] = $oneToManyAnnot->indexBy;
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyAnnot->fetch);
@@ -309,6 +336,10 @@ class AnnotationDriver implements Driver
$metadata->mapOneToMany($mapping);
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) {
if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) {
$mapping['id'] = true;
}
$mapping['joinColumns'] = $joinColumns;
$mapping['cascade'] = $manyToOneAnnot->cascade;
$mapping['inversedBy'] = $manyToOneAnnot->inversedBy;
@@ -354,6 +385,7 @@ class AnnotationDriver implements Driver
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
$mapping['cascade'] = $manyToManyAnnot->cascade;
$mapping['indexBy'] = $manyToManyAnnot->indexBy;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyAnnot->fetch);
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
@@ -367,9 +399,17 @@ class AnnotationDriver implements Driver
// Evaluate @HasLifecycleCallbacks annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
foreach ($class->getMethods() as $method) {
if ($method->isPublic()) {
// filter for the declaring class only, callbacks from parents will already be registered.
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) {
$annotations[get_class($annot)] = $annot;
}
}
if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist);
}
@@ -415,6 +455,20 @@ class AnnotationDriver implements Driver
{
$classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
// Compatibility with Doctrine Common 3.x
if ($classAnnotations && is_int(key($classAnnotations))) {
foreach ($classAnnotations as $annot) {
if ($annot instanceof \Doctrine\ORM\Mapping\Entity) {
return false;
}
if ($annot instanceof \Doctrine\ORM\Mapping\MappedSuperclass) {
return false;
}
}
return true;
}
return ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity']) &&
! isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']);
}
@@ -440,18 +494,20 @@ class AnnotationDriver implements Driver
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::LEAVES_ONLY
$iterator = new \RegexIterator(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::LEAVES_ONLY
),
'/^.+\\' . $this->_fileExtension . '$/i',
\RecursiveRegexIterator::GET_MATCH
);
foreach ($iterator as $file) {
if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
continue;
}
$sourceFile = realpath($file->getPathName());
$sourceFile = realpath($file[0]);
require_once $sourceFile;
$includedFiles[] = $sourceFile;
}
}
@@ -473,7 +529,7 @@ class AnnotationDriver implements Driver
/**
* Factory method for the Annotation Driver
*
*
* @param array|string $paths
* @param AnnotationReader $reader
* @return AnnotationDriver

View File

@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\DBAL\Schema\AbstractSchemaManager,
Doctrine\DBAL\Schema\SchemaException,
Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException,
Doctrine\Common\Util\Inflector;
@@ -54,7 +55,24 @@ class DatabaseDriver implements Driver
* @var array
*/
private $manyToManyTables = array();
/**
* @var array
*/
private $classNamesForTables = array();
/**
* @var array
*/
private $fieldNamesForColumns = array();
/**
* The namespace for the generated entities.
*
* @var string
*/
private $namespace;
/**
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
* docblock annotations.
@@ -66,17 +84,39 @@ class DatabaseDriver implements Driver
$this->_sm = $schemaManager;
}
/**
* Set tables manually instead of relying on the reverse engeneering capabilities of SchemaManager.
*
* @param array $entityTables
* @param array $manyToManyTables
* @return void
*/
public function setTables($entityTables, $manyToManyTables)
{
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
foreach ($entityTables AS $table) {
$className = $this->getClassNameForTable($table->getName());
$this->classToTableNames[$className] = $table->getName();
$this->tables[$table->getName()] = $table;
}
foreach ($manyToManyTables AS $table) {
$this->manyToManyTables[$table->getName()] = $table;
}
}
private function reverseEngineerMappingFromDatabase()
{
if ($this->tables !== null) {
return;
}
$tables = array();
foreach ($this->_sm->listTableNames() as $tableName) {
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
}
$this->tables = array();
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
foreach ($tables AS $tableName => $table) {
/* @var $table Table */
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
@@ -94,16 +134,12 @@ class DatabaseDriver implements Driver
sort($pkColumns);
sort($allForeignKeyColumns);
if ($pkColumns == $allForeignKeyColumns) {
if (count($table->getForeignKeys()) > 2) {
throw new \InvalidArgumentException("ManyToMany table '" . $tableName . "' with more or less than two foreign keys are not supported by the Database Reverese Engineering Driver.");
}
if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
$this->manyToManyTables[$tableName] = $table;
} else {
// lower-casing is necessary because of Oracle Uppercase Tablenames,
// assumption is lower-case + underscore separated.
$className = Inflector::classify(strtolower($tableName));
$className = $this->getClassNameForTable($tableName);
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
}
@@ -128,6 +164,11 @@ class DatabaseDriver implements Driver
$columns = $this->tables[$tableName]->getColumns();
$indexes = $this->tables[$tableName]->getIndexes();
try {
$primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns();
} catch(SchemaException $e) {
$primaryKeyColumns = array();
}
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
@@ -144,13 +185,13 @@ class DatabaseDriver implements Driver
$fieldMappings = array();
foreach ($columns as $column) {
$fieldMapping = array();
if (isset($indexes['primary']) && in_array($column->getName(), $indexes['primary']->getColumns())) {
if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
$fieldMapping['id'] = true;
} else if (in_array($column->getName(), $allForeignKeyColumns)) {
continue;
}
$fieldMapping['fieldName'] = Inflector::camelize(strtolower($column->getName()));
$fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false);
$fieldMapping['columnName'] = $column->getName();
$fieldMapping['type'] = strtolower((string) $column->getType());
@@ -185,8 +226,10 @@ class DatabaseDriver implements Driver
foreach ($this->manyToManyTables AS $manyTable) {
foreach ($manyTable->getForeignKeys() AS $foreignKey) {
// foreign key maps to the table of the current entity, many to many association probably exists
if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
$myFk = $foreignKey;
$otherFk = null;
foreach ($manyTable->getForeignKeys() AS $foreignKey) {
if ($foreignKey != $myFk) {
$otherFk = $foreignKey;
@@ -194,12 +237,18 @@ class DatabaseDriver implements Driver
}
}
if (!$otherFk) {
// the definition of this many to many table does not contain
// enough foreign key information to continue reverse engeneering.
continue;
}
$localColumn = current($myFk->getColumns());
$associationMapping = array();
$associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower(current($otherFk->getColumns()))));
$associationMapping['targetEntity'] = Inflector::classify(strtolower($otherFk->getForeignTableName()));
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
if (current($manyTable->getColumns())->getName() == $localColumn) {
$associationMapping['inversedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
$associationMapping['joinTable'] = array(
'name' => strtolower($manyTable->getName()),
'joinColumns' => array(),
@@ -224,7 +273,7 @@ class DatabaseDriver implements Driver
);
}
} else {
$associationMapping['mappedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
}
$metadata->mapManyToMany($associationMapping);
break;
@@ -239,8 +288,8 @@ class DatabaseDriver implements Driver
$localColumn = current($cols);
$associationMapping = array();
$associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower($localColumn)));
$associationMapping['targetEntity'] = Inflector::classify($foreignTable);
$associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
for ($i = 0; $i < count($cols); $i++) {
$associationMapping['joinColumns'][] = array(
@@ -273,4 +322,78 @@ class DatabaseDriver implements Driver
return array_keys($this->classToTableNames);
}
}
/**
* Set class name for a table.
*
* @param string $tableName
* @param string $className
* @return void
*/
public function setClassNameForTable($tableName, $className)
{
$this->classNamesForTables[$tableName] = $className;
}
/**
* Set field name for a column on a specific table.
*
* @param string $tableName
* @param string $columnName
* @param string $fieldName
* @return void
*/
public function setFieldNameForColumn($tableName, $columnName, $fieldName)
{
$this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
}
/**
* Return the mapped class name for a table if it exists. Otherwise return "classified" version.
*
* @param string $tableName
* @return string
*/
private function getClassNameForTable($tableName)
{
if (isset($this->classNamesForTables[$tableName])) {
return $this->namespace . $this->classNamesForTables[$tableName];
}
return $this->namespace . Inflector::classify(strtolower($tableName));
}
/**
* Return the mapped field name for a column, if it exists. Otherwise return camelized version.
*
* @param string $tableName
* @param string $columnName
* @param boolean $fk Whether the column is a foreignkey or not.
* @return string
*/
private function getFieldNameForColumn($tableName, $columnName, $fk = false)
{
if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) {
return $this->fieldNamesForColumns[$tableName][$columnName];
}
$columnName = strtolower($columnName);
// Replace _id if it is a foreignkey column
if ($fk) {
$columnName = str_replace('_id', '', $columnName);
}
return Inflector::camelize($columnName);
}
/**
* Set the namespace for the generated entities.
*
* @param string $namespace
* @return void
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
}
}

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
@@ -25,23 +23,41 @@ use Doctrine\Common\Annotations\Annotation;
/* Annotations */
/** @Annotation */
final class Entity extends Annotation {
public $repositoryClass;
public $readOnly = false;
}
/** @Annotation */
final class MappedSuperclass extends Annotation {}
/** @Annotation */
final class InheritanceType extends Annotation {}
/** @Annotation */
final class DiscriminatorColumn extends Annotation {
public $name;
public $fieldName; // field name used in non-object hydration (array/scalar)
public $type;
public $length;
}
/** @Annotation */
final class DiscriminatorMap extends Annotation {}
/** @Annotation */
final class Id extends Annotation {}
/** @Annotation */
final class GeneratedValue extends Annotation {
public $strategy = 'AUTO';
}
/** @Annotation */
final class Version extends Annotation {}
/** @Annotation */
final class JoinColumn extends Annotation {
public $name;
public $fieldName; // field name used in non-object hydration (array/scalar)
@@ -52,7 +68,11 @@ final class JoinColumn extends Annotation {
public $onUpdate;
public $columnDefinition;
}
/** @Annotation */
final class JoinColumns extends Annotation {}
/** @Annotation */
final class Column extends Annotation {
public $type = 'string';
public $length;
@@ -66,6 +86,8 @@ final class Column extends Annotation {
public $options = array();
public $columnDefinition;
}
/** @Annotation */
final class OneToOne extends Annotation {
public $targetEntity;
public $mappedBy;
@@ -74,64 +96,111 @@ final class OneToOne extends Annotation {
public $fetch = 'LAZY';
public $orphanRemoval = false;
}
/** @Annotation */
final class OneToMany extends Annotation {
public $mappedBy;
public $targetEntity;
public $cascade;
public $fetch = 'LAZY';
public $orphanRemoval = false;
public $indexBy;
}
/** @Annotation */
final class ManyToOne extends Annotation {
public $targetEntity;
public $cascade;
public $fetch = 'LAZY';
public $inversedBy;
}
/** @Annotation */
final class ManyToMany extends Annotation {
public $targetEntity;
public $mappedBy;
public $inversedBy;
public $cascade;
public $fetch = 'LAZY';
public $indexBy;
}
/** @Annotation */
final class ElementCollection extends Annotation {
public $tableName;
}
/** @Annotation */
final class Table extends Annotation {
public $name;
public $schema;
public $indexes;
public $uniqueConstraints;
}
/** @Annotation */
final class UniqueConstraint extends Annotation {
public $name;
public $columns;
}
/** @Annotation */
final class Index extends Annotation {
public $name;
public $columns;
}
/** @Annotation */
final class JoinTable extends Annotation {
public $name;
public $schema;
public $joinColumns = array();
public $inverseJoinColumns = array();
}
/** @Annotation */
final class SequenceGenerator extends Annotation {
public $sequenceName;
public $allocationSize = 1;
public $initialValue = 1;
}
/** @Annotation */
final class ChangeTrackingPolicy extends Annotation {}
/** @Annotation */
final class OrderBy extends Annotation {}
/* Annotations for lifecycle callbacks */
final class HasLifecycleCallbacks extends Annotation {}
final class PrePersist extends Annotation {}
final class PostPersist extends Annotation {}
final class PreUpdate extends Annotation {}
final class PostUpdate extends Annotation {}
final class PreRemove extends Annotation {}
final class PostRemove extends Annotation {}
final class PostLoad extends Annotation {}
/** @Annotation */
final class NamedQueries extends Annotation {}
/** @Annotation */
final class NamedQuery extends Annotation {
public $name;
public $query;
}
/* Annotations for lifecycle callbacks */
/** @Annotation */
final class HasLifecycleCallbacks extends Annotation {}
/** @Annotation */
final class PrePersist extends Annotation {}
/** @Annotation */
final class PostPersist extends Annotation {}
/** @Annotation */
final class PreUpdate extends Annotation {}
/** @Annotation */
final class PostUpdate extends Annotation {}
/** @Annotation */
final class PreRemove extends Annotation {}
/** @Annotation */
final class PostRemove extends Annotation {}
/** @Annotation */
final class PostLoad extends Annotation {}

View File

@@ -88,15 +88,20 @@ class DriverChain implements Driver
public function getAllClassNames()
{
$classNames = array();
$driverClasses = array();
foreach ($this->_drivers AS $namespace => $driver) {
$driverClasses = $driver->getAllClassNames();
foreach ($driverClasses AS $className) {
$oid = spl_object_hash($driver);
if (!isset($driverClasses[$oid])) {
$driverClasses[$oid] = $driver->getAllClassNames();
}
foreach ($driverClasses[$oid] AS $className) {
if (strpos($className, $namespace) === 0) {
$classNames[] = $className;
$classNames[$className] = true;
}
}
}
return array_unique($classNames);
return array_keys($classNames);
}
/**

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
@@ -22,7 +20,7 @@
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException;
Doctrine\ORM\Mapping\MappingException;
/**
* The StaticPHPDriver calls a static loadMetadata() method on your entity
@@ -31,15 +29,33 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo,
* @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 Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan H. Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class StaticPHPDriver implements Driver
{
/**
* Paths of entity directories.
*
* @var array
*/
private $_paths = array();
/**
* Map of all class names.
*
* @var array
*/
private $_classNames;
/**
* The file extension of mapping documents.
*
* @var string
*/
private $_fileExtension = '.php';
public function __construct($paths)
{
@@ -58,7 +74,7 @@ class StaticPHPDriver implements Driver
{
call_user_func_array(array($className, 'loadMetadata'), array($metadata));
}
/**
* {@inheritDoc}
* @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it?
@@ -77,13 +93,13 @@ class StaticPHPDriver implements Driver
$includedFiles = array();
foreach ($this->_paths as $path) {
if ( ! is_dir($path)) {
if (!is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::LEAVES_ONLY
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
@@ -102,7 +118,7 @@ class StaticPHPDriver implements Driver
foreach ($declared as $className) {
$rc = new \ReflectionClass($className);
$sourceFile = $rc->getFileName();
if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
if (in_array($sourceFile, $includedFiles) && !$this->isTransient($className)) {
$classes[] = $className;
}
}
@@ -119,4 +135,4 @@ class StaticPHPDriver implements Driver
{
return method_exists($className, 'loadMetadata') ? false : true;
}
}
}

View File

@@ -55,6 +55,9 @@ class XmlDriver extends AbstractFileDriver
$metadata->setCustomRepositoryClass(
isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null
);
if (isset($xmlRoot['read-only']) && $xmlRoot['read-only'] == "true") {
$metadata->markReadOnly();
}
} else if ($xmlRoot->getName() == 'mapped-superclass') {
$metadata->isMappedSuperclass = true;
} else {
@@ -69,11 +72,21 @@ class XmlDriver extends AbstractFileDriver
$metadata->setPrimaryTable($table);
// Evaluate named queries
if (isset($xmlRoot['named-queries'])) {
foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
$metadata->addNamedQuery(array(
'name' => (string)$namedQueryElement['name'],
'query' => (string)$namedQueryElement['query']
));
}
}
/* not implemented specially anyway. use table = schema.table
if (isset($xmlRoot['schema'])) {
$metadata->table['schema'] = (string)$xmlRoot['schema'];
}*/
if (isset($xmlRoot['inheritance-type'])) {
$inheritanceType = (string)$xmlRoot['inheritance-type'];
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType));
@@ -194,7 +207,13 @@ class XmlDriver extends AbstractFileDriver
}
// Evaluate <id ...> mappings
$associationIds = array();
foreach ($xmlRoot->id as $idElement) {
if ((bool)$idElement['association-key'] == true) {
$associationIds[(string)$idElement['name']] = true;
continue;
}
$mapping = array(
'id' => true,
'fieldName' => (string)$idElement['name'],
@@ -235,6 +254,10 @@ class XmlDriver extends AbstractFileDriver
'targetEntity' => (string)$oneToOneElement['target-entity']
);
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
if (isset($oneToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToOneElement['fetch']);
}
@@ -262,8 +285,8 @@ class XmlDriver extends AbstractFileDriver
$mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade);
}
if (isset($oneToOneElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$oneToOneElement->{'orphan-removal'};
if (isset($oneToOneElement['orphan-removal'])) {
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphan-removal'];
}
$metadata->mapOneToOne($mapping);
@@ -287,8 +310,8 @@ class XmlDriver extends AbstractFileDriver
$mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade);
}
if (isset($oneToManyElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$oneToManyElement->{'orphan-removal'};
if (isset($oneToManyElement['orphan-removal'])) {
$mapping['orphanRemoval'] = (bool)$oneToManyElement['orphan-removal'];
}
if (isset($oneToManyElement->{'order-by'})) {
@@ -299,6 +322,12 @@ class XmlDriver extends AbstractFileDriver
$mapping['orderBy'] = $orderBy;
}
if (isset($oneToManyElement['index-by'])) {
$mapping['indexBy'] = (string)$oneToManyElement['index-by'];
} else if (isset($oneToManyElement->{'index-by'})) {
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
}
$metadata->mapOneToMany($mapping);
}
}
@@ -311,6 +340,10 @@ class XmlDriver extends AbstractFileDriver
'targetEntity' => (string)$manyToOneElement['target-entity']
);
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
if (isset($manyToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToOneElement['fetch']);
}
@@ -325,9 +358,6 @@ class XmlDriver extends AbstractFileDriver
$joinColumns[] = $this->_getJoinColumnMapping($manyToOneElement->{'join-column'});
} else if (isset($manyToOneElement->{'join-columns'})) {
foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
if (!isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
}
}
@@ -401,6 +431,12 @@ class XmlDriver extends AbstractFileDriver
$mapping['orderBy'] = $orderBy;
}
if (isset($manyToManyElement['index-by'])) {
$mapping['indexBy'] = (string)$manyToManyElement['index-by'];
} else if (isset($manyToManyElement->{'index-by'})) {
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
}
$metadata->mapManyToMany($mapping);
}
}

View File

@@ -49,6 +49,9 @@ class YamlDriver extends AbstractFileDriver
$metadata->setCustomRepositoryClass(
isset($element['repositoryClass']) ? $element['repositoryClass'] : null
);
if (isset($element['readOnly']) && $element['readOnly'] == true) {
$metadata->markReadOnly();
}
} else if ($element['type'] == 'mappedSuperclass') {
$metadata->isMappedSuperclass = true;
} else {
@@ -62,6 +65,21 @@ class YamlDriver extends AbstractFileDriver
}
$metadata->setPrimaryTable($table);
// Evaluate named queries
if (isset($element['namedQueries'])) {
foreach ($element['namedQueries'] as $name => $queryMapping) {
if (is_string($queryMapping)) {
$queryMapping = array('query' => $queryMapping);
}
if ( ! isset($queryMapping['name'])) {
$queryMapping['name'] = $name;
}
$metadata->addNamedQuery($queryMapping);
}
}
/* not implemented specially anyway. use table = schema.table
if (isset($element['schema'])) {
$metadata->table['schema'] = $element['schema'];
@@ -135,9 +153,15 @@ class YamlDriver extends AbstractFileDriver
}
}
$associationIds = array();
if (isset($element['id'])) {
// Evaluate identifier settings
foreach ($element['id'] as $name => $idElement) {
if (isset($idElement['associationKey']) && $idElement['associationKey'] == true) {
$associationIds[$name] = true;
continue;
}
if (!isset($idElement['type'])) {
throw MappingException::propertyTypeIsRequired($className, $name);
}
@@ -234,6 +258,10 @@ class YamlDriver extends AbstractFileDriver
'targetEntity' => $oneToOneElement['targetEntity']
);
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
if (isset($oneToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']);
}
@@ -266,6 +294,10 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $oneToOneElement['cascade'];
}
if (isset($oneToOneElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphanRemoval'];
}
$metadata->mapOneToOne($mapping);
}
}
@@ -287,10 +319,18 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $oneToManyElement['cascade'];
}
if (isset($oneToManyElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$oneToManyElement['orphanRemoval'];
}
if (isset($oneToManyElement['orderBy'])) {
$mapping['orderBy'] = $oneToManyElement['orderBy'];
}
if (isset($oneToManyElement['indexBy'])) {
$mapping['indexBy'] = $oneToManyElement['indexBy'];
}
$metadata->mapOneToMany($mapping);
}
}
@@ -303,6 +343,10 @@ class YamlDriver extends AbstractFileDriver
'targetEntity' => $manyToOneElement['targetEntity']
);
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
if (isset($manyToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']);
}
@@ -331,6 +375,10 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $manyToOneElement['cascade'];
}
if (isset($manyToOneElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$manyToOneElement['orphanRemoval'];
}
$metadata->mapManyToOne($mapping);
}
}
@@ -386,10 +434,18 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $manyToManyElement['cascade'];
}
if (isset($manyToManyElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
}
if (isset($manyToManyElement['orderBy'])) {
$mapping['orderBy'] = $manyToManyElement['orderBy'];
}
if (isset($manyToManyElement['indexBy'])) {
$mapping['indexBy'] = $manyToManyElement['indexBy'];
}
$metadata->mapManyToMany($mapping);
}
}
@@ -450,6 +506,6 @@ class YamlDriver extends AbstractFileDriver
*/
protected function _loadMappingFile($file)
{
return \Symfony\Component\Yaml\Yaml::load($file);
return \Symfony\Component\Yaml\Yaml::parse($file);
}
}

View File

@@ -48,9 +48,9 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("Id generators can't be used with a composite id.");
}
public static function missingFieldName()
public static function missingFieldName($entity)
{
return new self("The association mapping misses the 'fieldName' attribute.");
return new self("The field or association mapping misses the 'fieldName' attribute in entity '$entity'.");
}
public static function missingTargetEntity($fieldName)
@@ -68,9 +68,14 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("No mapping file found named '$fileName' for class '$entityName'.");
}
public static function mappingNotFound($fieldName)
public static function mappingNotFound($className, $fieldName)
{
return new self("No mapping found for field '$fieldName'.");
return new self("No mapping found for field '$fieldName' on class '$className'.");
}
public static function queryNotFound($className, $queryName)
{
return new self("No query found named '$queryName' on class '$className'.");
}
public static function oneToManyRequiresMappedBy($fieldName)
@@ -160,6 +165,10 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once');
}
public static function duplicateQueryMapping($entity, $queryName) {
return new self('Query named "'.$queryName.'" in "'.$entity.'" was already declared, but it must be declared only once');
}
public static function singleIdNotAllowedOnCompositePrimaryKey($entity) {
return new self('Single id is not allowed on composite primary key in entity '.$entity);
}
@@ -227,4 +236,61 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("Duplicate definition of column '".$columnName."' on entity '".$className."' in a field or discriminator column mapping.");
}
public static function illegalToManyAssocationOnMappedSuperclass($className, $field)
{
return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '".$className."#".$field."'.");
}
/**
* @param string $className
* @param string $targetEntity
* @param string $targetField
* @return self
*/
public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId($className, $targetEntity, $targetField)
{
return new self("It is not possible to map entity '".$className."' with a composite primary key ".
"as part of the primary key of another entity '".$targetEntity."#".$targetField."'.");
}
public static function noSingleAssociationJoinColumnFound($className, $field)
{
return new self("'$className#$field' is not an association with a single join column.");
}
public static function noFieldNameFoundForColumn($className, $column)
{
return new self("Cannot find a field on '$className' that is mapped to column '$column'. Either the ".
"field does not exist or an association exists but it has multiple join columns.");
}
public static function illegalOrphanRemovalOnIdentifierAssociation($className, $field)
{
return new self("The orphan removal option is not allowed on an association that is ".
"part of the identifier in '$className#$field'.");
}
public static function illegalInverseIdentifierAssocation($className, $field)
{
return new self("An inverse association is not allowed to be identifier in '$className#$field'.");
}
public static function illegalToManyIdentifierAssoaction($className, $field)
{
return new self("Many-to-many or one-to-many associations are not allowed to be identifier in '$className#$field'.");
}
public static function noInheritanceOnMappedSuperClass($className)
{
return new self("Its not supported to define inheritance information on a mapped superclass '" . $className . "'.");
}
public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName)
{
return new self(
"Entity '" . $className . "' has to be part of the descriminator map of '" . $rootClassName . "' " .
"to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " .
"to avoid this exception from occuring."
);
}
}

View File

@@ -34,10 +34,25 @@ class ORMException extends Exception
return new self("It's a requirement to specify a Metadata Driver and pass it ".
"to Doctrine\ORM\Configuration::setMetadataDriverImpl().");
}
public static function entityMissingForeignAssignedId($entity, $relatedEntity)
{
return new self(
"Entity of type " . get_class($entity) . " has identity through a foreign entity " . get_class($relatedEntity) . ", " .
"however this entity has no ientity itself. You have to call EntityManager#persist() on the related entity " .
"and make sure it an identifier was generated before trying to persist '" . get_class($entity) . "'. In case " .
"of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call " .
"EntityManager#flush() between both persist operations."
);
}
public static function entityMissingAssignedId($entity)
{
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID. " .
"The identifier generation strategy for this entity requires the ID field to be populated before ".
"EntityManager#persist() is called. If you want automatically generated identifiers instead " .
"you need to adjust the metadata mapping accordingly."
);
}
public static function unrecognizedField($field)
@@ -45,6 +60,15 @@ class ORMException extends Exception
return new self("Unrecognized field: $field");
}
/**
* @param string $className
* @param string $field
*/
public static function invalidOrientation($className, $field)
{
return new self("Invalid order by orientation specified for " . $className . "#" . $field);
}
public static function invalidFlushMode($mode)
{
return new self("'$mode' is an invalid flush mode.");

View File

@@ -33,6 +33,7 @@ class OptimisticLockException extends ORMException
public function __construct($msg, $entity)
{
parent::__construct($msg);
$this->entity = $entity;
}

View File

@@ -59,14 +59,14 @@ final class PersistentCollection implements Collection
* The association mapping the collection belongs to.
* This is currently either a OneToManyMapping or a ManyToManyMapping.
*
* @var Doctrine\ORM\Mapping\AssociationMapping
* @var array
*/
private $association;
/**
* The EntityManager that manages the persistence of the collection.
*
* @var Doctrine\ORM\EntityManager
* @var \Doctrine\ORM\EntityManager
*/
private $em;
@@ -93,21 +93,21 @@ final class PersistentCollection implements Collection
/**
* Whether the collection has already been initialized.
*
*
* @var boolean
*/
private $initialized = true;
/**
* The wrapped Collection instance.
*
*
* @var Collection
*/
private $coll;
/**
* Creates a new persistent collection.
*
*
* @param EntityManager $em The EntityManager the collection will be associated with.
* @param ClassMetadata $class The class descriptor of the entity type of this collection.
* @param array The collection elements.
@@ -144,7 +144,7 @@ final class PersistentCollection implements Collection
{
return $this->owner;
}
public function getTypeClass()
{
return $this->typeClass;
@@ -154,7 +154,7 @@ final class PersistentCollection implements Collection
* INTERNAL:
* Adds an element to a collection during hydration. This will automatically
* complete bidirectional associations in the case of a one-to-many association.
*
*
* @param mixed $element The element to add.
*/
public function hydrateAdd($element)
@@ -172,7 +172,7 @@ final class PersistentCollection implements Collection
$this->owner);
}
}
/**
* INTERNAL:
* Sets a keyed element in the collection during hydration.
@@ -265,13 +265,13 @@ final class PersistentCollection implements Collection
/**
* INTERNAL: Gets the association mapping of the collection.
*
* @return Doctrine\ORM\Mapping\AssociationMapping
* @return \Doctrine\ORM\Mapping\AssociationMapping
*/
public function getMapping()
{
return $this->association;
}
/**
* Marks this collection as changed/dirty.
*/
@@ -306,17 +306,17 @@ final class PersistentCollection implements Collection
{
$this->isDirty = $dirty;
}
/**
* Sets the initialized flag of the collection, forcing it into that state.
*
*
* @param boolean $bool
*/
public function setInitialized($bool)
{
$this->initialized = $bool;
}
/**
* Checks whether this collection has been initialized.
*
@@ -377,7 +377,7 @@ final class PersistentCollection implements Collection
$this->em->getUnitOfWork()->getCollectionPersister($this->association)
->deleteRows($this, $element);
}*/
$this->initialize();
$removed = $this->coll->removeElement($element);
if ($removed) {
@@ -404,23 +404,13 @@ final class PersistentCollection implements Collection
*/
public function contains($element)
{
/* DRAFT
if ($this->initialized) {
return $this->coll->contains($element);
} else {
if ($element is MANAGED) {
if ($this->coll->contains($element)) {
return true;
}
$exists = check db for existence;
if ($exists) {
$this->coll->add($element);
}
return $exists;
}
return false;
}*/
if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
return $this->coll->contains($element) ||
$this->em->getUnitOfWork()
->getCollectionPersister($this->association)
->contains($this, $element);
}
$this->initialize();
return $this->coll->contains($element);
}
@@ -475,6 +465,12 @@ final class PersistentCollection implements Collection
*/
public function count()
{
if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
return $this->em->getUnitOfWork()
->getCollectionPersister($this->association)
->count($this) + ($this->isDirty ? $this->coll->count() : 0);
}
$this->initialize();
return $this->coll->count();
}
@@ -507,7 +503,7 @@ final class PersistentCollection implements Collection
$this->initialize();
return $this->coll->isEmpty();
}
/**
* {@inheritdoc}
*/
@@ -534,7 +530,7 @@ final class PersistentCollection implements Collection
$this->initialize();
return $this->coll->filter($p);
}
/**
* {@inheritdoc}
*/
@@ -552,7 +548,7 @@ final class PersistentCollection implements Collection
$this->initialize();
return $this->coll->partition($p);
}
/**
* {@inheritdoc}
*/
@@ -571,18 +567,22 @@ final class PersistentCollection implements Collection
return;
}
if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
// we need to initialize here, as orphan removal acts like implicit cascadeRemove,
// hence for event listeners we need the objects in memory.
$this->initialize();
foreach ($this->coll as $element) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
}
}
$this->coll->clear();
$this->initialized = true; // direct call, {@link initialize()} is too expensive
if ($this->association['isOwningSide']) {
$this->changed();
$this->em->getUnitOfWork()->scheduleCollectionDeletion($this);
$this->takeSnapshot();
}
}
/**
* Called by PHP when this collection is serialized. Ensures that only the
* elements are properly serialized.
@@ -594,7 +594,7 @@ final class PersistentCollection implements Collection
{
return array('coll', 'initialized');
}
/* ArrayAccess implementation */
/**
@@ -632,12 +632,12 @@ final class PersistentCollection implements Collection
{
return $this->remove($offset);
}
public function key()
{
return $this->coll->key();
}
/**
* Gets the element of the collection at the current iterator position.
*/
@@ -645,7 +645,7 @@ final class PersistentCollection implements Collection
{
return $this->coll->current();
}
/**
* Moves the internal iterator position to the next element.
*/
@@ -653,7 +653,7 @@ final class PersistentCollection implements Collection
{
return $this->coll->next();
}
/**
* Retrieves the wrapped Collection instance.
*/
@@ -675,6 +675,15 @@ final class PersistentCollection implements Collection
*/
public function slice($offset, $length = null)
{
if ( ! $this->initialized &&
! $this->isDirty &&
$this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
return $this->em->getUnitOfWork()
->getCollectionPersister($this->association)
->slice($this, $offset, $length);
}
$this->initialize();
return $this->coll->slice($offset, $length);
}

View File

@@ -36,19 +36,19 @@ abstract class AbstractCollectionPersister
protected $_em;
/**
* @var Doctrine\DBAL\Connection
* @var \Doctrine\DBAL\Connection
*/
protected $_conn;
/**
* @var Doctrine\ORM\UnitOfWork
* @var \Doctrine\ORM\UnitOfWork
*/
protected $_uow;
/**
* Initializes a new instance of a class derived from AbstractCollectionPersister.
*
* @param Doctrine\ORM\EntityManager $em
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{
@@ -65,9 +65,11 @@ abstract class AbstractCollectionPersister
public function delete(PersistentCollection $coll)
{
$mapping = $coll->getMapping();
if ( ! $mapping['isOwningSide']) {
return; // ignore inverse side
}
$sql = $this->_getDeleteSQL($coll);
$this->_conn->executeUpdate($sql, $this->_getDeleteSQLParameters($coll));
}
@@ -96,9 +98,11 @@ abstract class AbstractCollectionPersister
public function update(PersistentCollection $coll)
{
$mapping = $coll->getMapping();
if ( ! $mapping['isOwningSide']) {
return; // ignore inverse side
}
$this->deleteRows($coll);
//$this->updateRows($coll);
$this->insertRows($coll);
@@ -108,6 +112,7 @@ abstract class AbstractCollectionPersister
{
$deleteDiff = $coll->getDeleteDiff();
$sql = $this->_getDeleteRowSQL($coll);
foreach ($deleteDiff as $element) {
$this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element));
}
@@ -120,11 +125,37 @@ abstract class AbstractCollectionPersister
{
$insertDiff = $coll->getInsertDiff();
$sql = $this->_getInsertRowSQL($coll);
foreach ($insertDiff as $element) {
$this->_conn->executeUpdate($sql, $this->_getInsertRowSQLParameters($coll, $element));
}
}
public function count(PersistentCollection $coll)
{
throw new \BadMethodCallException("Counting the size of this persistent collection is not supported by this CollectionPersister.");
}
public function slice(PersistentCollection $coll, $offset, $length = null)
{
throw new \BadMethodCallException("Slicing elements is not supported by this CollectionPersister.");
}
public function contains(PersistentCollection $coll, $element)
{
throw new \BadMethodCallException("Checking for existance of an element is not supported by this CollectionPersister.");
}
public function containsKey(PersistentCollection $coll, $key)
{
throw new \BadMethodCallException("Checking for existance of a key is not supported by this CollectionPersister.");
}
public function get(PersistentCollection $coll, $index)
{
throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister.");
}
/**
* Gets the SQL statement used for deleting a row from the collection.
*

View File

@@ -28,27 +28,23 @@ use Doctrine\ORM\Mapping\ClassMetadata,
* types in the hierarchy.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
{
/**
* Map from column names to class names that declare the field the column is mapped to.
*
* @var array
*/
private $_declaringClassMap = array();
/**
* {@inheritdoc}
*/
protected function _prepareInsertData($entity)
{
$data = parent::_prepareInsertData($entity);
// Populate the discriminator column
$discColumn = $this->_class->discriminatorColumn;
$this->_columnTypes[$discColumn['name']] = $discColumn['type'];
$data[$this->_getDiscriminatorColumnTableName()][$discColumn['name']] = $this->_class->discriminatorValue;
return $data;
}
@@ -62,46 +58,22 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
/**
* {@inheritdoc}
*/
protected function _processSQLResult(array $sqlResult)
{
$data = array();
$discrColumnName = $this->_platform->getSQLResultCasing($this->_class->discriminatorColumn['name']);
$entityName = $this->_class->discriminatorMap[$sqlResult[$discrColumnName]];
unset($sqlResult[$discrColumnName]);
foreach ($sqlResult as $column => $value) {
$realColumnName = $this->_resultColumnNames[$column];
if (isset($this->_declaringClassMap[$column])) {
$class = $this->_declaringClassMap[$column];
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
$field = $class->fieldNames[$realColumnName];
if (isset($data[$field])) {
$data[$realColumnName] = $value;
} else {
$data[$field] = Type::getType($class->fieldMappings[$field]['type'])
->convertToPHPValue($value, $this->_platform);
}
}
} else {
$data[$realColumnName] = $value;
}
}
return array($entityName, $data);
}
/**
* {@inheritdoc}
*/
protected function _getSelectColumnSQL($field, ClassMetadata $class)
protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r')
{
$columnName = $class->columnNames[$field];
$sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform);
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform);
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
if ( ! isset($this->_resultColumnNames[$columnAlias])) {
$this->_resultColumnNames[$columnAlias] = $columnName;
$this->_declaringClassMap[$columnAlias] = $class;
}
$this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
return "$sql AS $columnAlias";
return $sql . ' AS ' . $columnAlias;
}
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className)
{
$columnAlias = $joinColumnName . $this->_sqlAliasCounter++;
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
$this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName);
return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,13 +20,17 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\ORMException,
Doctrine\ORM\Mapping\ClassMetadata;
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\DBAL\LockMode,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\Query\ResultSetMapping;
/**
* The joined subclass persister maps a single entity instance to several tables in the
* database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
* @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
*/
@@ -63,7 +67,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
* This function finds the ClassMetadata instance in an inheritance hierarchy
* that is responsible for enabling versioning.
*
* @return Doctrine\ORM\Mapping\ClassMetadata
* @return \Doctrine\ORM\Mapping\ClassMetadata
*/
private function _getVersionedClassMetadata()
{
@@ -107,10 +111,6 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
return;
}
if ($this->_class->isVersioned) {
$versionedClass = $this->_getVersionedClassMetadata();
}
$postInsertIds = array();
$idGen = $this->_class->idGenerator;
$isPostInsertId = $idGen->isPostInsertGenerator();
@@ -144,9 +144,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Execute insert on root table
$paramIndex = 1;
foreach ($insertData[$rootTableName] as $columnName => $value) {
$rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
}
$rootTableStmt->execute();
if ($isPostInsertId) {
@@ -161,12 +163,17 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
foreach ($subTableStmts as $tableName => $stmt) {
$data = isset($insertData[$tableName]) ? $insertData[$tableName] : array();
$paramIndex = 1;
foreach ((array) $id as $idVal) {
$stmt->bindValue($paramIndex++, $idVal);
foreach ((array) $id as $idName => $idVal) {
$type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING;
$stmt->bindValue($paramIndex++, $idVal, $type);
}
foreach ($data as $columnName => $value) {
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
}
$stmt->execute();
}
}
@@ -176,8 +183,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$stmt->closeCursor();
}
if (isset($versionedClass)) {
$this->_assignDefaultVersionValue($versionedClass, $entity, $id);
if ($this->_class->isVersioned) {
$this->assignDefaultVersionValue($entity, $id);
}
$this->_queuedInserts = array();
@@ -192,7 +199,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
{
$updateData = $this->_prepareUpdateData($entity);
if ($isVersioned = $this->_class->isVersioned) {
if (($isVersioned = $this->_class->isVersioned) != false) {
$versionedClass = $this->_getVersionedClassMetadata();
$versionedTable = $versionedClass->table['name'];
}
@@ -201,13 +208,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
foreach ($updateData as $tableName => $data) {
$this->_updateTable($entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName);
}
// Make sure the table with the version column is updated even if no columns on that
// table were affected.
if ($isVersioned && ! isset($updateData[$versionedTable])) {
$this->_updateTable($entity, $versionedClass->getQuotedTableName($this->_platform), array(), true);
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
$this->_assignDefaultVersionValue($this->_class, $entity, $id);
$this->assignDefaultVersionValue($entity, $id);
}
}
}
@@ -230,6 +238,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
} else {
// Delete from all tables individually, starting from this class' table up to the root table.
$this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id);
foreach ($this->_class->parentClasses as $parentClass) {
$this->_conn->delete($this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id);
}
@@ -239,13 +248,17 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
/**
* {@inheritdoc}
*/
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0)
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
{
$idColumns = $this->_class->getIdentifierColumnNames();
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
// Create the column list fragment only once
if ($this->_selectColumnListSql === null) {
$this->_rsm = new ResultSetMapping();
$this->_rsm->addEntityResult($this->_class->name, 'r');
// Add regular columns
$columnList = '';
foreach ($this->_class->fieldMappings as $fieldName => $mapping) {
@@ -263,12 +276,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$this->_getSQLTableAlias($assoc2['inherited'])
: $baseTableAlias;
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= ", $tableAlias.$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
);
}
}
}
@@ -283,7 +294,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
}
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
$this->_resultColumnNames[$resultColumnName] = $discrColumn;
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
}
// INNER JOIN parent tables
@@ -318,12 +330,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE
&& ! isset($assoc2['inherited'])) {
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
);
}
}
}
@@ -343,19 +353,25 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
$orderBySql = '';
if ($assoc != null && isset($assoc['orderBy'])) {
$orderBySql = $this->_getCollectionOrderBySQL($assoc['orderBy'], $baseTableAlias);
}
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : '';
if ($this->_selectColumnListSql === null) {
$this->_selectColumnListSql = $columnList;
}
return 'SELECT ' . $this->_selectColumnListSql
$lockSql = '';
if ($lockMode == LockMode::PESSIMISTIC_READ) {
$lockSql = ' ' . $this->_platform->getReadLockSql();
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
$lockSql = ' ' . $this->_platform->getWriteLockSql();
}
return $this->_platform->modifyLimitQuery('SELECT ' . $this->_selectColumnListSql
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias
. $joinSql
. ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql;
. ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql, $limit, $offset)
. $lockSql;
}
/**
@@ -423,4 +439,13 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
return $columns;
}
/**
* {@inheritdoc}
*/
protected function assignDefaultVersionValue($entity, $id)
{
$value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id);
$this->_class->setFieldValue($entity, $this->_class->versionField, $value);
}
}

View File

@@ -21,7 +21,8 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\PersistentCollection,
Doctrine\ORM\UnitOfWork;
/**
* Persister for many-to-many collections.
@@ -38,9 +39,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
*/
protected function _getDeleteRowSQL(PersistentCollection $coll)
{
$mapping = $coll->getMapping();
$mapping = $coll->getMapping();
$joinTable = $mapping['joinTable'];
$columns = $mapping['joinTableColumns'];
$columns = $mapping['joinTableColumns'];
return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
}
@@ -117,13 +119,21 @@ class ManyToManyPersister extends AbstractCollectionPersister
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
if ($isComposite) {
$params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
if ($class1->containsForeignIdentifier) {
$params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
} else {
$params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
}
} else {
$params[] = array_pop($identifier1);
}
} else {
if ($isComposite) {
$params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
if ($class2->containsForeignIdentifier) {
$params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
} else {
$params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
}
} else {
$params[] = array_pop($identifier2);
}
@@ -173,4 +183,119 @@ class ManyToManyPersister extends AbstractCollectionPersister
return $params;
}
/**
* {@inheritdoc}
*/
public function count(PersistentCollection $coll)
{
$params = array();
$mapping = $coll->getMapping();
$class = $this->_em->getClassMetadata($mapping['sourceEntity']);
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
if ($mapping['isOwningSide']) {
$joinTable = $mapping['joinTable'];
$joinColumns = $mapping['relationToSourceKeyColumns'];
} else {
$mapping = $this->_em->getClassMetadata($mapping['targetEntity'])->associationMappings[$mapping['mappedBy']];
$joinTable = $mapping['joinTable'];
$joinColumns = $mapping['relationToTargetKeyColumns'];
}
$whereClause = '';
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($joinColumns[$joinTableColumn])) {
if ($whereClause !== '') {
$whereClause .= ' AND ';
}
$whereClause .= "$joinTableColumn = ?";
if ($class->containsForeignIdentifier) {
$params[] = $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])];
} else {
$params[] = $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
}
}
}
$sql = 'SELECT count(*) FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
return $this->_conn->fetchColumn($sql, $params);
}
/**
* @param PersistentCollection $coll
* @param int $offset
* @param int $length
* @return array
*/
public function slice(PersistentCollection $coll, $offset, $length = null)
{
$mapping = $coll->getMapping();
return $this->_em->getUnitOfWork()
->getEntityPersister($mapping['targetEntity'])
->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
}
/**
* @param PersistentCollection $coll
* @param object $element
*/
public function contains(PersistentCollection $coll, $element)
{
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
return false;
}
$params = array();
$mapping = $coll->getMapping();
if (!$mapping['isOwningSide']) {
$sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']);
$targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
$sourceId = $uow->getEntityIdentifier($element);
$targetId = $uow->getEntityIdentifier($coll->getOwner());
$mapping = $sourceClass->associationMappings[$mapping['mappedBy']];
} else {
$sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
$sourceId = $uow->getEntityIdentifier($coll->getOwner());
$targetId = $uow->getEntityIdentifier($element);
}
$joinTable = $mapping['joinTable'];
$whereClause = '';
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
if ($whereClause !== '') {
$whereClause .= ' AND ';
}
$whereClause .= "$joinTableColumn = ?";
if ($targetClass->containsForeignIdentifier) {
$params[] = $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
} else {
$params[] = $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
}
} else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
if ($whereClause !== '') {
$whereClause .= ' AND ';
}
$whereClause .= "$joinTableColumn = ?";
if ($sourceClass->containsForeignIdentifier) {
$params[] = $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
} else {
$params[] = $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
}
}
}
$sql = 'SELECT 1 FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
return (bool)$this->_conn->fetchColumn($sql, $params);
}
}

View File

@@ -21,7 +21,8 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\PersistentCollection,
Doctrine\ORM\UnitOfWork;
/**
* Persister for one-to-many collections.
@@ -116,4 +117,69 @@ class OneToManyPersister extends AbstractCollectionPersister
*/
protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element)
{}
}
/**
* {@inheritdoc}
*/
public function count(PersistentCollection $coll)
{
$mapping = $coll->getMapping();
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
$sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
$params = array();
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
$where = '';
foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) {
if ($where != '') {
$where .= ' AND ';
}
$where .= $joinColumn['name'] . " = ?";
if ($targetClass->containsForeignIdentifier) {
$params[] = $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
} else {
$params[] = $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
}
}
$sql = "SELECT count(*) FROM " . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . " WHERE " . $where;
return $this->_conn->fetchColumn($sql, $params);
}
/**
* @param PersistentCollection $coll
* @param int $offset
* @param int $length
* @return \Doctrine\Common\Collections\ArrayCollection
*/
public function slice(PersistentCollection $coll, $offset, $length = null)
{
$mapping = $coll->getMapping();
return $this->_em->getUnitOfWork()
->getEntityPersister($mapping['targetEntity'])
->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length);
}
/**
* @param PersistentCollection $coll
* @param object $element
*/
public function contains(PersistentCollection $coll, $element)
{
$mapping = $coll->getMapping();
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
return false;
}
// 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()) );
return $uow->getEntityPersister($mapping['targetEntity'])
->exists($element, array($mapping['mappedBy'] => $id));
}
}

View File

@@ -26,6 +26,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
* SINGLE_TABLE strategy.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
* @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html
*/
@@ -40,6 +41,10 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
/** {@inheritdoc} */
protected function _getSelectColumnListSQL()
{
if ($this->_selectColumnListSql !== null) {
return $this->_selectColumnListSql;
}
$columnList = parent::_getSelectColumnListSQL();
// Append discriminator column
@@ -48,7 +53,8 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
$rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
$tableAlias = $this->_getSQLTableAlias($rootClass->name);
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
$this->_resultColumnNames[$resultColumnName] = $discrColumn;
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
// Append subclass columns
foreach ($this->_class->subClasses as $subClassName) {
@@ -63,18 +69,17 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
foreach ($subClass->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name
);
}
}
}
}
return $columnList;
$this->_selectColumnListSql = $columnList;
return $this->_selectColumnListSql;
}
/** {@inheritdoc} */
@@ -88,9 +93,9 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
}
/** {@inheritdoc} */
protected function _getSQLTableAlias($className)
protected function _getSQLTableAlias($className, $assocName = '')
{
return parent::_getSQLTableAlias($this->_class->rootEntityName);
return parent::_getSQLTableAlias($this->_class->rootEntityName, $assocName);
}
/** {@inheritdoc} */

View File

@@ -130,11 +130,12 @@ class ProxyFactory
{
$methods = $this->_generateMethods($class);
$sleepImpl = $this->_generateSleep($class);
$cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive
$placeholders = array(
'<namespace>',
'<proxyClassName>', '<className>',
'<methods>', '<sleepImpl>'
'<methods>', '<sleepImpl>', '<cloneImpl>'
);
if(substr($class->name, 0, 1) == "\\") {
@@ -146,7 +147,7 @@ class ProxyFactory
$replacements = array(
$this->_proxyNamespace,
$proxyClassName, $className,
$methods, $sleepImpl
$methods, $sleepImpl, $cloneImpl
);
$file = str_replace($placeholders, $replacements, $file);
@@ -164,14 +165,16 @@ class ProxyFactory
{
$methods = '';
$methodNames = array();
foreach ($class->reflClass->getMethods() as $method) {
/* @var $method ReflectionMethod */
if ($method->isConstructor() || strtolower($method->getName()) == "__sleep") {
if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
continue;
}
$methodNames[$method->getName()] = true;
if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
$methods .= PHP_EOL . ' public function ';
$methods .= "\n" . ' public function ';
if ($method->returnsReference()) {
$methods .= '&';
}
@@ -207,10 +210,10 @@ class ProxyFactory
}
$methods .= $parameterString . ')';
$methods .= PHP_EOL . ' {' . PHP_EOL;
$methods .= ' $this->_load();' . PHP_EOL;
$methods .= "\n" . ' {' . "\n";
$methods .= ' $this->__load();' . "\n";
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
$methods .= PHP_EOL . ' }' . PHP_EOL;
$methods .= "\n" . ' }' . "\n";
}
}
@@ -268,22 +271,48 @@ class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
$this->_entityPersister = $entityPersister;
$this->_identifier = $identifier;
}
private function _load()
/** @private */
public function __load()
{
if (!$this->__isInitialized__ && $this->_entityPersister) {
$this->__isInitialized__ = true;
if (method_exists($this, "__wakeup")) {
// call this after __isInitialized__to avoid infinite recursion
// but before loading to emulate what ClassMetadata::newInstance()
// provides.
$this->__wakeup();
}
if ($this->_entityPersister->load($this->_identifier, $this) === null) {
throw new \Doctrine\ORM\EntityNotFoundException();
}
unset($this->_entityPersister, $this->_identifier);
}
}
<methods>
public function __sleep()
{
<sleepImpl>
}
public function __clone()
{
if (!$this->__isInitialized__ && $this->_entityPersister) {
$this->__isInitialized__ = true;
$class = $this->_entityPersister->getClassMetadata();
$original = $this->_entityPersister->load($this->_identifier);
if ($original === null) {
throw new \Doctrine\ORM\EntityNotFoundException();
}
foreach ($class->reflFields AS $field => $reflProperty) {
$reflProperty->setValue($this, $reflProperty->getValue($original));
}
unset($this->_entityPersister, $this->_identifier);
}
<cloneImpl>
}
}';
}

View File

@@ -44,19 +44,28 @@ final class Query extends AbstractQuery
* is called.
*/
const STATE_DIRTY = 2;
/* Query HINTS */
/**
* The refresh hint turns any query into a refresh query with the result that
* any local changes in entities are overridden with the fetched values.
*
*
* @var string
*/
const HINT_REFRESH = 'doctrine.refresh';
/**
* Internal hint: is set to the proxy entity that is currently triggered for loading
*
* @var string
*/
const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
/**
* The forcePartialLoad query hint forces a particular query to return
* partial objects.
*
*
* @var string
* @todo Rename: HINT_OPTIMIZE
*/
@@ -64,15 +73,15 @@ final class Query extends AbstractQuery
/**
* The includeMetaColumns query hint causes meta columns like foreign keys and
* discriminator columns to be selected and returned as part of the query result.
*
*
* This hint does only apply to non-object queries.
*
*
* @var string
*/
const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
/**
* An array of class names that implement Doctrine\ORM\Query\TreeWalker and
* An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
* are iterated and executed after the DQL has been parsed into an AST.
*
* @var string
@@ -80,7 +89,7 @@ final class Query extends AbstractQuery
const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
/**
* A string with a class name that implements Doctrine\ORM\Query\TreeWalker
* A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
* and is used for generating the target SQL from any DQL AST tree.
*
* @var string
@@ -110,15 +119,15 @@ final class Query extends AbstractQuery
private $_dql = null;
/**
* @var Doctrine\ORM\Query\ParserResult The parser result that holds DQL => SQL information.
* @var \Doctrine\ORM\Query\ParserResult The parser result that holds DQL => SQL information.
*/
private $_parserResult;
/**
* @var integer The first result to return (the "offset").
*/
private $_firstResult = null;
/**
* @var integer The maximum number of results to return (the "limit").
*/
@@ -138,7 +147,7 @@ final class Query extends AbstractQuery
* @var int Query Cache lifetime.
*/
private $_queryCacheTTL;
/**
* @var boolean Whether to use a query cache, if available. Defaults to TRUE.
*/
@@ -149,7 +158,7 @@ final class Query extends AbstractQuery
/**
* Initializes a new Query instance.
*
* @param Doctrine\ORM\EntityManager $entityManager
* @param \Doctrine\ORM\EntityManager $entityManager
*/
/*public function __construct(EntityManager $entityManager)
{
@@ -170,9 +179,9 @@ final class Query extends AbstractQuery
/**
* Returns the corresponding AST for this DQL query.
*
* @return Doctrine\ORM\Query\AST\SelectStatement |
* Doctrine\ORM\Query\AST\UpdateStatement |
* Doctrine\ORM\Query\AST\DeleteStatement
* @return \Doctrine\ORM\Query\AST\SelectStatement |
* \Doctrine\ORM\Query\AST\UpdateStatement |
* \Doctrine\ORM\Query\AST\DeleteStatement
*/
public function getAST()
{
@@ -182,21 +191,22 @@ final class Query extends AbstractQuery
/**
* Parses the DQL query, if necessary, and stores the parser result.
*
*
* Note: Populates $this->_parserResult as a side-effect.
*
* @return Doctrine\ORM\Query\ParserResult
* @return \Doctrine\ORM\Query\ParserResult
*/
private function _parse()
{
if ($this->_state === self::STATE_CLEAN) {
return $this->_parserResult;
}
// Check query cache.
if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) {
$hash = $this->_getQueryCacheId();
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
if ($cached === false) {
// Cache miss.
$parser = new Parser($this);
@@ -210,8 +220,9 @@ final class Query extends AbstractQuery
$parser = new Parser($this);
$this->_parserResult = $parser->parse();
}
$this->_state = self::STATE_CLEAN;
return $this->_parserResult;
}
@@ -229,38 +240,7 @@ final class Query extends AbstractQuery
throw QueryException::invalidParameterNumber();
}
$sqlParams = $types = array();
foreach ($this->_params as $key => $value) {
if ( ! isset($paramMappings[$key])) {
throw QueryException::unknownParameter($key);
}
if (isset($this->_paramTypes[$key])) {
foreach ($paramMappings[$key] as $position) {
$types[$position] = $this->_paramTypes[$key];
}
}
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
} else {
$class = $this->_em->getClassMetadata(get_class($value));
$idValues = $class->getIdentifierValues($value);
}
$sqlPositions = $paramMappings[$key];
$sqlParams += array_combine((array)$sqlPositions, $idValues);
} else {
foreach ($paramMappings[$key] as $position) {
$sqlParams[$position] = $value;
}
}
}
if ($sqlParams) {
ksort($sqlParams);
$sqlParams = array_values($sqlParams);
}
list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
if ($this->_resultSetMapping === null) {
$this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
@@ -269,6 +249,83 @@ final class Query extends AbstractQuery
return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
}
/**
* Processes query parameter mappings
*
* @param array $paramMappings
* @return array
*/
private function processParameterMappings($paramMappings)
{
$sqlParams = $types = array();
foreach ($this->_params as $key => $value) {
if ( ! isset($paramMappings[$key])) {
throw QueryException::unknownParameter($key);
}
if (isset($this->_paramTypes[$key])) {
foreach ($paramMappings[$key] as $position) {
$types[$position] = $this->_paramTypes[$key];
}
}
$sqlPositions = $paramMappings[$key];
$value = array_values($this->processParameterValue($value));
$countValue = count($value);
for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
$sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
}
}
if (count($sqlParams) != count($types)) {
throw QueryException::parameterTypeMissmatch();
}
if ($sqlParams) {
ksort($sqlParams);
$sqlParams = array_values($sqlParams);
ksort($types);
$types = array_values($types);
}
return array($sqlParams, $types);
}
/**
* Process an individual parameter value
*
* @param mixed $value
* @return array
*/
private function processParameterValue($value)
{
if (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);
}
if ( ! (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)))) {
return array($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));
}
/**
* Defines a cache driver to be used for caching queries.
*
@@ -280,10 +337,10 @@ final class Query extends AbstractQuery
$this->_queryCache = $queryCache;
return $this;
}
/**
* Defines whether the query should make use of a query cache, if available.
*
*
* @param boolean $bool
* @return @return Query This query instance.
*/
@@ -371,7 +428,7 @@ final class Query extends AbstractQuery
* Sets a DQL query string.
*
* @param string $dqlQuery DQL Query
* @return Doctrine\ORM\AbstractQuery
* @return \Doctrine\ORM\AbstractQuery
*/
public function setDQL($dqlQuery)
{
@@ -417,7 +474,7 @@ final class Query extends AbstractQuery
{
return stripos($this->getDQL(), $dql) === false ? false : true;
}
/**
* Sets the position of the first result to retrieve (the "offset").
*
@@ -430,21 +487,21 @@ final class Query extends AbstractQuery
$this->_state = self::STATE_DIRTY;
return $this;
}
/**
* Gets the position of the first result the query object was set to retrieve (the "offset").
* Returns NULL if {@link setFirstResult} was not applied to this query.
*
*
* @return integer The position of the first result.
*/
public function getFirstResult()
{
return $this->_firstResult;
}
/**
* Sets the maximum number of results to retrieve (the "limit").
*
*
* @param integer $maxResults
* @return Query This query object.
*/
@@ -454,11 +511,11 @@ final class Query extends AbstractQuery
$this->_state = self::STATE_DIRTY;
return $this;
}
/**
* Gets the maximum number of results the query object was set to retrieve (the "limit").
* Returns NULL if {@link setMaxResults} was not applied to this query.
*
*
* @return integer Maximum number of results.
*/
public function getMaxResults()
@@ -479,7 +536,7 @@ final class Query extends AbstractQuery
$this->setHint(self::HINT_INTERNAL_ITERATION, true);
return parent::iterate($params, $hydrationMode);
}
/**
* {@inheritdoc}
*/
@@ -488,7 +545,7 @@ final class Query extends AbstractQuery
$this->_state = self::STATE_DIRTY;
return parent::setHint($name, $value);
}
/**
* {@inheritdoc}
*/
@@ -501,7 +558,7 @@ final class Query extends AbstractQuery
/**
* Set the lock mode for this Query.
*
* @see Doctrine\DBAL\LockMode
* @see \Doctrine\DBAL\LockMode
* @param int $lockMode
* @return Query
*/
@@ -543,7 +600,7 @@ final class Query extends AbstractQuery
ksort($this->_hints);
return md5(
$this->getDql() . var_export($this->_hints, true) .
$this->getDql() . var_export($this->_hints, true) .
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
);
@@ -559,4 +616,4 @@ final class Query extends AbstractQuery
parent::__clone();
$this->_state = self::STATE_DIRTY;
}
}
}

View File

@@ -0,0 +1,47 @@
<?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\Query\AST;
/**
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
*
* @since 2.1
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class CoalesceExpression extends Node
{
public $scalarExpressions = array();
public function __construct(array $scalarExpressions)
{
$this->scalarExpressions = $scalarExpressions;
}
public function dispatch($sqlWalker)
{
return $sqlWalker->walkCoalesceExpression($this);
}
}

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
@@ -27,7 +25,6 @@ namespace Doctrine\ORM\Query\AST;
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>

View File

@@ -0,0 +1,71 @@
<?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\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
/**
* "DATE_ADD(date1, interval, unit)"
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DateAddFunction extends FunctionNode
{
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function getSql(SqlWalker $sqlWalker)
{
$unit = strtolower($this->unit);
if ($unit == "day") {
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddDaysExpression(
$this->firstDateExpression->dispatch($sqlWalker),
$this->intervalExpression->dispatch($sqlWalker)
);
} else if ($unit == "month") {
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMonthExpression(
$this->firstDateExpression->dispatch($sqlWalker),
$this->intervalExpression->dispatch($sqlWalker)
);
} else {
throw QueryException::semanticalError('DATE_ADD() only supports units of type day and month.');
}
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$this->unit = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -0,0 +1,58 @@
<?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\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\Parser;
/**
* "DATE_DIFF(date1, date2)"
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DateDiffFunction extends FunctionNode
{
public $date1;
public $date2;
public function getSql(SqlWalker $sqlWalker)
{
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateDiffExpression(
$this->date1->dispatch($sqlWalker),
$this->date2->dispatch($sqlWalker)
);
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->date1 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$this->date2 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -0,0 +1,58 @@
<?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\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
/**
* "DATE_ADD(date1, interval, unit)"
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DateSubFunction extends DateAddFunction
{
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function getSql(SqlWalker $sqlWalker)
{
$unit = strtolower($this->unit);
if ($unit == "day") {
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubDaysExpression(
$this->firstDateExpression->dispatch($sqlWalker),
$this->intervalExpression->dispatch($sqlWalker)
);
} else if ($unit == "month") {
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMonthExpression(
$this->firstDateExpression->dispatch($sqlWalker),
$this->intervalExpression->dispatch($sqlWalker)
);
} else {
throw QueryException::semanticalError('DATE_SUB() only supports units of type day and month.');
}
}
}

View File

@@ -53,8 +53,8 @@ class SizeFunction extends FunctionNode
if ($assoc['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY) {
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$targetTableAlias = $sqlWalker->getSqlTableAlias($targetClass->table['name']);
$sourceTableAlias = $sqlWalker->getSqlTableAlias($class->table['name'], $dqlAlias);
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->table['name']);
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias);
$sql .= $targetClass->getQuotedTableName($platform) . ' ' . $targetTableAlias . ' WHERE ';
@@ -76,8 +76,8 @@ class SizeFunction extends FunctionNode
$joinTable = $owningAssoc['joinTable'];
// SQL table aliases
$joinTableAlias = $sqlWalker->getSqlTableAlias($joinTable['name']);
$sourceTableAlias = $sqlWalker->getSqlTableAlias($class->table['name'], $dqlAlias);
$joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']);
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias);
// join to target table
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $platform) . ' ' . $joinTableAlias . ' WHERE ';

View File

@@ -0,0 +1,49 @@
<?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\Query\AST;
/**
* NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
*
* @since 2.1
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class NullIfExpression extends Node
{
public $firstExpression;
public $secondExpression;
public function __construct($firstExpression, $secondExpression)
{
$this->firstExpression = $firstExpression;
$this->secondExpression = $secondExpression;
}
public function dispatch($sqlWalker)
{
return $sqlWalker->walkNullIfExpression($this);
}
}

View File

@@ -49,9 +49,9 @@ abstract class AbstractSqlExecutor
/**
* Executes all sql statements.
*
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param \Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
* @return Doctrine\DBAL\Driver\Statement
* @return \Doctrine\DBAL\Driver\Statement
*/
abstract public function execute(Connection $conn, array $params, array $types);
}

View File

@@ -63,9 +63,11 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
$idColumnList = implode(', ', $idColumnNames);
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $primaryDqlAlias);
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
. ' SELECT t0.' . implode(', t0.', $idColumnNames);
$sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $primaryDqlAlias, 't0');
$rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias);
$fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array())));
$this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
@@ -96,13 +98,13 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
}
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable;
$this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
}
/**
* Executes all SQL statements.
*
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param \Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
* @override
*/

View File

@@ -51,7 +51,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
$em = $sqlWalker->getEntityManager();
$conn = $em->getConnection();
$platform = $conn->getDatabasePlatform();
$updateClause = $AST->updateClause;
$primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName);
@@ -64,11 +64,14 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
$idColumnList = implode(', ', $idColumnNames);
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $updateClause->aliasIdentificationVariable);
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
. ' SELECT t0.' . implode(', t0.', $idColumnNames);
$sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $updateClause->aliasIdentificationVariable, 't0');
$rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable);
$fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array())));
$this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
// 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect)
@@ -85,8 +88,9 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
foreach ($updateItems as $updateItem) {
$field = $updateItem->pathExpression->field;
if (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited']) ||
isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) {
isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) {
$newValue = $updateItem->newValue;
if ( ! $affected) {
@@ -102,7 +106,9 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
//FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage.
if ($newValue instanceof AST\InputParameter) {
$paramKey = $newValue->name;
$this->_sqlParameters[$i][] = $sqlWalker->getQuery()->getParameter($paramKey);
$this->_sqlParameters[$i]['parameters'][] = $sqlWalker->getQuery()->getParameter($paramKey);
$this->_sqlParameters[$i]['types'][] = $sqlWalker->getQuery()->getParameterType($paramKey);
++$this->_numParametersInUpdateClause;
}
}
@@ -120,15 +126,18 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
// 4. Store DDL for temporary identifier table.
$columnDefinitions = array();
foreach ($idColumnNames as $idColumnName) {
$columnDefinitions[$idColumnName] = array(
'notnull' => true,
'type' => Type::getType($rootClass->getTypeOfColumn($idColumnName))
);
}
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable;
$this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
}
/**
@@ -146,11 +155,23 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
$conn->executeUpdate($this->_createTempTableSql);
// Insert identifiers. Parameters from the update clause are cut off.
$numUpdated = $conn->executeUpdate($this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), $types);
$numUpdated = $conn->executeUpdate(
$this->_insertSql,
array_slice($params, $this->_numParametersInUpdateClause),
array_slice($types, $this->_numParametersInUpdateClause)
);
// Execute UPDATE statements
for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) {
$conn->executeUpdate($this->_sqlStatements[$i], isset($this->_sqlParameters[$i]) ? $this->_sqlParameters[$i] : array());
$parameters = array();
$types = array();
if (isset($this->_sqlParameters[$i])) {
$parameters = isset($this->_sqlParameters[$i]['parameters']) ? $this->_sqlParameters[$i]['parameters'] : array();
$types = isset($this->_sqlParameters[$i]['types']) ? $this->_sqlParameters[$i]['types'] : array();
}
$conn->executeUpdate($this->_sqlStatements[$i], $parameters, $types);
}
// Drop temporary table

View File

@@ -73,7 +73,7 @@ class Expr
* Creates an ASCending order expression.
*
* @param $sort
* @return OrderBy
* @return Expr\OrderBy
*/
public function asc($expr)
{
@@ -84,7 +84,7 @@ class Expr
* Creates a DESCending order expression.
*
* @param $sort
* @return OrderBy
* @return Expr\OrderBy
*/
public function desc($expr)
{
@@ -443,6 +443,28 @@ class Expr
return new Expr\Func($x . ' NOT IN', (array) $y);
}
/**
* Creates an IS NULL expression with the given arguments.
*
* @param string $x Field in string format to be restricted by IS NULL
* @return string
*/
public function isNull($x)
{
return $x . ' IS NULL';
}
/**
* Creates an IS NOT NULL expression with the given arguments.
*
* @param string $x Field in string format to be restricted by IS NOT NULL
* @return string
*/
public function isNotNull($x)
{
return $x . ' IS NOT NULL';
}
/**
* Creates a LIKE() comparison expression with the given arguments.
*

View File

@@ -32,9 +32,9 @@ namespace Doctrine\ORM\Query\Expr;
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Andx extends Base
class Andx extends Composite
{
protected $_separator = ') AND (';
protected $_separator = ' AND ';
protected $_allowedClasses = array(
'Doctrine\ORM\Query\Expr\Comparison',
'Doctrine\ORM\Query\Expr\Func',

View File

@@ -39,7 +39,7 @@ abstract class Base
protected $_postSeparator = ')';
protected $_allowedClasses = array();
private $_parts = array();
protected $_parts = array();
public function __construct($args = array())
{
@@ -55,7 +55,7 @@ abstract class Base
public function add($arg)
{
if ( ! empty($arg) || ($arg instanceof self && $arg->count() > 0)) {
if ( $arg !== null || ($arg instanceof self && $arg->count() > 0)) {
// If we decide to keep Expr\Base instances, we can use this check
if ( ! is_string($arg)) {
$class = get_class($arg);

View File

@@ -0,0 +1,68 @@
<?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
* 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\Query\Expr;
/**
* Expression class for building DQL and parts
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Composite extends Base
{
public function __toString()
{
if ($this->count() === 1) {
return (string) $this->_parts[0];
}
$components = array();
foreach ($this->_parts as $part) {
$components[] = $this->processQueryPart($part);
}
return implode($this->_separator, $components);
}
private function processQueryPart($part)
{
$queryPart = (string) $part;
if (is_object($part) && $part instanceof self && $part->count() > 1) {
return $this->_preSeparator . $queryPart . $this->_postSeparator;
}
// Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND")
if (stripos($queryPart, ' OR ') !== false || stripos($queryPart, ' AND ') !== false) {
return $this->_preSeparator . $queryPart . $this->_postSeparator;
}
return $queryPart;
}
}

View File

@@ -45,20 +45,23 @@ class Join
private $_alias;
private $_conditionType;
private $_condition;
private $_indexBy;
public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null)
public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null, $indexBy = null)
{
$this->_joinType = $joinType;
$this->_join = $join;
$this->_alias = $alias;
$this->_joinType = $joinType;
$this->_join = $join;
$this->_alias = $alias;
$this->_conditionType = $conditionType;
$this->_condition = $condition;
$this->_condition = $condition;
$this->_indexBy = $indexBy;
}
public function __toString()
{
return strtoupper($this->_joinType) . ' JOIN ' . $this->_join
. ($this->_alias ? ' ' . $this->_alias : '')
. ($this->_condition ? ' ' . strtoupper($this->_conditionType) . ' ' . $this->_condition : '');
. ($this->_condition ? ' ' . strtoupper($this->_conditionType) . ' ' . $this->_condition : '')
. ($this->_indexBy ? ' INDEX BY ' . $this->_indexBy : '');
}
}

View File

@@ -32,9 +32,9 @@ namespace Doctrine\ORM\Query\Expr;
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Orx extends Base
class Orx extends Composite
{
protected $_separator = ') OR (';
protected $_separator = ' OR ';
protected $_allowedClasses = array(
'Doctrine\ORM\Query\Expr\Andx',
'Doctrine\ORM\Query\Expr\Comparison',

View File

@@ -126,7 +126,7 @@ class Lexer extends \Doctrine\Common\Lexer
'[a-z_\\\][a-z0-9_\:\\\]*[a-z0-9_]{1}',
'(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?',
"'(?:[^']|'')*'",
'\?[1-9][0-9]*|:[a-z][a-z0-9_]+'
'\?[0-9]*|:[a-z]{1}[a-z0-9_]{0,}'
);
}

View File

@@ -0,0 +1,72 @@
<?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\Query;
use Doctrine\DBAL\Connection,
Doctrine\DBAL\Types\Type;
/**
* Provides an enclosed support for parameter infering.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class ParameterTypeInferer
{
/**
* Infer type of a given value, returning a compatible constant:
* - Type (\Doctrine\DBAL\Types\Type::*)
* - Connection (\Doctrine\DBAL\Connection::PARAM_*)
*
* @param mixed $value Parameter value
*
* @return mixed Parameter type constant
*/
public static function inferType($value)
{
switch (true) {
case is_integer($value):
return Type::INTEGER;
case ($value instanceof \DateTime):
return Type::DATETIME;
case is_array($value):
$key = key($value);
if (is_integer($value[$key])) {
return Connection::PARAM_INT_ARRAY;
}
return Connection::PARAM_STR_ARRAY;
default:
// Do nothing
break;
}
return \PDO::PARAM_STR;
}
}

View File

@@ -45,19 +45,22 @@ class Parser
/** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */
private static $_NUMERIC_FUNCTIONS = array(
'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction'
'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction',
'date_diff' => 'Doctrine\ORM\Query\AST\Functions\DateDiffFunction',
);
/** READ-ONLY: Maps BUILT-IN datetime function names to AST class names. */
private static $_DATETIME_FUNCTIONS = array(
'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction',
'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction',
'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction'
'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction',
'date_add' => 'Doctrine\ORM\Query\AST\Functions\DateAddFunction',
'date_sub' => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction',
);
/**
@@ -72,14 +75,14 @@ class Parser
/**
* The lexer.
*
* @var Doctrine\ORM\Query\Lexer
* @var \Doctrine\ORM\Query\Lexer
*/
private $_lexer;
/**
* The parser result.
*
* @var Doctrine\ORM\Query\ParserResult
* @var \Doctrine\ORM\Query\ParserResult
*/
private $_parserResult;
@@ -167,7 +170,7 @@ class Parser
/**
* Gets the lexer used by the parser.
*
* @return Doctrine\ORM\Query\Lexer
* @return \Doctrine\ORM\Query\Lexer
*/
public function getLexer()
{
@@ -177,7 +180,7 @@ class Parser
/**
* Gets the ParserResult that is being filled with information during parsing.
*
* @return Doctrine\ORM\Query\ParserResult
* @return \Doctrine\ORM\Query\ParserResult
*/
public function getParserResult()
{
@@ -231,7 +234,7 @@ class Parser
* If they match, updates the lookahead token; otherwise raises a syntax
* error.
*
* @param int|string token type or value
* @param int token type
* @return void
* @throws QueryException If the tokens dont match.
*/
@@ -277,20 +280,8 @@ class Parser
{
$AST = $this->getAST();
// Fix order of identification variables.
// They have to appear in the select clause in the same order as the
// declarations (from ... x join ... y join ... z ...) appear in the query
// as the hydration process relies on that order for proper operation.
if ( count($this->_identVariableExpressions) > 1) {
foreach ($this->_queryComponents as $dqlAlias => $qComp) {
if (isset($this->_identVariableExpressions[$dqlAlias])) {
$expr = $this->_identVariableExpressions[$dqlAlias];
$key = array_search($expr, $AST->selectClause->selectExpressions);
unset($AST->selectClause->selectExpressions[$key]);
$AST->selectClause->selectExpressions[] = $expr;
}
}
}
$this->fixIdentificationVariableOrder($AST);
$this->assertSelectEntityRootAliasRequirement();
if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
$this->_customTreeWalkers = $customWalkers;
@@ -332,6 +323,46 @@ class Parser
return $this->_parserResult;
}
private function assertSelectEntityRootAliasRequirement()
{
if ( count($this->_identVariableExpressions) > 0) {
$foundRootEntity = false;
foreach ($this->_identVariableExpressions AS $dqlAlias => $expr) {
if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) {
$foundRootEntity = true;
}
}
if (!$foundRootEntity) {
$this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
}
}
}
/**
* Fix order of identification variables.
*
* They have to appear in the select clause in the same order as the
* declarations (from ... x join ... y join ... z ...) appear in the query
* as the hydration process relies on that order for proper operation.
*
* @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
* @return void
*/
private function fixIdentificationVariableOrder($AST)
{
if ( count($this->_identVariableExpressions) > 1) {
foreach ($this->_queryComponents as $dqlAlias => $qComp) {
if (isset($this->_identVariableExpressions[$dqlAlias])) {
$expr = $this->_identVariableExpressions[$dqlAlias];
$key = array_search($expr, $AST->selectClause->selectExpressions);
unset($AST->selectClause->selectExpressions[$key]);
$AST->selectClause->selectExpressions[] = $expr;
}
}
}
}
/**
* Generates a new syntax error.
@@ -895,6 +926,10 @@ class Parser
$token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable();
if (!isset($this->_queryComponents[$identVariable])) {
$this->semanticalError('Identification Variable ' . $identVariable .' used in join path expression but was not defined before.');
}
$this->match(Lexer::T_DOT);
$this->match(Lexer::T_IDENTIFIER);
@@ -1296,6 +1331,10 @@ class Parser
$token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable();
if (!isset($this->_queryComponents[$identVariable])) {
$this->semanticalError('Cannot group by undefined identification variable.');
}
return $identVariable;
}
@@ -1427,7 +1466,7 @@ class Parser
/**
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
*
* @return Doctrine\ORM\Query\AST\RangeVariableDeclaration
* @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
*/
public function RangeVariableDeclaration()
{
@@ -1498,7 +1537,7 @@ class Parser
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
* ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
*
* @return Doctrine\ORM\Query\AST\Join
* @return \Doctrine\ORM\Query\AST\Join
*/
public function Join()
{
@@ -1568,7 +1607,7 @@ class Parser
/**
* IndexBy ::= "INDEX" "BY" StateFieldPathExpression
*
* @return Doctrine\ORM\Query\AST\IndexBy
* @return \Doctrine\ORM\Query\AST\IndexBy
*/
public function IndexBy()
{
@@ -1605,7 +1644,11 @@ class Parser
return $this->StateFieldPathExpression();
} else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
return $this->SimpleArithmeticExpression();
} else if ($this->_isFunction()) {
} else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
// Since NULLIF and COALESCE can be identified as a function,
// we need to check if before check for FunctionDeclaration
return $this->CaseExpression();
} else if ($this->_isFunction() || $this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
// We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator)
$this->_lexer->peek(); // "("
$peek = $this->_peekBeyondClosingParenthesis();
@@ -1613,8 +1656,12 @@ class Parser
if ($this->_isMathOperator($peek)) {
return $this->SimpleArithmeticExpression();
}
return $this->FunctionDeclaration();
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
} else {
return $this->FunctionDeclaration();
}
} else if ($lookahead == Lexer::T_STRING) {
return $this->StringPrimary();
} else if ($lookahead == Lexer::T_INPUT_PARAMETER) {
@@ -1622,8 +1669,6 @@ class Parser
} else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) {
$this->match($lookahead);
return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
} else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
return $this->CaseExpression();
} else {
$this->syntaxError();
}
@@ -1631,11 +1676,66 @@ class Parser
public function CaseExpression()
{
$lookahead = $this->_lexer->lookahead['type'];
// if "CASE" "WHEN" => GeneralCaseExpression
// else if "CASE" => SimpleCaseExpression
// else if "COALESCE" => CoalesceExpression
// else if "NULLIF" => NullifExpression
$this->semanticalError('CaseExpression not yet supported.');
// [DONE] else if "COALESCE" => CoalesceExpression
// [DONE] else if "NULLIF" => NullifExpression
switch ($lookahead) {
case Lexer::T_NULLIF:
return $this->NullIfExpression();
case Lexer::T_COALESCE:
return $this->CoalesceExpression();
default:
$this->semanticalError('CaseExpression not yet supported.');
return null;
}
}
/**
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
*
* @return \Doctrine\ORM\Query\AST\CoalesceExpression
*/
public function CoalesceExpression()
{
$this->match(Lexer::T_COALESCE);
$this->match(Lexer::T_OPEN_PARENTHESIS);
// Process ScalarExpressions (1..N)
$scalarExpressions = array();
$scalarExpressions[] = $this->ScalarExpression();
while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
$this->match(Lexer::T_COMMA);
$scalarExpressions[] = $this->ScalarExpression();
}
$this->match(Lexer::T_CLOSE_PARENTHESIS);
return new AST\CoalesceExpression($scalarExpressions);
}
/**
* NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
*
* @return \Doctrine\ORM\Query\AST\ExistsExpression
*/
public function NullIfExpression()
{
$this->match(Lexer::T_NULLIF);
$this->match(Lexer::T_OPEN_PARENTHESIS);
$firstExpression = $this->ScalarExpression();
$this->match(Lexer::T_COMMA);
$secondExpression = $this->ScalarExpression();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
return new AST\NullIfExpression($firstExpression, $secondExpression);
}
/**
@@ -1643,7 +1743,7 @@ class Parser
* IdentificationVariable | StateFieldPathExpression |
* (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
*
* @return Doctrine\ORM\Query\AST\SelectExpression
* @return \Doctrine\ORM\Query\AST\SelectExpression
*/
public function SelectExpression()
{
@@ -1674,12 +1774,16 @@ class Parser
}
} else if ($this->_isFunction()) {
$this->_lexer->peek(); // "("
$beyond = $this->_peekBeyondClosingParenthesis();
$lookaheadType = $this->_lexer->lookahead['type'];
$beyond = $this->_peekBeyondClosingParenthesis();
if ($this->_isMathOperator($beyond)) {
$expression = $this->ScalarExpression();
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$expression = $this->AggregateExpression();
} else if (in_array ($lookaheadType, array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) {
$expression = $this->CaseExpression();
} else {
// Shortcut: ScalarExpression => Function
$expression = $this->FunctionDeclaration();
@@ -1689,7 +1793,8 @@ class Parser
$expression = $this->PartialObjectExpression();
$identVariable = $expression->identificationVariable;
} else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
$this->_lexer->lookahead['type'] == Lexer::T_FLOAT) {
$this->_lexer->lookahead['type'] == Lexer::T_FLOAT ||
$this->_lexer->lookahead['type'] == Lexer::T_STRING) {
// Shortcut: ScalarExpression => SimpleArithmeticExpression
$expression = $this->SimpleArithmeticExpression();
} else {
@@ -1758,15 +1863,8 @@ class Parser
}
$this->_lexer->peek();
$beyond = $this->_peekBeyondClosingParenthesis();
if ($this->_isMathOperator($beyond)) {
$expression = $this->ScalarExpression();
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$expression = $this->AggregateExpression();
} else {
$expression = $this->FunctionDeclaration();
}
$expression = $this->ScalarExpression();
$expr = new AST\SimpleSelectExpression($expression);
@@ -1869,7 +1967,7 @@ class Parser
/**
* ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
*
* @return Doctrine\ORM\Query\AST\ConditionalPrimary
* @return \Doctrine\ORM\Query\AST\ConditionalPrimary
*/
public function ConditionalPrimary()
{
@@ -2192,7 +2290,7 @@ class Parser
/**
* ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
* | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
* | FunctionsReturningDatetime | IdentificationVariable
* | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
*/
public function ArithmeticPrimary()
{
@@ -2216,7 +2314,11 @@ class Parser
if ($peek['value'] == '.') {
return $this->SingleValuedPathExpression();
}
if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) {
return $this->ResultVariable();
}
return $this->StateFieldPathExpression();
case Lexer::T_INPUT_PARAMETER:
@@ -2271,7 +2373,8 @@ class Parser
if ($peek['value'] == '.') {
return $this->StateFieldPathExpression();
} else if ($peek['value'] == '(') {
return $this->FunctionsReturningStrings();
// do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions.
return $this->FunctionDeclaration();
} else {
$this->syntaxError("'.' or '('");
}
@@ -2358,7 +2461,7 @@ class Parser
$functionName = $this->_lexer->token['value'];
$this->match(Lexer::T_OPEN_PARENTHESIS);
$pathExp = $this->StateFieldPathExpression();
$pathExp = $this->SimpleArithmeticExpression();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
}

View File

@@ -72,11 +72,15 @@ class QueryException extends \Doctrine\ORM\ORMException
return new self("Invalid parameter: token ".$key." is not defined in the query.");
}
public static function parameterTypeMissmatch()
{
return new self("DQL Query parameter and type numbers missmatch, but have to be exactly equal.");
}
public static function invalidPathExpression($pathExpr)
{
return new self(
"Invalid PathExpression '" . $pathExpr->identificationVariable .
"." . implode('.', $pathExpr->parts) . "'."
"Invalid PathExpression '" . $pathExpr->identificationVariable . "." . $pathExpr->field . "'."
);
}
@@ -85,7 +89,7 @@ class QueryException extends \Doctrine\ORM\ORMException
}
/**
* @param Doctrine\ORM\Mapping\AssociationMapping $assoc
* @param \Doctrine\ORM\Mapping\AssociationMapping $assoc
*/
public static function iterateWithFetchJoinCollectionNotAllowed($assoc)
{
@@ -136,4 +140,10 @@ class QueryException extends \Doctrine\ORM\ORMException
"in the query."
);
}
public static function instanceOfUnrelatedClass($className, $rootClass)
{
return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " .
"inheritance hierachy exists between these two classes.");
}
}

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
@@ -114,6 +112,13 @@ class ResultSetMapping
* @var array
*/
public $declaringClasses = array();
/**
* This is necessary to hydrate derivate foreign keys correctly.
*
* @var array
*/
public $isIdentifierColumn = array();
/**
* Adds an entity result to this ResultSetMapping.
@@ -383,14 +388,17 @@ class ResultSetMapping
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
* @param $alias
* @param $columnName
* @param $fieldName
* @param string $alias
* @param string $columnName
* @param string $fieldName
* @param bool
*/
public function addMetaResult($alias, $columnName, $fieldName)
public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false)
{
$this->metaMappings[$columnName] = $fieldName;
$this->columnOwnerMap[$columnName] = $alias;
if ($isIdentifierColumn) {
$this->isIdentifierColumn[$alias][$columnName] = true;
}
}
}
}

View File

@@ -0,0 +1,107 @@
<?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\Query;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
/**
* A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields
*
* @author Michael Ridgway <mcridgway@gmail.com>
* @since 2.1
*/
class ResultSetMappingBuilder extends ResultSetMapping
{
/**
* @var EntityManager
*/
private $em;
/**
* @param EntityManager
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Adds a root entity and all of its fields to the result set.
*
* @param string $class The class name of the root entity.
* @param string $alias The unique alias to use for the root entity.
* @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName)
*/
public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = array())
{
$this->addEntityResult($class, $alias);
$this->addAllClassFields($class, $alias, $renamedColumns);
}
/**
* Adds a joined entity and all of its fields to the result set.
*
* @param string $class The class name of the joined entity.
* @param string $alias The unique alias to use for the joined entity.
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
* @param object $relation The association field that connects the parent entity result with the joined entity result.
* @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName)
*/
public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = array())
{
$this->addJoinedEntityResult($class, $alias, $parentAlias, $relation);
$this->addAllClassFields($class, $alias, $renamedColumns);
}
/**
* Adds all fields of the given class to the result set mapping (columns and meta fields)
*/
protected function addAllClassFields($class, $alias, $renamedColumns = array())
{
$classMetadata = $this->em->getClassMetadata($class);
if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) {
throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.');
}
$platform = $this->em->getConnection()->getDatabasePlatform();
foreach ($classMetadata->getColumnNames() AS $columnName) {
$propertyName = $classMetadata->getFieldName($columnName);
if (isset($renamedColumns[$columnName])) {
$columnName = $renamedColumns[$columnName];
}
if (isset($this->fieldMappings[$columnName])) {
throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper.");
}
$this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName);
}
foreach ($classMetadata->associationMappings AS $associationMapping) {
if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
foreach ($associationMapping['joinColumns'] AS $joinColumn) {
$columnName = $joinColumn['name'];
$renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName;
if (isset($this->metaMappings[$renamedColumnName])) {
throw new \InvalidArgumentException("The column '$renamedColumnName' conflicts with another column in the mapper.");
}
$this->addMetaResult($alias, $platform->getSQLResultCasing($renamedColumnName), $platform->getSQLResultCasing($columnName));
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,7 @@ abstract class TreeWalkerAdapter implements TreeWalker
/**
* Retrieve Query Instance reponsible for the current walkers execution.
*
* @return Doctrine\ORM\Query
* @return \Doctrine\ORM\Query
*/
protected function _getQuery()
{
@@ -65,7 +65,7 @@ abstract class TreeWalkerAdapter implements TreeWalker
/**
* Retrieve ParserResult
*
* @return Doctrine\ORM\Query\ParserResult
* @return \Doctrine\ORM\Query\ParserResult
*/
protected function _getParserResult()
{

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(),
@@ -119,7 +120,7 @@ class QueryBuilder
* For more complex expression construction, consider storing the expression
* builder object in a local variable.
*
* @return Expr
* @return Query\Expr
*/
public function expr()
{
@@ -217,9 +218,30 @@ class QueryBuilder
->setFirstResult($this->_firstResult)
->setMaxResults($this->_maxResults);
}
/**
* Gets the FIRST root alias of the query. This is the first entity alias involved
* in the construction of the query.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u');
*
* echo $qb->getRootAlias(); // u
* </code>
*
* @deprecated Please use $qb->getRootAliases() instead.
* @return string $rootAlias
*/
public function getRootAlias()
{
$aliases = $this->getRootAliases();
return $aliases[0];
}
/**
* Gets the root alias of the query. This is the first entity alias involved
* Gets the root aliases of the query. This is the entity aliases involved
* in the construction of the query.
*
* <code>
@@ -227,15 +249,61 @@ class QueryBuilder
* ->select('u')
* ->from('User', 'u');
*
* echo $qb->getRootAlias(); // u
* $qb->getRootAliases(); // array('u')
* </code>
*
* @return string $rootAlias
* @todo Rename/Refactor: getRootAliases(), there can be multiple roots!
* @return array $rootAliases
*/
public function getRootAlias()
public function getRootAliases()
{
return $this->_dqlParts['from'][0]->getAlias();
$aliases = array();
foreach ($this->_dqlParts['from'] as &$fromClause) {
if (is_string($fromClause)) {
$spacePos = strrpos($fromClause, ' ');
$from = substr($fromClause, 0, $spacePos);
$alias = substr($fromClause, $spacePos + 1);
$fromClause = new Query\Expr\From($from, $alias);
}
$aliases[] = $fromClause->getAlias();
}
return $aliases;
}
/**
* Gets the root entities of the query. This is the entity aliases involved
* in the construction of the query.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u');
*
* $qb->getRootEntities(); // array('User')
* </code>
*
* @return array $rootEntities
*/
public function getRootEntities()
{
$entities = array();
foreach ($this->_dqlParts['from'] as &$fromClause) {
if (is_string($fromClause)) {
$spacePos = strrpos($fromClause, ' ');
$from = substr($fromClause, 0, $spacePos);
$alias = substr($fromClause, $spacePos + 1);
$fromClause = new Query\Expr\From($from, $alias);
}
$entities[] = $fromClause->getFrom();
}
return $entities;
}
/**
@@ -256,10 +324,13 @@ class QueryBuilder
*/
public function setParameter($key, $value, $type = null)
{
if ($type !== null) {
$this->_paramTypes[$key] = $type;
if ($type === null) {
$type = Query\ParameterTypeInferer::inferType($value);
}
$this->_paramTypes[$key] = $type;
$this->_params[$key] = $value;
return $this;
}
@@ -272,8 +343,8 @@ class QueryBuilder
* ->from('User', 'u')
* ->where('u.id = :user_id1 OR u.id = :user_id2')
* ->setParameters(array(
* ':user_id1' => 1,
* ':user_id2' => 2
* 'user_id1' => 1,
* 'user_id2' => 2
* ));
* </code>
*
@@ -282,8 +353,13 @@ class QueryBuilder
*/
public function setParameters(array $params, array $types = array())
{
$this->_paramTypes = $types;
$this->_params = $params;
foreach ($params as $key => $value) {
if (isset($types[$key])) {
$this->setParameter($key, $value, $types[$key]);
} else {
$this->setParameter($key, $value);
}
}
return $this;
}
@@ -368,9 +444,29 @@ class QueryBuilder
public function add($dqlPartName, $dqlPart, $append = false)
{
$isMultiple = is_array($this->_dqlParts[$dqlPartName]);
// This is introduced for backwards compatibility reasons.
// TODO: Remove for 3.0
if ($dqlPartName == 'join') {
$newDqlPart = array();
foreach ($dqlPart AS $k => $v) {
if (is_numeric($k)) {
$newDqlPart[$this->getRootAlias()] = $v;
} else {
$newDqlPart[$k] = $v;
}
}
$dqlPart = $newDqlPart;
}
if ($append && $isMultiple) {
$this->_dqlParts[$dqlPartName][] = $dqlPart;
if (is_array($dqlPart)) {
$key = key($dqlPart);
$this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
} else {
$this->_dqlParts[$dqlPartName][] = $dqlPart;
}
} else {
$this->_dqlParts[$dqlPartName] = ($isMultiple) ? array($dqlPart) : $dqlPart;
}
@@ -407,6 +503,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.
*
@@ -523,11 +638,12 @@ class QueryBuilder
* @param string $alias The alias of the join
* @param string $conditionType The condition type constant. Either ON or WITH.
* @param string $condition The condition for the join
* @param string $indexBy The index for the join
* @return QueryBuilder This QueryBuilder instance.
*/
public function join($join, $alias, $conditionType = null, $condition = null)
public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{
return $this->innerJoin($join, $alias, $conditionType, $condition);
return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
}
/**
@@ -547,12 +663,18 @@ class QueryBuilder
* @param string $alias The alias of the join
* @param string $conditionType The condition type constant. Either ON or WITH.
* @param string $condition The condition for the join
* @param string $indexBy The index for the join
* @return QueryBuilder This QueryBuilder instance.
*/
public function innerJoin($join, $alias, $conditionType = null, $condition = null)
public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{
return $this->add('join', new Expr\Join(
Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition
$rootAlias = substr($join, 0, strpos($join, '.'));
if (!in_array($rootAlias, $this->getRootAliases())) {
$rootAlias = $this->getRootAlias();
}
return $this->add('join', array(
$rootAlias => new Expr\Join(Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy)
), true);
}
@@ -574,12 +696,18 @@ class QueryBuilder
* @param string $alias The alias of the join
* @param string $conditionType The condition type constant. Either ON or WITH.
* @param string $condition The condition for the join
* @param string $indexBy The index for the join
* @return QueryBuilder This QueryBuilder instance.
*/
public function leftJoin($join, $alias, $conditionType = null, $condition = null)
public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{
return $this->add('join', new Expr\Join(
Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition
$rootAlias = substr($join, 0, strpos($join, '.'));
if (!in_array($rootAlias, $this->getRootAliases())) {
$rootAlias = $this->getRootAlias();
}
return $this->add('join', array(
$rootAlias => new Expr\Join(Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy)
), true);
}
@@ -629,7 +757,7 @@ class QueryBuilder
*/
public function where($predicates)
{
if ( ! (func_num_args() == 1 && ($predicates instanceof Expr\Andx || $predicates instanceof Expr\Orx))) {
if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
$predicates = new Expr\Andx(func_get_args());
}
@@ -865,14 +993,38 @@ class QueryBuilder
private function _getDQLForSelect()
{
return 'SELECT'
. $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('from', array('pre' => ' FROM ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('join', array('pre' => ' ', 'separator' => ' '))
$dql = 'SELECT'
. ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
. $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '));
$fromParts = $this->getDQLPart('from');
$joinParts = $this->getDQLPart('join');
$fromClauses = array();
// Loop through all FROM clauses
if ( ! empty($fromParts)) {
$dql .= ' FROM ';
foreach ($fromParts as $from) {
$fromClause = (string) $from;
if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
foreach ($joinParts[$from->getAlias()] as $join) {
$fromClause .= ' ' . ((string) $join);
}
}
$fromClauses[] = $fromClause;
}
}
$dql .= implode(', ', $fromClauses)
. $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
. $this->_getReducedDQLQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('having', array('pre' => ' HAVING '))
. $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
return $dql;
}
private function _getReducedDQLQueryPart($queryPartName, $options = array())
@@ -952,4 +1104,4 @@ class QueryBuilder
}
}
}
}
}

View File

@@ -63,6 +63,10 @@ class ConvertMappingCommand extends Console\Command\Command
'dest-path', InputArgument::REQUIRED,
'The path to generate your entities classes.'
),
new InputOption(
'force', null, InputOption::VALUE_NONE,
'Force to overwrite existing mapping files.'
),
new InputOption(
'from-database', null, null, 'Whether or not to convert mapping information from existing database.'
),
@@ -73,10 +77,28 @@ class ConvertMappingCommand extends Console\Command\Command
new InputOption(
'num-spaces', null, InputOption::VALUE_OPTIONAL,
'Defines the number of indentation spaces', 4
)
),
new InputOption(
'namespace', null, InputOption::VALUE_OPTIONAL,
'Defines a namespace for the generated entity classes, if converted from database.'
),
))
->setHelp(<<<EOT
Convert mapping information between supported formats.
This is an execute <info>one-time</info> command. It should not be necessary for
you to call this method multiple times, escpecially when using the <comment>--from-database</comment>
flag.
Converting an existing databsae schema into mapping files only solves about 70-80%
of the necessary mapping information. Additionally the detection from an existing
database cannot detect inverse associations, inheritance types,
entities with foreign keys as primary keys and many of the
semantical operations on associations such as cascade.
<comment>Hint:</comment> There is no need to convert YAML or XML mapping files to annotations
every time you make changes. All mapping drivers are first class citizens
in Doctrine 2 and can be used as runtime mapping for the ORM.
EOT
);
}
@@ -89,11 +111,17 @@ EOT
$em = $this->getHelper('em')->getEntityManager();
if ($input->getOption('from-database') === true) {
$em->getConfiguration()->setMetadataDriverImpl(
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
)
$databaseDriver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
);
$em->getConfiguration()->setMetadataDriverImpl(
$databaseDriver
);
if (($namespace = $input->getOption('namespace')) !== null) {
$databaseDriver->setNamespace($namespace);
}
}
$cmf = new DisconnectedClassMetadataFactory();
@@ -119,8 +147,8 @@ EOT
$toType = strtolower($input->getArgument('to-type'));
$cme = new ClassMetadataExporter();
$exporter = $cme->getExporter($toType, $destPath);
$exporter = $this->getExporter($toType, $destPath);
$exporter->setOverwriteExistingFiles( ($input->getOption('force') !== false) );
if ($toType == 'annotation') {
$entityGenerator = new EntityGenerator();
@@ -148,4 +176,11 @@ EOT
$output->write('No Metadata Classes to process.' . PHP_EOL);
}
}
protected function getExporter($toType, $destPath)
{
$cme = new ClassMetadataExporter();
return $cme->getExporter($toType, $destPath);
}
}

View File

@@ -85,6 +85,23 @@ class GenerateEntitiesCommand extends Console\Command\Command
))
->setHelp(<<<EOT
Generate entity classes and method stubs from your mapping information.
If you use the <comment>--update-entities</comment> or <comment>--regenerate-entities</comment> flags your exisiting
code gets overwritten. The EntityGenerator will only append new code to your
file and will not delete the old code. However this approach may still be prone
to error and we suggest you use code repositories such as GIT or SVN to make
backups of your code.
It makes sense to generate the entity code if you are using entities as Data
Access Objects only and dont put much additional logic on them. If you are
however putting much more logic on the entities you should refrain from using
the entity-generator and code your entities manually.
<error>Important:</error> Even if you specified Inheritance options in your
XML or YAML Mapping files the generator cannot generate the base and
child classes for you correctly, because it doesn't know which
class is supposed to extend which. You have to adjust the entity
code manually for inheritance to work!
EOT
);
}

View File

@@ -0,0 +1,80 @@
<?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\Console\Command;
use Doctrine\ORM\Mapping\MappingException;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
/**
* Show information about mapped entities
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.1
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class InfoCommand extends Command
{
protected function configure()
{
$this
->setName('orm:info')
->setDescription('Show basic information about all mapped entities')
->setHelp(<<<EOT
The <info>doctrine:mapping:info</info> shows basic information about which
entities exist and possibly if their mapping information contains errors or
not.
EOT
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
/* @var $entityManager \Doctrine\ORM\EntityManager */
$entityManager = $this->getHelper('em')->getEntityManager();
$entityClassNames = $entityManager->getConfiguration()
->getMetadataDriverImpl()
->getAllClassNames();
if (!$entityClassNames) {
throw new \Exception(
'You do not have any mapped Doctrine ORM entities according to the current configuration. '.
'If you have entities or mapping files you should check your mapping configuration for errors.'
);
}
$output->writeln(sprintf("Found <info>%d</info> mapped entities:", count($entityClassNames)));
foreach ($entityClassNames as $entityClassName) {
try {
$cm = $entityManager->getClassMetadata($entityClassName);
$output->writeln(sprintf("<info>[OK]</info> %s", $entityClassName));
} catch (MappingException $e) {
$output->writeln("<error>[FAIL]</error> ".$entityClassName);
$output->writeln(sprintf("<comment>%s</comment>", $e->getMessage()));
$output->writeln('');
}
}
}
}

View File

@@ -65,7 +65,7 @@ EOT
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
{
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL);
$output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL . PHP_EOL);
if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getCreateSchemaSql($metadatas);

View File

@@ -92,7 +92,7 @@ EOT
}
$output->write('Database schema dropped successfully!' . PHP_EOL);
} else {
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL);
$output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL . PHP_EOL);
if ($isFullDatabaseDrop) {
$sqls = $schemaTool->getDropDatabaseSQL();

View File

@@ -28,7 +28,8 @@ use Symfony\Component\Console\Input\InputArgument,
Doctrine\ORM\Tools\SchemaTool;
/**
* Command to update the database schema for a set of classes based on their mappings.
* Command to generate the SQL needed to update the database schema to match
* the current mapping information.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
@@ -38,37 +39,58 @@ use Symfony\Component\Console\Input\InputArgument,
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Ryan Weaver <ryan@thatsquality.com>
*/
class UpdateCommand extends AbstractCommand
{
protected $name = 'orm:schema-tool:update';
/**
* @see Console\Command\Command
*/
protected function configure()
{
$this
->setName('orm:schema-tool:update')
->setName($this->name)
->setDescription(
'Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.'
'Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.'
)
->setDefinition(array(
new InputOption(
'complete', null, InputOption::VALUE_NONE,
'If defined, all assets of the database which are not relevant to the current metadata will be dropped.'
),
new InputOption(
'dump-sql', null, InputOption::VALUE_NONE,
'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.'
'Dumps the generated SQL statements to the screen (does not execute them).'
),
new InputOption(
'force', null, InputOption::VALUE_NONE,
"Don't ask for the incremental update of the database, but force the operation to run."
'Causes the generated SQL statements to be physically executed against your database.'
),
))
->setHelp(<<<EOT
Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
Beware that if --complete is not defined, it will do a save update, which does not delete any tables, sequences or affected foreign keys.
If defined, all assets of the database which are not relevant to the current metadata are dropped by this command.
));
$fullName = $this->getName();
$this->setHelp(<<<EOT
The <info>$fullName</info> command generates the SQL needed to
synchronize the database schema with the current mapping metadata of the
default entity manager.
For example, if you add metadata for a new column to an entity, this command
would generate and output the SQL needed to add the new column to the database:
<info>$fullName --dump-sql</info>
Alternatively, you can execute the generated queries:
<info>$fullName --force</info>
Finally, be aware that if the <info>--complete</info> option is passed, this
task will drop all database assets (e.g. tables, etc) that are *not* described
by the current metadata. In other words, without this option, this task leaves
untouched any "extra" tables that exist in the database, but which aren't
described by any metadata.
EOT
);
}
@@ -78,26 +100,36 @@ EOT
// Defining if update is complete or not (--complete not defined means $saveMode = true)
$saveMode = ($input->getOption('complete') !== true);
if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
} else if ($input->getOption('force') === true) {
$output->write('Updating database schema...' . PHP_EOL);
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
if (0 == count($sqls)) {
$output->writeln('Nothing to update - your database is already in sync with the current entity metadata.');
return;
}
$dumpSql = (true === $input->getOption('dump-sql'));
$force = (true === $input->getOption('force'));
if ($dumpSql && $force) {
throw new \InvalidArgumentException('You can pass either the --dump-sql or the --force option (but not both simultaneously).');
}
if ($dumpSql) {
$output->writeln(implode(';' . PHP_EOL, $sqls));
} else if ($force) {
$output->writeln('Updating database schema...');
$schemaTool->updateSchema($metadatas, $saveMode);
$output->write('Database schema updated successfully!' . PHP_EOL);
$output->writeln(sprintf('Database schema updated successfully! "<info>%s</info>" queries were executed', count($sqls)));
} else {
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL);
$output->write('Use the incremental update to detect changes during development and use' . PHP_EOL);
$output->write('this SQL DDL to manually update your database in production.' . PHP_EOL . PHP_EOL);
$output->writeln('<comment>ATTENTION</comment>: This operation should not be executed in a production environment.');
$output->writeln(' Use the incremental update to detect changes during development and use');
$output->writeln(' the SQL DDL provided to manually update your database in production.');
$output->writeln('');
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
if (count($sqls)) {
$output->write('Schema-Tool would execute ' . count($sqls) . ' queries to update the database.' . PHP_EOL);
$output->write('Please run the operation with --force to execute these queries or use --dump-sql to see them.' . PHP_EOL);
} else {
$output->write('Nothing to update. The database is in sync with the current entity metadata.' . PHP_EOL);
}
$output->writeln(sprintf('The Schema-Tool would execute <info>"%s"</info> queries to update the database.', count($sqls)));
$output->writeln('Please run the operation by passing one of the following options:');
$output->writeln(sprintf(' <info>%s --force</info> to execute the command', $this->getName()));
$output->writeln(sprintf(' <info>%s --dump-sql</info> to dump the SQL statements to the screen', $this->getName()));
}
}
}

View File

@@ -64,6 +64,7 @@ class ConsoleRunner
new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(),
new \Doctrine\ORM\Tools\Console\Command\InfoCommand()
));
}
}

View File

@@ -70,10 +70,10 @@ class ConvertDoctrine1Schema
if (is_dir($path)) {
$files = glob($path . '/*.yml');
foreach ($files as $file) {
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::load($file));
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($file));
}
} else {
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::load($path));
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($path));
}
}
@@ -188,7 +188,6 @@ class ConvertDoctrine1Schema
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
} else if (isset($column['sequence'])) {
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE);
$metadata->setSequenceGeneratorDefinition($definition);
$definition = array(
'sequenceName' => is_array($column['sequence']) ? $column['sequence']['name']:$column['sequence']
);
@@ -198,6 +197,7 @@ class ConvertDoctrine1Schema
if (isset($column['sequence']['value'])) {
$definition['initialValue'] = $column['sequence']['value'];
}
$metadata->setSequenceGeneratorDefinition($definition);
}
return $fieldMapping;
}

View File

@@ -52,6 +52,17 @@ class DisconnectedClassMetadataFactory extends ClassMetadataFactory
return $metadata;
}
/**
* Validate runtime metadata is correctly defined.
*
* @param ClassMetadata $class
* @param ClassMetadata $parent
*/
protected function validateRuntimeMetadata($class, $parent)
{
// validate nothing
}
/**
* @override
*/

View File

@@ -47,14 +47,18 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo,
*/
class EntityGenerator
{
/**
* @var bool
*/
private $_backupExisting = true;
/** The extension to use for written php files */
private $_extension = '.php';
/** Whether or not the current ClassMetadataInfo instance is new or old */
private $_isNew = true;
/** If isNew is false then this variable contains instance of ReflectionClass for current entity */
private $_reflection;
private $_staticReflection = array();
/** Number of spaces to use for indention in generated code */
private $_numSpaces = 4;
@@ -68,6 +72,11 @@ class EntityGenerator
/** Whether or not to generation annotations */
private $_generateAnnotations = false;
/**
* @var string
*/
private $_annotationsPrefix = '';
/** Whether or not to generated sub methods */
private $_generateEntityStubMethods = false;
@@ -82,6 +91,8 @@ class EntityGenerator
<namespace>
use Doctrine\ORM\Mapping as ORM;
<entityAnnotation>
<entityClassName>
{
@@ -92,7 +103,7 @@ class EntityGenerator
'/**
* <description>
*
* @return <variableType>$<variableName>
* @return <variableType>
*/
public function <methodName>()
{
@@ -130,11 +141,25 @@ public function <methodName>()
<spaces>// Add your code here
}';
private static $_constructorMethodTemplate =
'public function __construct()
{
<spaces><collections>
}
';
public function __construct()
{
if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
$this->_annotationsPrefix = 'ORM\\';
}
}
/**
* Generate and write entity classes for the given array of ClassMetadataInfo instances
*
* @param array $metadatas
* @param string $outputDirectory
* @param string $outputDirectory
* @return void
*/
public function generate(array $metadatas, $outputDirectory)
@@ -148,7 +173,7 @@ public function <methodName>()
* Generated and write entity class to disk for the given ClassMetadataInfo instance
*
* @param ClassMetadataInfo $metadata
* @param string $outputDirectory
* @param string $outputDirectory
* @return void
*/
public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
@@ -160,16 +185,23 @@ public function <methodName>()
mkdir($dir, 0777, true);
}
$this->_isNew = ! file_exists($path);
$this->_isNew = !file_exists($path) || (file_exists($path) && $this->_regenerateEntityIfExists);
if ( ! $this->_isNew) {
require_once $path;
$this->_parseTokensInEntityFile(file_get_contents($path));
} else {
$this->_staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array());
}
$this->_reflection = new \ReflectionClass($metadata->name);
if ($this->_backupExisting && file_exists($path)) {
$backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
if (!copy($path, $backupPath)) {
throw new \RuntimeException("Attempt to backup overwritten entitiy file but copy operation failed.");
}
}
// If entity doesn't exist or we're re-generating the entities entirely
if ($this->_isNew || ( ! $this->_isNew && $this->_regenerateEntityIfExists)) {
if ($this->_isNew) {
file_put_contents($path, $this->generateEntityClass($metadata));
// If entity exists and we're allowed to update the entity class
} else if ( ! $this->_isNew && $this->_updateEntityIfExists) {
@@ -180,7 +212,7 @@ public function <methodName>()
/**
* Generate a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance
*
* @param ClassMetadataInfo $metadata
* @param ClassMetadataInfo $metadata
* @return string $code
*/
public function generateEntityClass(ClassMetadataInfo $metadata)
@@ -206,8 +238,8 @@ public function <methodName>()
/**
* Generate the updated code for the given ClassMetadataInfo and entity at path
*
* @param ClassMetadataInfo $metadata
* @param string $path
* @param ClassMetadataInfo $metadata
* @param string $path
* @return string $code;
*/
public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
@@ -224,7 +256,7 @@ public function <methodName>()
/**
* Set the number of spaces the exported class should have
*
* @param integer $numSpaces
* @param integer $numSpaces
* @return void
*/
public function setNumSpaces($numSpaces)
@@ -236,7 +268,7 @@ public function <methodName>()
/**
* Set the extension to use when writing php files to disk
*
* @param string $extension
* @param string $extension
* @return void
*/
public function setExtension($extension)
@@ -257,7 +289,7 @@ public function <methodName>()
/**
* Set whether or not to generate annotations for the entity
*
* @param bool $bool
* @param bool $bool
* @return void
*/
public function setGenerateAnnotations($bool)
@@ -265,10 +297,23 @@ public function <methodName>()
$this->_generateAnnotations = $bool;
}
/**
* Set an annotation prefix.
*
* @param string $prefix
*/
public function setAnnotationPrefix($prefix)
{
if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
return;
}
$this->_annotationsPrefix = $prefix;
}
/**
* Set whether or not to try and update the entity if it already exists
*
* @param bool $bool
* @param bool $bool
* @return void
*/
public function setUpdateEntityIfExists($bool)
@@ -298,6 +343,14 @@ public function <methodName>()
$this->_generateEntityStubMethods = $bool;
}
/**
* Should an existing entity be backed up if it already exists?
*/
public function setBackupExisting($bool)
{
$this->_backupExisting = $bool;
}
private function _generateEntityNamespace(ClassMetadataInfo $metadata)
{
if ($this->_hasNamespace($metadata)) {
@@ -313,7 +366,7 @@ public function <methodName>()
private function _generateEntityBody(ClassMetadataInfo $metadata)
{
$fieldMappingProperties = $this->_generateEntityFieldMappingProperties($metadata);
$fieldMappingProperties = $this->_generateEntityFieldMappingProperties($metadata);
$associationMappingProperties = $this->_generateEntityAssociationMappingProperties($metadata);
$stubMethods = $this->_generateEntityStubMethods ? $this->_generateEntityStubMethods($metadata) : null;
$lifecycleCallbackMethods = $this->_generateEntityLifecycleCallbackMethods($metadata);
@@ -328,6 +381,8 @@ public function <methodName>()
$code[] = $associationMappingProperties;
}
$code[] = $this->_generateEntityConstructor($metadata);
if ($stubMethods) {
$code[] = $stubMethods;
}
@@ -339,14 +394,104 @@ public function <methodName>()
return implode("\n", $code);
}
private function _generateEntityConstructor(ClassMetadataInfo $metadata)
{
if ($this->_hasMethod('__construct', $metadata)) {
return '';
}
$collections = array();
foreach ($metadata->associationMappings AS $mapping) {
if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
$collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
}
}
if ($collections) {
return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate));
}
return '';
}
/**
* @todo this won't work if there is a namespace in brackets and a class outside of it.
* @param string $src
*/
private function _parseTokensInEntityFile($src)
{
$tokens = token_get_all($src);
$lastSeenNamespace = "";
$lastSeenClass = false;
$inNamespace = false;
$inClass = false;
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
continue;
}
if ($inNamespace) {
if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
$lastSeenNamespace .= $token[1];
} else if (is_string($token) && in_array($token, array(';', '{'))) {
$inNamespace = false;
}
}
if ($inClass) {
$inClass = false;
$lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
$this->_staticReflection[$lastSeenClass]['properties'] = array();
$this->_staticReflection[$lastSeenClass]['methods'] = array();
}
if ($token[0] == T_NAMESPACE) {
$lastSeenNamespace = "";
$inNamespace = true;
} else if ($token[0] == T_CLASS) {
$inClass = true;
} else if ($token[0] == T_FUNCTION) {
if ($tokens[$i+2][0] == T_STRING) {
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1];
} else if ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1];
}
} else if (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
$this->_staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
}
}
}
private function _hasProperty($property, ClassMetadataInfo $metadata)
{
return ($this->_isNew) ? false : $this->_reflection->hasProperty($property);
if ($this->_extendsClass()) {
// don't generate property if its already on the base class.
$reflClass = new \ReflectionClass($this->_getClassToExtend());
if ($reflClass->hasProperty($property)) {
return true;
}
}
return (
isset($this->_staticReflection[$metadata->name]) &&
in_array($property, $this->_staticReflection[$metadata->name]['properties'])
);
}
private function _hasMethod($method, ClassMetadataInfo $metadata)
{
return ($this->_isNew) ? false : $this->_reflection->hasMethod($method);
if ($this->_extendsClass()) {
// don't generate method if its already on the base class.
$reflClass = new \ReflectionClass($this->_getClassToExtend());
if ($reflClass->hasMethod($method)) {
return true;
}
}
return (
isset($this->_staticReflection[$metadata->name]) &&
in_array($method, $this->_staticReflection[$metadata->name]['methods'])
);
}
private function _hasNamespace(ClassMetadataInfo $metadata)
@@ -405,9 +550,9 @@ public function <methodName>()
}
if ($metadata->isMappedSuperclass) {
$lines[] = ' * @MappedSupperClass';
$lines[] = ' * @' . $this->_annotationsPrefix . 'MappedSuperClass';
} else {
$lines[] = ' * @Entity';
$lines[] = ' * @' . $this->_annotationsPrefix . 'Entity';
}
if ($metadata->customRepositoryClassName) {
@@ -415,7 +560,7 @@ public function <methodName>()
}
if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
$lines[] = ' * @HasLifecycleCallbacks';
$lines[] = ' * @' . $this->_annotationsPrefix . 'HasLifecycleCallbacks';
}
}
@@ -431,17 +576,13 @@ public function <methodName>()
$table[] = 'name="' . $metadata->table['name'] . '"';
}
if (isset($metadata->table['schema'])) {
$table[] = 'schema="' . $metadata->table['schema'] . '"';
}
return '@Table(' . implode(', ', $table) . ')';
return '@' . $this->_annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
}
private function _generateInheritanceAnnotation($metadata)
{
if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
return '@InheritanceType("'.$this->_getInheritanceTypeString($metadata->inheritanceType).'")';
return '@' . $this->_annotationsPrefix . 'InheritanceType("'.$this->_getInheritanceTypeString($metadata->inheritanceType).'")';
}
}
@@ -453,7 +594,7 @@ public function <methodName>()
. '", type="' . $discrColumn['type']
. '", length=' . $discrColumn['length'];
return '@DiscriminatorColumn(' . $columnDefinition . ')';
return '@' . $this->_annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
}
}
@@ -466,7 +607,7 @@ public function <methodName>()
$inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
}
return '@DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
return '@' . $this->_annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
}
}
@@ -475,7 +616,7 @@ public function <methodName>()
$methods = array();
foreach ($metadata->fieldMappings as $fieldMapping) {
if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id']) {
if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) {
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
$methods[] = $code;
}
@@ -494,23 +635,7 @@ public function <methodName>()
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
if ($associationMapping['isOwningSide']) {
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
} else {
if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
$methods[] = $code;
}
}
} else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
} else if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
@@ -536,8 +661,10 @@ public function <methodName>()
}
}
return implode('', $methods);
return implode("\n\n", $methods);
}
return "";
}
private function _generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
@@ -562,7 +689,8 @@ public function <methodName>()
$lines = array();
foreach ($metadata->fieldMappings as $fieldMapping) {
if ($this->_hasProperty($fieldMapping['fieldName'], $metadata)) {
if ($this->_hasProperty($fieldMapping['fieldName'], $metadata) ||
$metadata->isInheritedField($fieldMapping['fieldName'])) {
continue;
}
@@ -576,11 +704,18 @@ public function <methodName>()
private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null)
{
$methodName = $type . Inflector::classify($fieldName);
if ($type == "add") {
$addMethod = explode("\\", $typeHint);
$addMethod = end($addMethod);
$methodName = $type . $addMethod;
} else {
$methodName = $type . Inflector::classify($fieldName);
}
if ($this->_hasMethod($methodName, $metadata)) {
return;
}
$this->_staticReflection[$metadata->name]['methods'][] = $methodName;
$var = sprintf('_%sMethodTemplate', $type);
$template = self::$$var;
@@ -613,9 +748,10 @@ public function <methodName>()
if ($this->_hasMethod($methodName, $metadata)) {
return;
}
$this->_staticReflection[$metadata->name]['methods'][] = $methodName;
$replacements = array(
'<name>' => $name,
'<name>' => $this->_annotationsPrefix . $name,
'<methodName>' => $methodName,
);
@@ -649,7 +785,7 @@ public function <methodName>()
}
if (isset($joinColumn['onDelete'])) {
$joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false');
$joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
}
if (isset($joinColumn['onUpdate'])) {
@@ -660,7 +796,7 @@ public function <methodName>()
$joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
}
return '@JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
return '@' . $this->_annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
}
private function _generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
@@ -710,17 +846,17 @@ public function <methodName>()
if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
$typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
$typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
}
if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
$typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
}
$lines[] = $this->_spaces . ' * @' . $type . '(' . implode(', ', $typeOptions) . ')';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
$lines[] = $this->_spaces . ' * @JoinColumns({';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinColumns({';
$joinColumnsLines = array();
@@ -742,7 +878,7 @@ public function <methodName>()
$joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
}
$lines[] = $this->_spaces . ' * @JoinTable(' . implode(', ', $joinTable) . ',';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
$lines[] = $this->_spaces . ' * joinColumns={';
foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
@@ -761,10 +897,10 @@ public function <methodName>()
}
if (isset($associationMapping['orderBy'])) {
$lines[] = $this->_spaces . ' * @OrderBy({';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'OrderBy({';
foreach ($associationMapping['orderBy'] as $name => $direction) {
$lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",';
$lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",';
}
$lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
@@ -815,31 +951,17 @@ public function <methodName>()
$column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
}
if (isset($fieldMapping['options'])) {
$options = array();
foreach ($fieldMapping['options'] as $key => $value) {
$value = var_export($value, true);
$value = str_replace("'", '"', $value);
$options[] = ! is_numeric($key) ? $key . '=' . $value:$value;
}
if ($options) {
$column[] = 'options={' . implode(', ', $options) . '}';
}
}
if (isset($fieldMapping['unique'])) {
$column[] = 'unique=' . var_export($fieldMapping['unique'], true);
}
$lines[] = $this->_spaces . ' * @Column(' . implode(', ', $column) . ')';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
if (isset($fieldMapping['id']) && $fieldMapping['id']) {
$lines[] = $this->_spaces . ' * @Id';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Id';
if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$lines[] = $this->_spaces.' * @GeneratedValue(strategy="' . $generatorType . '")';
$lines[] = $this->_spaces.' * @' . $this->_annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
}
if ($metadata->sequenceGeneratorDefinition) {
@@ -857,12 +979,12 @@ public function <methodName>()
$sequenceGenerator[] = 'initialValue="' . $metadata->sequenceGeneratorDefinition['initialValue'] . '"';
}
$lines[] = $this->_spaces . ' * @SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
}
}
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$lines[] = $this->_spaces . ' * @Version';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Version';
}
}
@@ -941,4 +1063,4 @@ public function <methodName>()
throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
}
}
}
}

View File

@@ -23,10 +23,11 @@
namespace Doctrine\ORM\Tools\Export\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Tools\Export\ExportException;
/**
* Abstract base class which is to be used for the Exporter drivers
* which can be found in Doctrine\ORM\Tools\Export\Driver
* which can be found in \Doctrine\ORM\Tools\Export\Driver
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
@@ -39,12 +40,18 @@ abstract class AbstractExporter
protected $_metadata = array();
protected $_outputDir;
protected $_extension;
protected $_overwriteExistingFiles = false;
public function __construct($dir = null)
{
$this->_outputDir = $dir;
}
public function setOverwriteExistingFiles($overwrite)
{
$this->_overwriteExistingFiles = $overwrite;
}
/**
* Converts a single ClassMetadata instance to the exported format
* and returns it
@@ -110,6 +117,9 @@ abstract class AbstractExporter
if ( ! is_dir($dir)) {
mkdir($dir, 0777, true);
}
if (file_exists($path) && !$this->_overwriteExistingFiles) {
throw ExportException::attemptOverwriteExistingFile($path);
}
file_put_contents($path, $output);
}
}

View File

@@ -120,22 +120,30 @@ class PhpExporter extends AbstractExporter
$associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
$method = 'mapOneToMany';
$oneToManyMappingArray = array(
'mappedBy' => $associationMapping['mappedBy'],
'orphanRemoval' => $associationMapping['orphanRemoval'],
'orderBy' => $associationMapping['orderBy']
$method = 'mapOneToMany';
$potentialAssociationMappingIndexes = array(
'mappedBy',
'orphanRemoval',
'orderBy',
);
foreach ($potentialAssociationMappingIndexes as $index) {
if (isset($associationMapping[$index])) {
$oneToManyMappingArray[$index] = $associationMapping[$index];
}
}
$associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
} else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
$method = 'mapManyToMany';
$manyToManyMappingArray = array(
'mappedBy' => $associationMapping['mappedBy'],
'joinTable' => $associationMapping['joinTable'],
'orderBy' => $associationMapping['orderBy']
$method = 'mapManyToMany';
$potentialAssociationMappingIndexes = array(
'mappedBy',
'joinTable',
'orderBy',
);
foreach ($potentialAssociationMappingIndexes as $index) {
if (isset($associationMapping[$index])) {
$manyToManyMappingArray[$index] = $associationMapping[$index];
}
}
$associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray);
}

View File

@@ -43,7 +43,7 @@ class YamlExporter extends AbstractExporter
*
* TODO: Should this code be pulled out in to a toArray() method in ClassMetadata
*
* @param ClassMetadataInfo $metadata
* @param ClassMetadataInfo $metadata
* @return mixed $exported
*/
public function exportClassMetadata(ClassMetadataInfo $metadata)
@@ -84,9 +84,9 @@ class YamlExporter extends AbstractExporter
if (isset($metadata->table['uniqueConstraints'])) {
$array['uniqueConstraints'] = $metadata->table['uniqueConstraints'];
}
$fieldMappings = $metadata->fieldMappings;
$ids = array();
foreach ($fieldMappings as $name => $fieldMapping) {
$fieldMapping['column'] = $fieldMapping['columnName'];
@@ -94,7 +94,7 @@ class YamlExporter extends AbstractExporter
$fieldMapping['columnName'],
$fieldMapping['fieldName']
);
if ($fieldMapping['column'] == $name) {
unset($fieldMapping['column']);
}
@@ -111,7 +111,7 @@ class YamlExporter extends AbstractExporter
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $this->_getIdGeneratorTypeString($metadata->generatorType);
}
if ($ids) {
$array['fields'] = $ids;
}
@@ -145,7 +145,7 @@ class YamlExporter extends AbstractExporter
'targetEntity' => $associationMapping['targetEntity'],
'cascade' => $cascade,
);
if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
$joinColumns = $associationMapping['joinColumns'];
$newJoinColumns = array();
@@ -164,7 +164,7 @@ class YamlExporter extends AbstractExporter
'joinColumns' => $newJoinColumns,
'orphanRemoval' => $associationMapping['orphanRemoval'],
);
$associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
$array['oneToOne'][$name] = $associationMappingArray;
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
@@ -172,7 +172,7 @@ class YamlExporter extends AbstractExporter
'mappedBy' => $associationMapping['mappedBy'],
'inversedBy' => $associationMapping['inversedBy'],
'orphanRemoval' => $associationMapping['orphanRemoval'],
'orderBy' => $associationMapping['orderBy']
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
);
$associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
@@ -181,10 +181,10 @@ class YamlExporter extends AbstractExporter
$manyToManyMappingArray = array(
'mappedBy' => $associationMapping['mappedBy'],
'inversedBy' => $associationMapping['inversedBy'],
'joinTable' => $associationMapping['joinTable'],
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
'joinTable' => isset($associationMapping['joinTable']) ? $associationMapping['joinTable'] : null,
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
);
$associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray);
$array['manyToMany'][$name] = $associationMappingArray;
}

View File

@@ -15,4 +15,9 @@ class ExportException extends ORMException
{
return new self("The mapping driver '$type' does not exist");
}
public static function attemptOverwriteExistingFile($file)
{
return new self("Attempting to overwrite an existing file '".$file."'.");
}
}

View File

@@ -56,7 +56,7 @@ class SchemaTool
* Initializes a new SchemaTool instance that uses the connection of the
* provided EntityManager.
*
* @param Doctrine\ORM\EntityManager $em
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{
@@ -174,18 +174,18 @@ class SchemaTool
$discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table);
} else {
// Add an ID FK column to child tables
/* @var Doctrine\ORM\Mapping\ClassMetadata $class */
/* @var \Doctrine\ORM\Mapping\ClassMetadata $class */
$idMapping = $class->fieldMappings[$class->identifier[0]];
$this->_gatherColumn($class, $idMapping, $table);
$columnName = $class->getQuotedColumnName($class->identifier[0], $this->_platform);
// TODO: This seems rather hackish, can we optimize it?
$table->getColumn($class->identifier[0])->setAutoincrement(false);
$table->getColumn($columnName)->setAutoincrement(false);
$pkColumns[] = $columnName;
// Add a FK constraint on the ID column
$table->addUnnamedForeignKeyConstraint(
$this->_em->getClassMetadata($class->rootEntityName)->getTableName(),
$this->_em->getClassMetadata($class->rootEntityName)->getQuotedTableName($this->_platform),
array($columnName), array($columnName), array('onDelete' => 'CASCADE')
);
}
@@ -199,15 +199,31 @@ class SchemaTool
$this->_gatherRelationsSql($class, $table, $schema);
}
$pkColumns = array();
foreach ($class->identifier AS $identifierField) {
if (isset($class->fieldMappings[$identifierField])) {
$pkColumns[] = $class->getQuotedColumnName($identifierField, $this->_platform);
} else if (isset($class->associationMappings[$identifierField])) {
/* @var $assoc \Doctrine\ORM\Mapping\OneToOne */
$assoc = $class->associationMappings[$identifierField];
foreach ($assoc['joinColumns'] AS $joinColumn) {
$pkColumns[] = $joinColumn['name'];
}
}
}
if (!$table->hasIndex('primary')) {
$table->setPrimaryKey($pkColumns);
}
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);
}
}
@@ -275,16 +291,21 @@ class SchemaTool
$pkColumns = array();
foreach ($class->fieldMappings as $fieldName => $mapping) {
if ($class->isInheritanceTypeSingleTable() && isset($mapping['inherited'])) {
continue;
}
$column = $this->_gatherColumn($class, $mapping, $table);
if ($class->isIdentifier($mapping['fieldName'])) {
$pkColumns[] = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform);
}
}
// For now, this is a hack required for single table inheritence, since this method is called
// twice by single table inheritence relations
if(!$table->hasIndex('primary')) {
$table->setPrimaryKey($pkColumns);
//$table->setPrimaryKey($pkColumns);
}
return $columns;
@@ -405,13 +426,47 @@ class SchemaTool
}
}
/**
* Get the class metadata that is responsible for the definition of the referenced column name.
*
* Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its
* not a simple field, go through all identifier field names that are associations recursivly and
* find that referenced column name.
*
* TODO: Is there any way to make this code more pleasing?
*
* @param ClassMetadata $class
* @param string $referencedColumnName
* @return array(ClassMetadata, referencedFieldName)
*/
private function getDefiningClass($class, $referencedColumnName)
{
$referencedFieldName = $class->getFieldName($referencedColumnName);
if ($class->hasField($referencedFieldName)) {
return array($class, $referencedFieldName);
} else if (in_array($referencedColumnName, $class->getIdentifierColumnNames())) {
// it seems to be an entity as foreign key
foreach ($class->getIdentifierFieldNames() AS $fieldName) {
if ($class->hasAssociation($fieldName) && $class->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) {
return $this->getDefiningClass(
$this->_em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']),
$class->getSingleAssociationReferencedJoinColumnName($fieldName)
);
}
}
}
return null;
}
/**
* Gather columns and fk constraints that are required for one part of relationship.
*
* @param array $joinColumns
* @param \Doctrine\DBAL\Schema\Table $theJoinTable
* @param ClassMetadata $class
* @param \Doctrine\ORM\Mapping\AssociationMapping $mapping
* @param array $mapping
* @param array $primaryKeyColumns
* @param array $uniqueConstraints
*/
@@ -420,12 +475,13 @@ class SchemaTool
$localColumns = array();
$foreignColumns = array();
$fkOptions = array();
$foreignTableName = $class->getQuotedTableName($this->_platform);
foreach ($joinColumns as $joinColumn) {
$columnName = $joinColumn['name'];
$referencedFieldName = $class->getFieldName($joinColumn['referencedColumnName']);
list($definingClass, $referencedFieldName) = $this->getDefiningClass($class, $joinColumn['referencedColumnName']);
if ( ! $class->hasField($referencedFieldName)) {
if (!$definingClass) {
throw new \Doctrine\ORM\ORMException(
"Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ".
$mapping['sourceEntity'] . " towards ". $mapping['targetEntity'] . " does not exist."
@@ -441,7 +497,7 @@ class SchemaTool
// It might exist already if the foreign key is mapped into a regular
// property as well.
$fieldMapping = $class->getFieldMapping($referencedFieldName);
$fieldMapping = $definingClass->getFieldMapping($referencedFieldName);
$columnDef = null;
if (isset($joinColumn['columnDefinition'])) {
@@ -453,16 +509,14 @@ class SchemaTool
if (isset($joinColumn['nullable'])) {
$columnOptions['notnull'] = !$joinColumn['nullable'];
}
if ($fieldMapping['type'] == "string") {
if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) {
$columnOptions['length'] = $fieldMapping['length'];
} else if ($fieldMapping['type'] == "decimal") {
$columnOptions['scale'] = $fieldMapping['scale'];
$columnOptions['precision'] = $fieldMapping['precision'];
}
$theJoinTable->addColumn(
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions
);
$theJoinTable->addColumn($columnName, $fieldMapping['type'], $columnOptions);
}
if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) {
@@ -479,7 +533,7 @@ class SchemaTool
}
$theJoinTable->addUnnamedForeignKeyConstraint(
$class->getTableName(), $localColumns, $foreignColumns, $fkOptions
$foreignTableName, $localColumns, $foreignColumns, $fkOptions
);
}
@@ -498,7 +552,11 @@ class SchemaTool
$conn = $this->_em->getConnection();
foreach ($dropSchemaSql as $sql) {
$conn->executeQuery($sql);
try {
$conn->executeQuery($sql);
} catch(\Exception $e) {
}
}
}
@@ -534,65 +592,62 @@ class SchemaTool
}
/**
*
* Get SQL to drop the tables defined by the passed classes.
*
* @param array $classes
* @return array
*/
public function getDropSchemaSQL(array $classes)
{
$visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->_platform);
$schema = $this->getSchemaFromMetadata($classes);
$sm = $this->_em->getConnection()->getSchemaManager();
$fullSchema = $sm->createSchema();
foreach ($fullSchema->getTables() AS $table) {
if (!$schema->hasTable($table->getName())) {
foreach ($table->getForeignKeys() AS $foreignKey) {
/* @var $foreignKey \Doctrine\DBAL\Schema\ForeignKeyConstraint */
if ($schema->hasTable($foreignKey->getForeignTableName())) {
$visitor->acceptForeignKey($table, $foreignKey);
}
}
} else {
$visitor->acceptTable($table);
foreach ($table->getForeignKeys() AS $foreignKey) {
$visitor->acceptForeignKey($table, $foreignKey);
}
}
}
$sql = array();
$orderedTables = array();
foreach ($classes AS $class) {
if ($class->isIdGeneratorSequence() && !$class->isMappedSuperclass && $class->name == $class->rootEntityName && $this->_platform->supportsSequences()) {
$sql[] = $this->_platform->getDropSequenceSQL($class->sequenceGeneratorDefinition['sequenceName']);
if ($this->_platform->supportsSequences()) {
foreach ($schema->getSequences() AS $sequence) {
$visitor->acceptSequence($sequence);
}
foreach ($schema->getTables() AS $table) {
/* @var $sequence Table */
if ($table->hasPrimaryKey()) {
$columns = $table->getPrimaryKey()->getColumns();
if (count($columns) == 1) {
$checkSequence = $table->getName() . "_" . $columns[0] . "_seq";
if ($fullSchema->hasSequence($checkSequence)) {
$visitor->acceptSequence($fullSchema->getSequence($checkSequence));
}
}
}
}
}
$commitOrder = $this->_getCommitOrder($classes);
$associationTables = $this->_getAssociationTables($commitOrder);
// Drop association tables first
foreach ($associationTables as $associationTable) {
if (!in_array($associationTable, $orderedTables)) {
$orderedTables[] = $associationTable;
}
}
// Drop tables in reverse commit order
for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
$class = $commitOrder[$i];
if (($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName)
|| $class->isMappedSuperclass) {
continue;
}
if (!in_array($class->getTableName(), $orderedTables)) {
$orderedTables[] = $class->getTableName();
}
}
$dropTablesSql = array();
foreach ($orderedTables AS $tableName) {
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
$foreignKeys = $sm->listTableForeignKeys($tableName);
foreach ($foreignKeys AS $foreignKey) {
$sql[] = $this->_platform->getDropForeignKeySQL($foreignKey, $tableName);
}
$dropTablesSql[] = $this->_platform->getDropTableSQL($tableName);
}
return array_merge($sql, $dropTablesSql);
return $visitor->getQueries();
}
/**
* Updates the database schema of the given classes by comparing the ClassMetadata
* ins$tableNametances to the current database schema that is inspected.
* instances to the current database schema that is inspected. If $saveMode is set
* to true the command is executed in the Database, else SQL is returned.
*
* @param array $classes
* @param boolean $saveMode
* @return void
*/
public function updateSchema(array $classes, $saveMode=false)
@@ -608,8 +663,11 @@ class SchemaTool
/**
* Gets the sequence of SQL statements that need to be performed in order
* to bring the given class mappings in-synch with the relational schema.
* If $saveMode is set to true the command is executed in the Database,
* else SQL is returned.
*
* @param array $classes The classes to consider.
* @param boolean $saveMode True for writing to DB, false for SQL string
* @return array The sequence of SQL statements.
*/
public function getUpdateSchemaSql(array $classes, $saveMode=false)
@@ -628,44 +686,4 @@ class SchemaTool
return $schemaDiff->toSql($this->_platform);
}
}
private function _getCommitOrder(array $classes)
{
$calc = new CommitOrderCalculator;
// Calculate dependencies
foreach ($classes as $class) {
$calc->addClass($class);
foreach ($class->associationMappings as $assoc) {
if ($assoc['isOwningSide']) {
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
if ( ! $calc->hasClass($targetClass->name)) {
$calc->addClass($targetClass);
}
// add dependency ($targetClass before $class)
$calc->addDependency($targetClass, $class);
}
}
}
return $calc->getCommitOrder();
}
private function _getAssociationTables(array $classes)
{
$associationTables = array();
foreach ($classes as $class) {
foreach ($class->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {
$associationTables[] = $assoc['joinTable']['name'];
}
}
}
return $associationTables;
}
}

View File

@@ -214,6 +214,6 @@ class SchemaValidator
$schemaTool = new SchemaTool($this->em);
$allMetadata = $this->em->getMetadataFactory()->getAllMetadata();
return (count($schemaTool->getUpdateSchemaSql($allMetadata, false)) == 0);
return (count($schemaTool->getUpdateSchemaSql($allMetadata, true)) == 0);
}
}

View File

@@ -0,0 +1,194 @@
<?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;
use Doctrine\Common\ClassLoader;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\ORM\Mapping\Driver\YamlDriver;
/**
* Convenience class for setting up Doctrine from different installations and configurations.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class Setup
{
/**
* Use this method to register all autoloaders for a setup where Doctrine is checked out from
* its github repository at {@link http://github.com/doctrine/doctrine2}
*
* @param string $gitCheckoutRootPath
* @return void
*/
static public function registerAutoloadGit($gitCheckoutRootPath)
{
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
require_once $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php";
}
$loader = new ClassLoader("Doctrine\Common", $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib");
$loader->register();
$loader = new ClassLoader("Doctrine\DBAL", $gitCheckoutRootPath . "/lib/vendor/doctrine-dbal/lib");
$loader->register();
$loader = new ClassLoader("Doctrine\ORM", $gitCheckoutRootPath . "/lib");
$loader->register();
$loader = new ClassLoader("Symfony\Component", $gitCheckoutRootPath . "/lib/vendor");
$loader->register();
}
/**
* Use this method to register all autoloaders for a setup where Doctrine is installed
* though {@link http://pear.doctrine-project.org}.
*
* @return void
*/
static public function registerAutoloadPEAR()
{
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
require_once "Doctrine/Common/ClassLoader.php";
}
$loader = new ClassLoader("Doctrine");
$loader->register();
$parts = explode(PATH_SEPARATOR, get_include_path());
foreach ($parts AS $includePath) {
if ($includePath != "." && file_exists($includePath . "/Doctrine")) {
$loader = new ClassLoader("Symfony\Component", $includePath . "/Doctrine");
$loader->register();
return;
}
}
}
/**
* Use this method to register all autoloads for a downloaded Doctrine library.
* Pick the directory the library was uncompressed into.
*
* @param string $directory
*/
static public function registerAutoloadDirectory($directory)
{
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
require_once $directory . "/Doctrine/Common/ClassLoader.php";
}
$loader = new ClassLoader("Doctrine", $directory);
$loader->register();
$loader = new ClassLoader("Symfony\Component", $directory . "/Doctrine");
$loader->register();
}
/**
* Create a configuration with an annotation metadata driver.
*
* @param array $paths
* @param boolean $isDevMode
* @param string $proxyDir
* @param Cache $cache
* @return Configuration
*/
static public function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
{
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths));
return $config;
}
/**
* Create a configuration with an annotation metadata driver.
*
* @param array $paths
* @param boolean $isDevMode
* @param string $proxyDir
* @param Cache $cache
* @return Configuration
*/
static public function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
{
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl(new XmlDriver($paths));
return $config;
}
/**
* Create a configuration with an annotation metadata driver.
*
* @param array $paths
* @param boolean $isDevMode
* @param string $proxyDir
* @param Cache $cache
* @return Configuration
*/
static public function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
{
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl(new YamlDriver($paths));
return $config;
}
/**
* Create a configuration without a metadata driver.
*
* @param bool $isDevMode
* @param string $proxyDir
* @param Cache $cache
* @return Configuration
*/
static public function createConfiguration($isDevMode = false, $proxyDir = null, Cache $cache = null)
{
$proxyDir = $proxyDir ?: sys_get_temp_dir();
if ($isDevMode === false && $cache === null) {
if (extension_loaded('apc')) {
$cache = new \Doctrine\Common\Cache\ApcCache;
} else if (extension_loaded('xcache')) {
$cache = new \Doctrine\Common\Cache\XcacheCache;
} else if (extension_loaded('memcache')) {
$memcache = new \Memcache();
$memcache->connect('127.0.0.1');
$cache = new \Doctrine\Common\Cache\MemcacheCache();
$cache->setMemcache($memcache);
} else {
$cache = new ArrayCache;
}
} else if ($cache === null) {
$cache = new ArrayCache;
}
$cache->setNamespace("dc2_" . md5($proxyDir) . "_"); // to avoid collisions
$config = new Configuration();
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
$config->setResultCacheImpl($cache);
$config->setProxyDir( $proxyDir );
$config->setProxyNamespace('DoctrineProxies');
$config->setAutoGenerateProxyClasses($isDevMode);
return $config;
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,743 +0,0 @@
<?php
namespace Symfony\Component\Console;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\DialogHelper;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* An Application is the container for a collection of commands.
*
* It is the main entry point of a Console application.
*
* This class is optimized for a standard CLI environment.
*
* Usage:
*
* $app = new Application('myapp', '1.0 (stable)');
* $app->add(new SimpleCommand());
* $app->run();
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Application
{
protected $commands;
protected $aliases;
protected $wantHelps = false;
protected $runningCommand;
protected $name;
protected $version;
protected $catchExceptions;
protected $autoExit;
protected $definition;
protected $helperSet;
/**
* Constructor.
*
* @param string $name The name of the application
* @param string $version The version of the application
*/
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->catchExceptions = true;
$this->autoExit = true;
$this->commands = array();
$this->aliases = array();
$this->helperSet = new HelperSet(array(
new FormatterHelper(),
new DialogHelper(),
));
$this->add(new HelpCommand());
$this->add(new ListCommand());
$this->definition = new InputDefinition(array(
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message.'),
new InputOption('--verbose', '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this program version.'),
new InputOption('--ansi', '-a', InputOption::VALUE_NONE, 'Force ANSI output.'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'),
));
}
/**
* Runs the current application.
*
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return integer 0 if everything went fine, or an error code
*
* @throws \Exception When doRun returns Exception
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output) {
$output = new ConsoleOutput();
}
try {
$statusCode = $this->doRun($input, $output);
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
}
$this->renderException($e, $output);
$statusCode = $e->getCode();
$statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
}
if ($this->autoExit) {
if ($statusCode > 255) {
$statusCode = 255;
}
// @codeCoverageIgnoreStart
exit($statusCode);
// @codeCoverageIgnoreEnd
} else {
return $statusCode;
}
}
/**
* Runs the current application.
*
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return integer 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(array('--ansi', '-a'))) {
$output->setDecorated(true);
}
if (true === $input->hasParameterOption(array('--help', '-h'))) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(array('command' => 'help'));
} else {
$this->wantHelps = true;
}
}
if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
$input->setInteractive(false);
}
if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
$output->setVerbosity(Output::VERBOSITY_QUIET);
} elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) {
$output->setVerbosity(Output::VERBOSITY_VERBOSE);
}
if (true === $input->hasParameterOption(array('--version', '-V'))) {
$output->writeln($this->getLongVersion());
return 0;
}
if (!$name) {
$name = 'list';
$input = new ArrayInput(array('command' => 'list'));
}
// the command name MUST be the first element of the input
$command = $this->find($name);
$this->runningCommand = $command;
$statusCode = $command->run($input, $output);
$this->runningCommand = null;
return is_numeric($statusCode) ? $statusCode : 0;
}
/**
* Set a helper set to be used with the command.
*
* @param HelperSet $helperSet The helper set
*/
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Get the helper set associated with the command
*
* @return HelperSet The HelperSet instance associated with this command
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition The InputDefinition instance
*/
public function getDefinition()
{
return $this->definition;
}
/**
* Gets the help message.
*
* @return string A help message.
*/
public function getHelp()
{
$messages = array(
$this->getLongVersion(),
'',
'<comment>Usage:</comment>',
sprintf(" [options] command [arguments]\n"),
'<comment>Options:</comment>',
);
foreach ($this->definition->getOptions() as $option) {
$messages[] = sprintf(' %-29s %s %s',
'<info>--'.$option->getName().'</info>',
$option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : ' ',
$option->getDescription()
);
}
return implode("\n", $messages);
}
/**
* Sets whether to catch exceptions or not during commands execution.
*
* @param Boolean $boolean Whether to catch exceptions or not during commands execution
*/
public function setCatchExceptions($boolean)
{
$this->catchExceptions = (Boolean) $boolean;
}
/**
* Sets whether to automatically exit after a command execution or not.
*
* @param Boolean $boolean Whether to automatically exit after a command execution or not
*/
public function setAutoExit($boolean)
{
$this->autoExit = (Boolean) $boolean;
}
/**
* Gets the name of the application.
*
* @return string The application name
*/
public function getName()
{
return $this->name;
}
/**
* Sets the application name.
*
* @param string $name The application name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Gets the application version.
*
* @return string The application version
*/
public function getVersion()
{
return $this->version;
}
/**
* Sets the application version.
*
* @param string $version The application version
*/
public function setVersion($version)
{
$this->version = $version;
}
/**
* Returns the long version of the application.
*
* @return string The long application version
*/
public function getLongVersion()
{
if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
} else {
return '<info>Console Tool</info>';
}
}
/**
* Registers a new command.
*
* @param string $name The command name
*
* @return Command The newly created command
*/
public function register($name)
{
return $this->add(new Command($name));
}
/**
* Adds an array of command objects.
*
* @param Command[] $commands An array of commands
*/
public function addCommands(array $commands)
{
foreach ($commands as $command) {
$this->add($command);
}
}
/**
* Adds a command object.
*
* If a command with the same name already exists, it will be overridden.
*
* @param Command $command A Command object
*
* @return Command The registered command
*/
public function add(Command $command)
{
$command->setApplication($this);
$this->commands[$command->getFullName()] = $command;
foreach ($command->getAliases() as $alias) {
$this->aliases[$alias] = $command;
}
return $command;
}
/**
* Returns a registered command by name or alias.
*
* @param string $name The command name or alias
*
* @return Command A Command object
*
* @throws \InvalidArgumentException When command name given does not exist
*/
public function get($name)
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
}
$command = isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
if ($this->wantHelps) {
$this->wantHelps = false;
$helpCommand = $this->get('help');
$helpCommand->setCommand($command);
return $helpCommand;
}
return $command;
}
/**
* Returns true if the command exists, false otherwise
*
* @param string $name The command name or alias
*
* @return Boolean true if the command exists, false otherwise
*/
public function has($name)
{
return isset($this->commands[$name]) || isset($this->aliases[$name]);
}
/**
* Returns an array of all unique namespaces used by currently registered commands.
*
* It does not returns the global namespace which always exists.
*
* @return array An array of namespaces
*/
public function getNamespaces()
{
$namespaces = array();
foreach ($this->commands as $command) {
if ($command->getNamespace()) {
$namespaces[$command->getNamespace()] = true;
}
}
return array_keys($namespaces);
}
/**
* Finds a registered namespace by a name or an abbreviation.
*
* @return string A registered namespace
*
* @throws \InvalidArgumentException When namespace is incorrect or ambiguous
*/
public function findNamespace($namespace)
{
$abbrevs = static::getAbbreviations($this->getNamespaces());
if (!isset($abbrevs[$namespace])) {
throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
}
if (count($abbrevs[$namespace]) > 1) {
throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace])));
}
return $abbrevs[$namespace][0];
}
/**
* Finds a command by name or alias.
*
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
* @param string $name A command name or a command alias
*
* @return Command A Command instance
*
* @throws \InvalidArgumentException When command name is incorrect or ambiguous
*/
public function find($name)
{
// namespace
$namespace = '';
if (false !== $pos = strrpos($name, ':')) {
$namespace = $this->findNamespace(substr($name, 0, $pos));
$name = substr($name, $pos + 1);
}
$fullName = $namespace ? $namespace.':'.$name : $name;
// name
$commands = array();
foreach ($this->commands as $command) {
if ($command->getNamespace() == $namespace) {
$commands[] = $command->getName();
}
}
$abbrevs = static::getAbbreviations($commands);
if (isset($abbrevs[$name]) && 1 == count($abbrevs[$name])) {
return $this->get($namespace ? $namespace.':'.$abbrevs[$name][0] : $abbrevs[$name][0]);
}
if (isset($abbrevs[$name]) && count($abbrevs[$name]) > 1) {
$suggestions = $this->getAbbreviationSuggestions(array_map(function ($command) use ($namespace) { return $namespace.':'.$command; }, $abbrevs[$name]));
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $suggestions));
}
// aliases
$abbrevs = static::getAbbreviations(array_keys($this->aliases));
if (!isset($abbrevs[$fullName])) {
throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $fullName));
}
if (count($abbrevs[$fullName]) > 1) {
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $this->getAbbreviationSuggestions($abbrevs[$fullName])));
}
return $this->get($abbrevs[$fullName][0]);
}
/**
* Gets the commands (registered in the given namespace if provided).
*
* The array keys are the full names and the values the command instances.
*
* @param string $namespace A namespace name
*
* @return array An array of Command instances
*/
public function all($namespace = null)
{
if (null === $namespace) {
return $this->commands;
}
$commands = array();
foreach ($this->commands as $name => $command) {
if ($namespace === $command->getNamespace()) {
$commands[$name] = $command;
}
}
return $commands;
}
/**
* Returns an array of possible abbreviations given a set of names.
*
* @param array $names An array of names
*
* @return array An array of abbreviations
*/
static public function getAbbreviations($names)
{
$abbrevs = array();
foreach ($names as $name) {
for ($len = strlen($name) - 1; $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
if (!isset($abbrevs[$abbrev])) {
$abbrevs[$abbrev] = array($name);
} else {
$abbrevs[$abbrev][] = $name;
}
}
}
// Non-abbreviations always get entered, even if they aren't unique
foreach ($names as $name) {
$abbrevs[$name] = array($name);
}
return $abbrevs;
}
/**
* Returns a text representation of the Application.
*
* @param string $namespace An optional namespace name
*
* @return string A string representing the Application
*/
public function asText($namespace = null)
{
$commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
$messages = array($this->getHelp(), '');
if ($namespace) {
$messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $namespace);
} else {
$messages[] = '<comment>Available commands:</comment>';
}
$width = 0;
foreach ($commands as $command) {
$width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
}
$width += 2;
// add commands by namespace
foreach ($this->sortCommands($commands) as $space => $commands) {
if (!$namespace && '_global' !== $space) {
$messages[] = '<comment>'.$space.'</comment>';
}
foreach ($commands as $command) {
$aliases = $command->getAliases() ? '<comment> ('.implode(', ', $command->getAliases()).')</comment>' : '';
$messages[] = sprintf(" <info>%-${width}s</info> %s%s", ($command->getNamespace() ? ':' : '').$command->getName(), $command->getDescription(), $aliases);
}
}
return implode("\n", $messages);
}
/**
* Returns an XML representation of the Application.
*
* @param string $namespace An optional namespace name
* @param Boolean $asDom Whether to return a DOM or an XML string
*
* @return string|DOMDocument An XML string representing the Application
*/
public function asXml($namespace = null, $asDom = false)
{
$commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($xml = $dom->createElement('symfony'));
$xml->appendChild($commandsXML = $dom->createElement('commands'));
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
} else {
$xml->appendChild($namespacesXML = $dom->createElement('namespaces'));
}
// add commands by namespace
foreach ($this->sortCommands($commands) as $space => $commands) {
if (!$namespace) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $space);
}
foreach ($commands as $command) {
if (!$namespace) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($command->getName()));
}
$node = $command->asXml(true)->getElementsByTagName('command')->item(0);
$node = $dom->importNode($node, true);
$commandsXML->appendChild($node);
}
}
return $asDom ? $dom : $dom->saveXml();
}
/**
* Renders a catched exception.
*
* @param Exception $e An exception instance
* @param OutputInterface $output An OutputInterface instance
*/
public function renderException($e, $output)
{
$strlen = function ($string)
{
return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
};
$title = sprintf(' [%s] ', get_class($e));
$len = $strlen($title);
$lines = array();
foreach (explode("\n", $e->getMessage()) as $line) {
$lines[] = sprintf(' %s ', $line);
$len = max($strlen($line) + 4, $len);
}
$messages = array(str_repeat(' ', $len), $title.str_repeat(' ', $len - $strlen($title)));
foreach ($lines as $line) {
$messages[] = $line.str_repeat(' ', $len - $strlen($line));
}
$messages[] = str_repeat(' ', $len);
$output->writeln("\n");
foreach ($messages as $message) {
$output->writeln('<error>'.$message.'</error>');
}
$output->writeln("\n");
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
$output->writeln("\n");
}
if (Output::VERBOSITY_VERBOSE === $output->getVerbosity()) {
$output->writeln('</comment>Exception trace:</comment>');
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, array(
'function' => '',
'file' => $e->getFile() != null ? $e->getFile() : 'n/a',
'line' => $e->getLine() != null ? $e->getLine() : 'n/a',
'args' => array(),
));
for ($i = 0, $count = count($trace); $i < $count; $i++) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = $trace[$i]['function'];
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
$output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
}
$output->writeln("\n");
}
}
protected function getCommandName(InputInterface $input)
{
return $input->getFirstArgument('command');
}
protected function sortCommands($commands)
{
$namespacedCommands = array();
foreach ($commands as $name => $command) {
$key = $command->getNamespace() ? $command->getNamespace() : '_global';
if (!isset($namespacedCommands[$key])) {
$namespacedCommands[$key] = array();
}
$namespacedCommands[$key][$name] = $command;
}
ksort($namespacedCommands);
foreach ($namespacedCommands as $name => &$commands) {
ksort($commands);
}
return $namespacedCommands;
}
protected function getAbbreviationSuggestions($abbrevs)
{
return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
}
}

View File

@@ -1,512 +0,0 @@
<?php
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Base class for all commands.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Command
{
protected $name;
protected $namespace;
protected $aliases;
protected $definition;
protected $help;
protected $application;
protected $description;
protected $ignoreValidationErrors;
protected $applicationDefinitionMerged;
protected $code;
/**
* Constructor.
*
* @param string $name The name of the command
*
* @throws \LogicException When the command name is empty
*/
public function __construct($name = null)
{
$this->definition = new InputDefinition();
$this->ignoreValidationErrors = false;
$this->applicationDefinitionMerged = false;
$this->aliases = array();
if (null !== $name) {
$this->setName($name);
}
$this->configure();
if (!$this->name) {
throw new \LogicException('The command name cannot be empty.');
}
}
/**
* Sets the application instance for this command.
*
* @param Application $application An Application instance
*/
public function setApplication(Application $application = null)
{
$this->application = $application;
}
/**
* Configures the current command.
*/
protected function configure()
{
}
/**
* Executes the current command.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*
* @return integer 0 if everything went fine, or an error code
*
* @throws \LogicException When this abstract class is not implemented
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new \LogicException('You must override the execute() method in the concrete command class.');
}
/**
* Interacts with the user.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}
/**
* Initializes the command just after the input has been validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
/**
* Runs the command.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
public function run(InputInterface $input, OutputInterface $output)
{
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try {
$input->bind($this->definition);
} catch (\Exception $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if ($input->isInteractive()) {
$this->interact($input, $output);
}
$input->validate();
if ($this->code) {
return call_user_func($this->code, $input, $output);
} else {
return $this->execute($input, $output);
}
}
/**
* Sets the code to execute when running this command.
*
* @param \Closure $code A \Closure
*
* @return Command The current instance
*/
public function setCode(\Closure $code)
{
$this->code = $code;
return $this;
}
/**
* Merges the application definition with the command definition.
*/
protected function mergeApplicationDefinition()
{
if (null === $this->application || true === $this->applicationDefinitionMerged) {
return;
}
$this->definition->setArguments(array_merge(
$this->application->getDefinition()->getArguments(),
$this->definition->getArguments()
));
$this->definition->addOptions($this->application->getDefinition()->getOptions());
$this->applicationDefinitionMerged = true;
}
/**
* Sets an array of argument and option instances.
*
* @param array|Definition $definition An array of argument and option instances or a definition instance
*
* @return Command The current instance
*/
public function setDefinition($definition)
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->applicationDefinitionMerged = false;
return $this;
}
/**
* Gets the InputDefinition attached to this Command.
*
* @return InputDefinition An InputDefinition instance
*/
public function getDefinition()
{
return $this->definition;
}
/**
* Adds an argument.
*
* @param string $name The argument name
* @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
*
* @return Command The current instance
*/
public function addArgument($name, $mode = null, $description = '', $default = null)
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
return $this;
}
/**
* Adds an option.
*
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @param integer $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or self::VALUE_NONE)
*
* @return Command The current instance
*/
public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @param string $name The command name
*
* @return Command The current instance
*
* @throws \InvalidArgumentException When command name given is empty
*/
public function setName($name)
{
if (false !== $pos = strrpos($name, ':')) {
$namespace = substr($name, 0, $pos);
$name = substr($name, $pos + 1);
} else {
$namespace = $this->namespace;
}
if (!$name) {
throw new \InvalidArgumentException('A command name cannot be empty.');
}
$this->namespace = $namespace;
$this->name = $name;
return $this;
}
/**
* Returns the command namespace.
*
* @return string The command namespace
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Returns the command name
*
* @return string The command name
*/
public function getName()
{
return $this->name;
}
/**
* Returns the fully qualified command name.
*
* @return string The fully qualified command name
*/
public function getFullName()
{
return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName();
}
/**
* Sets the description for the command.
*
* @param string $description The description for the command
*
* @return Command The current instance
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @param string $help The help for the command
*
* @return Command The current instance
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*
* @return string The help for the command
*/
public function getHelp()
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
$name = $this->namespace.':'.$this->name;
$placeholders = array(
'%command.name%',
'%command.full_name%'
);
$replacements = array(
$name,
$_SERVER['PHP_SELF'].' '.$name
);
return str_replace($placeholders, $replacements, $this->getHelp());
}
/**
* Sets the aliases for the command.
*
* @param array $aliases An array of aliases for the command
*
* @return Command The current instance
*/
public function setAliases($aliases)
{
$this->aliases = $aliases;
return $this;
}
/**
* Returns the aliases for the command.
*
* @return array An array of aliases for the command
*/
public function getAliases()
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @return string The synopsis
*/
public function getSynopsis()
{
return sprintf('%s %s', $this->getFullName(), $this->definition->getSynopsis());
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws \InvalidArgumentException if the helper is not defined
*/
protected function getHelper($name)
{
return $this->application->getHelperSet()->get($name);
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws \InvalidArgumentException if the helper is not defined
*/
public function __get($name)
{
return $this->application->getHelperSet()->get($name);
}
/**
* Returns a text representation of the command.
*
* @return string A string representing the command
*/
public function asText()
{
$messages = array(
'<comment>Usage:</comment>',
' '.$this->getSynopsis(),
'',
);
if ($this->getAliases()) {
$messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $this->getAliases()).'</info>';
}
$messages[] = $this->definition->asText();
if ($help = $this->getProcessedHelp()) {
$messages[] = '<comment>Help:</comment>';
$messages[] = ' '.implode("\n ", explode("\n", $help))."\n";
}
return implode("\n", $messages);
}
/**
* Returns an XML representation of the command.
*
* @param Boolean $asDom Whether to return a DOM or an XML string
*
* @return string|DOMDocument An XML string representing the command
*/
public function asXml($asDom = false)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($commandXML = $dom->createElement('command'));
$commandXML->setAttribute('id', $this->getFullName());
$commandXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global');
$commandXML->setAttribute('name', $this->getName());
$commandXML->appendChild($usageXML = $dom->createElement('usage'));
$usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), '')));
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getDescription()))));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$help = $this->help;
$helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
$commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
foreach ($this->getAliases() as $alias) {
$aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
$aliasXML->appendChild($dom->createTextNode($alias));
}
$definition = $this->definition->asXml(true);
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('arguments')->item(0), true));
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('options')->item(0), true));
return $asDom ? $dom : $dom->saveXml();
}
}

View File

@@ -1,77 +0,0 @@
<?php
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Command\Command;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* HelpCommand displays the help for a given command.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class HelpCommand extends Command
{
protected $command;
/**
* @see Command
*/
protected function configure()
{
$this->ignoreValidationErrors = true;
$this
->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
))
->setName('help')
->setAliases(array('?'))
->setDescription('Displays help for a command')
->setHelp(<<<EOF
The <info>help</info> command displays help for a given command:
<info>./symfony help list</info>
You can also output the help as XML by using the <comment>--xml</comment> option:
<info>./symfony help --xml list</info>
EOF
);
}
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command) {
$this->command = $this->application->get($input->getArgument('command_name'));
}
if ($input->getOption('xml')) {
$output->writeln($this->command->asXml(), Output::OUTPUT_RAW);
} else {
$output->writeln($this->command->asText());
}
}
}

View File

@@ -1,67 +0,0 @@
<?php
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Command\Command;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* ListCommand displays the list of all available commands for the application.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class ListCommand extends Command
{
/**
* @see Command
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
))
->setName('list')
->setDescription('Lists commands')
->setHelp(<<<EOF
The <info>list</info> command lists all commands:
<info>./symfony list</info>
You can also display the commands for a specific namespace:
<info>./symfony list test</info>
You can also output the information as XML by using the <comment>--xml</comment> option:
<info>./symfony list --xml</info>
EOF
);
}
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('xml')) {
$output->writeln($this->application->asXml($input->getArgument('namespace')), Output::OUTPUT_RAW);
} else {
$output->writeln($this->application->asText($input->getArgument('namespace')));
}
}
}

View File

@@ -1,110 +0,0 @@
<?php
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\OutputInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* The Dialog class provides helpers to interact with the user.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class DialogHelper extends Helper
{
/**
* Asks a question to the user.
*
* @param OutputInterface $output
* @param string|array $question The question to ask
* @param string $default The default answer if none is given by the user
*
* @return string The user answer
*/
public function ask(OutputInterface $output, $question, $default = null)
{
// @codeCoverageIgnoreStart
$output->writeln($question);
$ret = trim(fgets(STDIN));
return $ret ? $ret : $default;
// @codeCoverageIgnoreEnd
}
/**
* Asks a confirmation to the user.
*
* The question will be asked until the user answer by nothing, yes, or no.
*
* @param OutputInterface $output
* @param string|array $question The question to ask
* @param Boolean $default The default answer if the user enters nothing
*
* @return Boolean true if the user has confirmed, false otherwise
*/
public function askConfirmation(OutputInterface $output, $question, $default = true)
{
// @codeCoverageIgnoreStart
$answer = 'z';
while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
$answer = $this->ask($output, $question);
}
if (false === $default) {
return $answer && 'y' == strtolower($answer[0]);
} else {
return !$answer || 'y' == strtolower($answer[0]);
}
// @codeCoverageIgnoreEnd
}
/**
* Asks for a value and validates the response.
*
* @param OutputInterface $output
* @param string|array $question
* @param Closure $validator
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
*
* @return mixed
*
* @throws \Exception When any of the validator returns an error
*/
public function askAndValidate(OutputInterface $output, $question, \Closure $validator, $attempts = false)
{
// @codeCoverageIgnoreStart
$error = null;
while (false === $attempts || $attempts--) {
if (null !== $error) {
$output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
}
$value = $this->ask($output, $question, null);
try {
return $validator($value);
} catch (\Exception $error) {
}
}
throw $error;
// @codeCoverageIgnoreEnd
}
/**
* Returns the helper's canonical name
*/
public function getName()
{
return 'dialog';
}
}

View File

@@ -1,82 +0,0 @@
<?php
namespace Symfony\Component\Console\Helper;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* The Formatter class provides helpers to format messages.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class FormatterHelper extends Helper
{
/**
* Formats a message within a section.
*
* @param string $section The section name
* @param string $message The message
* @param string $style The style to apply to the section
*/
public function formatSection($section, $message, $style = 'info')
{
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
* @param string $style The style to apply to the whole block
* @param Boolean $large Whether to return a large block
*
* @return string The formatter message
*/
public function formatBlock($messages, $style, $large = false)
{
if (!is_array($messages)) {
$messages = array($messages);
}
$len = 0;
$lines = array();
foreach ($messages as $message) {
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? array(str_repeat(' ', $len)) : array();
foreach ($lines as $line) {
$messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
}
foreach ($messages as &$message) {
$message = sprintf('<%s>%s</%s>', $style, $message, $style);
}
return implode("\n", $messages);
}
protected function strlen($string)
{
return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
}
/**
* Returns the helper's canonical name
*/
public function getName()
{
return 'formatter';
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace Symfony\Component\Console\Helper;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Helper is the base class for all helper classes.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
abstract class Helper implements HelperInterface
{
protected $helperSet = null;
/**
* Sets the helper set associated with this helper.
*
* @param HelperSet $helperSet A HelperSet instance
*/
public function setHelperSet(HelperSet $helperSet = null)
{
$this->helperSet = $helperSet;
}
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet A HelperSet instance
*/
public function getHelperSet()
{
return $this->helperSet;
}
}

View File

@@ -1,41 +0,0 @@
<?php
namespace Symfony\Component\Console\Helper;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* HelperInterface is the interface all helpers must implement.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface HelperInterface
{
/**
* Sets the helper set associated with this helper.
*
* @param HelperSet $helperSet A HelperSet instance
*/
function setHelperSet(HelperSet $helperSet = null);
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet A HelperSet instance
*/
function getHelperSet();
/**
* Returns the canonical name of this helper.
*
* @return string The canonical name
*/
function getName();
}

View File

@@ -1,102 +0,0 @@
<?php
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* HelperSet represents a set of helpers to be used with a command.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class HelperSet
{
protected $helpers;
protected $command;
/**
* @param Helper[] $helpers An array of helper.
*/
public function __construct(array $helpers = array())
{
$this->helpers = array();
foreach ($helpers as $alias => $helper) {
$this->set($helper, is_int($alias) ? null : $alias);
}
}
/**
* Sets a helper.
*
* @param HelperInterface $value The helper instance
* @param string $alias An alias
*/
public function set(HelperInterface $helper, $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias) {
$this->helpers[$alias] = $helper;
}
$helper->setHelperSet($this);
}
/**
* Returns true if the helper if defined.
*
* @param string $name The helper name
*
* @return Boolean true if the helper is defined, false otherwise
*/
public function has($name)
{
return isset($this->helpers[$name]);
}
/**
* Gets a helper value.
*
* @param string $name The helper name
*
* @return HelperInterface The helper instance
*
* @throws \InvalidArgumentException if the helper is not defined
*/
public function get($name)
{
if (!$this->has($name)) {
throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
}
return $this->helpers[$name];
}
/**
* Sets the command associated with this helper set.
*
* @param Command $command A Command instance
*/
public function setCommand(Command $command = null)
{
$this->command = $command;
}
/**
* Gets the command associated with this helper set.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}
}

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