Starting using the new files I've already worked on

Esse commit está contido em:
Felipe
2010-10-29 00:06:40 -02:00
commit 6e9b435b3e
556 arquivos alterados com 90945 adições e 0 exclusões
Arquivo executável
+8
Ver Arquivo
@@ -0,0 +1,8 @@
Requires:
PHP 5.3
PHP-cli
In your console, run:
sudo php mind install
You're good to go
Arquivo executável
+24
Ver Arquivo
@@ -0,0 +1,24 @@
The MIT License
Copyright (c) 2010 TheWebMind.org
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Arquivo executável
+7
Ver Arquivo
@@ -0,0 +1,7 @@
See INSTALL.txt file to see how to install it.
After installation, for help, type:
$ mind -h
http://thewebmind.org
http://docs.thewebmind.org
contact@thewebmind.org
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 98 KiB

+55
Ver Arquivo
@@ -0,0 +1,55 @@
@user=users.
@project=projects.
@version=versions.
@object=objects.
@component=components.
@property=properties.
project has many users.
project has name:varchar(255, required).
project has info:text().
project has creator:int() // a user key.
project has dt_creation:date(-1, "Exec:now()").
user has many projects.
user has name:varchar(255).
user has login:varchar(40, required).
user has pwd:varchar(40, required).
user has status:char(1);
user has type:char(1, {A=Admin|N=Normal}).
project has many versions.
version has version:varchar(9, required).
version has tag:varchar(60).
version has obs:text(2048).
version has originalcode:text().
version has machine_lang:varchar(16) // the chosen language, like PHP, Java Python.
version has framework:varchar(60) // the chosen framework to generate the code.
version has database:varchar(16) // the chosen DBMS to deal with .
version has a user.
version has many objects.
objects have type:char(1, {T=Table|C=Class|I=Interface}).
objects have name:varchar(256).
every object has version:int(-1).
object has locked:int(-1, "Exec:0") // use 0 or 1.
object has info:varchar(2048).
each object may have many components.
components have type:char(1, {P=Propertie|M=Method|A=Annotation}).
each component have properties.
All the properties have name:varchar(255, required).
And the properties have value:text().
Also, the properties have comment:varchar(255).
properties may have many properties.
+110
Ver Arquivo
@@ -0,0 +1,110 @@
/*######################################################
# Generated by Mind 00:09 10/27/2010 #
# Generate PostgreSQL DataBase Commands #
######################################################*/
/* DDL: table project */
CREATE TABLE project
(
pk_project integer unique not null,
name varchar(255) not null ,
info text ,
creator int4 ,
dt_creation timestamp,
PRIMARY KEY(pk_project)
);
/* DDL: table user */
CREATE TABLE user
(
pk_user integer unique not null,
name varchar(255) ,
login varchar(40) not null ,
pwd varchar(40) not null ,
status char(1) ,
type char(1) ,
PRIMARY KEY(pk_user)
);
/* DDL: table project_user */
CREATE TABLE project_user
(
pk_project_user integer unique not null,
fk_project integer ,
fk_user integer ,
PRIMARY KEY(pk_project_user),
FOREIGN KEY(fk_user) REFERENCES user(pk_user),
FOREIGN KEY(fk_project) REFERENCES project(pk_project)
);
/* DDL: table version */
CREATE TABLE version
(
pk_version integer unique not null,
version varchar(9) not null ,
tag varchar(60) ,
obs text ,
originalcode text ,
machine_lang varchar(16) ,
framework varchar(60) ,
database varchar(16) ,
fk_project integer ,
fk_user integer ,
PRIMARY KEY(pk_version),
FOREIGN KEY (fk_user) REFERENCES user(pk_user),
FOREIGN KEY (fk_project) REFERENCES project(pk_project)
);
/* DDL: table object */
CREATE TABLE object
(
pk_object integer unique not null,
type char(1) ,
name varchar(256) ,
version int4 ,
locked int4 default 0,
info varchar(2048) ,
fk_version integer ,
PRIMARY KEY(pk_object),
FOREIGN KEY (fk_version) REFERENCES version(pk_version)
);
/* DDL: table component */
CREATE TABLE component
(
pk_component integer unique not null,
type char(1) ,
fk_object integer ,
PRIMARY KEY(pk_component),
FOREIGN KEY (fk_object) REFERENCES object(pk_object)
);
/* DDL: table property */
CREATE TABLE property
(
pk_property integer unique not null,
name varchar(255) not null ,
value text ,
comment varchar(255) ,
fk_component integer ,
PRIMARY KEY(pk_property),
FOREIGN KEY (fk_component) REFERENCES component(pk_component)
);
/* DDL: table property_pointer */
CREATE TABLE property_pointer
(
pk_property_pointer integer unique not null,
main_property integer not null ,
fk_property integer ,
PRIMARY KEY(pk_property_pointer),
FOREIGN KEY (fk_property) REFERENCES property(pk_property)
);
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 11 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 12 KiB

+5
Ver Arquivo
@@ -0,0 +1,5 @@
Diagrama de sequência
http://www.websequencediagrams.com/
Diagrama ER
http://thewebmind.org/
+17
Ver Arquivo
@@ -0,0 +1,17 @@
<?php
$_REQ= Array();
$_REQ['request_method']= $_SERVER['REQUEST_METHOD'];
$_REQ['env']= 'http';
switch($_REQ['request_method'])
{
case 'GET' : $_REQ['data']= $_GET;
break;
case 'POST': $_REQ['data']= $_POST;
break;
case 'PUT' : parse_str(file_get_contents('php://input'), $put_vars);
$_REQ['data'] = $put_vars;
break;
default:
$_REQ['data']= null;
}
include('mind3rd/API/utils.php');
+54
Ver Arquivo
@@ -0,0 +1,54 @@
#!/usr/bin/env php
<?php
/**
* this file will be only accessed trhough the console
* if you pass the parameter install to it, it will
* create the SQLite database, start it, and try to
* create shortcuts, allowing the use of the comand
* mind, in your console
*/
/**
* @var _MIND
* @global mixed $_GLOBALS['_MIND'] This variable contains many information about the proect, the system and also have some methods an attributes to deal with such data
*/
$_MIND= Array();
$_MIND['env']= 'shell';
// checks if it has received any argument
if(sizeOf($_SERVER['argv'])>0)
{
// installation should create the required SQLite database and a shortcut command
if($_SERVER['argv'][1] == 'install')
{
if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')
{
echo ".";
echo shell_exec("sudo touch /bin/mind; echo .");
echo shell_exec("sudo echo '#!/usr/bin/env php' > /bin/mind; echo .");
echo shell_exec("sudo echo '<?php' >> /bin/mind; echo .");
echo shell_exec("sudo echo \"".'\$_REQ= Array(); \$_REQ[\'env\']= \'shell\';'.
" define('_MINDSRC_', '". getcwd()."'); ".
" require('".getcwd()."/mind3rd/API/utils.php');\" >> /bin/mind; echo .");
echo shell_exec("sudo chmod 777 /bin/mind; echo .");
}else{
// TODO: prepare it to other operating systems
}
if($db = new SQLiteDatabase('mind3rd/SQLite/mind'))
{
$DDL= file_get_contents('mind3rd/SQLite/ddl.sql');
if(!$db->queryExec($DDL))
{
die($err);
}
$db->queryExec("INSERT into user(name, login, pwd, status, type) VALUES ('Administrator', 'admin', '".sha1('admin')."', 'A', 'A');");
}else{
die($err);
}
echo "Done\n";
exit;
}
// if not installing, it should be redirected to mind3rd/API/shell.php
include('location:./mind3rd/API/shell.php');
}
+19
Ver Arquivo
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<http_no_such_file> [ERROR] No such file!
</http_no_such_file>
<shell_no_such_file> [ERROR] No such function!
Type help for help
</shell_no_such_file>
<autenticate_requiredFields> [ERROR] You must send a user and a password to autenticate
If you are using the console, you can send only the user. A password will be required soon
</autenticate_requiredFields>
<autenticate_requiredPwd> I'll need a password, please:
</autenticate_requiredPwd>
<auth_fail> Wrong user or password
</auth_fail>
<not_allowed> Your session is not valid. Please, execute
> autenticate $user
or send a user and a password to registrate
</not_allowed>
</root>
+26
Ver Arquivo
@@ -0,0 +1,26 @@
<?php
class Mind
{
public $about= null;
public $defaults= null;
public $conf= null;
public $l10n= null;
public function write($k, $echo=true)
{
//$msg= $this->l10n->xpath("/root/speach[@voice='".$k."']");
//echo "/root/".$k;
$msg= (string)$this->l10n->$k;
if($echo)
echo $msg;
return $msg;
}
public function Mind(){
$path= _MINDSRC_;
$this->about= parse_ini_file($path.'/mind3rd/env/about.ini');
$this->defaults= parse_ini_file($path.'/mind3rd/env/defaults.ini');
$this->conf= parse_ini_file($path.'/mind3rd/env/mind.ini');
$this->l10n= SimpleXML_load_file($path.'/mind3rd/API/L10N/'.$this->defaults['defaul_human_language'].'.xml');
}
}
+25
Ver Arquivo
@@ -0,0 +1,25 @@
<?php
class Mind
{
public $about= null;
public $defaults= null;
public $conf= null;
public $l10n= null;
public function write($k, $echo=true)
{
//$msg= $this->l10n->xpath("/root/speach[@voice='".$k."']");
//echo "/root/".$k;
$msg= (string)$this->l10n->$k;
if(!$echo)
echo $msg;
return $msg;
}
public function Mind(){
$this->about= parse_ini_file('mind3rd/env/about.ini');
$this->defaults= parse_ini_file('mind3rd/env/defaults.ini');
$this->conf= parse_ini_file('mind3rd/env/mind.ini');
$this->l10n= SimpleXML_load_file('mind3rd/API/L10N/'.$this->defaults['defaul_human_language'].'.xml');
}
}
+5
Ver Arquivo
@@ -0,0 +1,5 @@
K 25
svn:wc:ra_dav:version-url
V 41
/doctrine/doctrine2.git/!svn/ver/4932/lib
END
+34
Ver Arquivo
@@ -0,0 +1,34 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
Doctrine
dir
vendor
dir
+5
Ver Arquivo
@@ -0,0 +1,5 @@
K 25
svn:wc:ra_dav:version-url
V 50
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine
END
+31
Ver Arquivo
@@ -0,0 +1,31 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
ORM
dir
+119
Ver Arquivo
@@ -0,0 +1,119 @@
K 25
svn:wc:ra_dav:version-url
V 54
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM
END
AbstractQuery.php
K 25
svn:wc:ra_dav:version-url
V 72
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/AbstractQuery.php
END
NoResultException.php
K 25
svn:wc:ra_dav:version-url
V 76
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/NoResultException.php
END
NonUniqueResultException.php
K 25
svn:wc:ra_dav:version-url
V 83
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/NonUniqueResultException.php
END
Events.php
K 25
svn:wc:ra_dav:version-url
V 65
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Events.php
END
Query.php
K 25
svn:wc:ra_dav:version-url
V 64
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Query.php
END
TransactionRequiredException.php
K 25
svn:wc:ra_dav:version-url
V 87
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/TransactionRequiredException.php
END
EntityManager.php
K 25
svn:wc:ra_dav:version-url
V 72
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/EntityManager.php
END
Configuration.php
K 25
svn:wc:ra_dav:version-url
V 72
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Configuration.php
END
UnitOfWork.php
K 25
svn:wc:ra_dav:version-url
V 69
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/UnitOfWork.php
END
EntityNotFoundException.php
K 25
svn:wc:ra_dav:version-url
V 82
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/EntityNotFoundException.php
END
QueryBuilder.php
K 25
svn:wc:ra_dav:version-url
V 71
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/QueryBuilder.php
END
PersistentCollection.php
K 25
svn:wc:ra_dav:version-url
V 79
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/PersistentCollection.php
END
OptimisticLockException.php
K 25
svn:wc:ra_dav:version-url
V 82
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/OptimisticLockException.php
END
ORMException.php
K 25
svn:wc:ra_dav:version-url
V 71
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/ORMException.php
END
NativeQuery.php
K 25
svn:wc:ra_dav:version-url
V 70
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/NativeQuery.php
END
EntityRepository.php
K 25
svn:wc:ra_dav:version-url
V 75
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/EntityRepository.php
END
PessimisticLockException.php
K 25
svn:wc:ra_dav:version-url
V 83
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/PessimisticLockException.php
END
Version.php
K 25
svn:wc:ra_dav:version-url
V 66
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Version.php
END
README.markdown
K 25
svn:wc:ra_dav:version-url
V 70
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/README.markdown
END
+698
Ver Arquivo
@@ -0,0 +1,698 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine/ORM
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
AbstractQuery.php
file
2010-10-22T02:39:46.123322Z
1fd2c25dd82df8e7a527496c84dc20dd
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
17176
NoResultException.php
file
2010-10-22T02:39:46.123322Z
ae1222460f654b077ebe2039f5c81f83
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1325
Proxy
dir
Event
dir
NonUniqueResultException.php
file
2010-10-22T02:39:46.123322Z
d29625e50e4441489e330226edc85380
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1184
Events.php
file
2010-10-22T02:39:46.123322Z
3440a63347dcdc8909ee829f11a7bcf8
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
4115
Mapping
dir
Query.php
file
2010-10-22T02:39:46.123322Z
5fafd8c3faa47d92185d7a534ce34a66
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
16058
TransactionRequiredException.php
file
2010-10-22T02:39:46.173324Z
a8bd25855fcc0c41dfbc4a3fabc66539
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1596
Id
dir
EntityManager.php
file
2010-10-22T02:39:46.123322Z
3eaf04d788f1c90462721b695853d25f
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
22517
Configuration.php
file
2010-10-22T02:39:46.123322Z
42c1608574fa3ce633440c10d9af5dd1
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
14974
UnitOfWork.php
file
2010-10-22T02:39:46.173324Z
7264f6dfdc0250fddd6f486562c83e82
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
87147
EntityNotFoundException.php
file
2010-10-22T02:39:46.123322Z
8ba06972a4f209c006a34031e082ae2f
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1271
Tools
dir
NativeQuery.php
file
2010-10-22T02:39:46.123322Z
4465109f6a32d350e3e079dffcd7fb04
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2123
EntityRepository.php
file
2010-10-22T02:39:46.123322Z
b80e3588d7328bdd374ad0a2d5ced872
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
7149
ORMException.php
file
2010-10-22T02:39:46.123322Z
57e8ab7a70fd700fe9da092d6a3a50e3
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
3957
OptimisticLockException.php
file
2010-10-22T02:39:46.123322Z
621e0117b72550e8d934f19d065e039f
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2183
PersistentCollection.php
file
2010-10-22T02:39:46.123322Z
d3fdfdfe1cc5da080709b7f19bdc5d8f
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
18206
QueryBuilder.php
file
2010-10-22T02:39:46.123322Z
16d80984987db0f4adfded9171a0af6b
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
28527
Persisters
dir
Query
dir
PessimisticLockException.php
file
2010-10-22T02:39:46.123322Z
7b1a35b610010960c76235ce5f428e2d
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1495
Version.php
file
2010-10-22T02:39:46.173324Z
f7a488723c409a4d1f650223c72f45b5
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2034
Internal
dir
README.markdown
file
2010-10-22T02:39:46.173324Z
d41d8cd98f00b204e9800998ecf8427e
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
0
@@ -0,0 +1,590 @@
<?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
* 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;
use Doctrine\DBAL\Types\Type,
Doctrine\ORM\Query\QueryException;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
*/
abstract class AbstractQuery
{
/* Hydration mode constants */
/**
* Hydrates an object graph. This is the default behavior.
*/
const HYDRATE_OBJECT = 1;
/**
* Hydrates an array graph.
*/
const HYDRATE_ARRAY = 2;
/**
* Hydrates a flat, rectangular result set with scalar values.
*/
const HYDRATE_SCALAR = 3;
/**
* Hydrates a single scalar value.
*/
const HYDRATE_SINGLE_SCALAR = 4;
/**
* @var array The parameter map of this query.
*/
protected $_params = array();
/**
* @var array The parameter type map of this query.
*/
protected $_paramTypes = array();
/**
* @var ResultSetMapping The user-specified ResultSetMapping to use.
*/
protected $_resultSetMapping;
/**
* @var Doctrine\ORM\EntityManager The entity manager used by this query object.
*/
protected $_em;
/**
* @var array The map of query hints.
*/
protected $_hints = array();
/**
* @var integer The hydration mode.
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* The locally set cache driver used for caching result sets of this query.
*
* @var CacheDriver
*/
protected $_resultCacheDriver;
/**
* Boolean flag for whether or not to cache the results of this query.
*
* @var boolean
*/
protected $_useResultCache;
/**
* @var string The id to store the result cache entry under.
*/
protected $_resultCacheId;
/**
* @var boolean Boolean value that indicates whether or not expire the result cache.
*/
protected $_expireResultCache = false;
/**
* @var int Result Cache lifetime.
*/
protected $_resultCacheTTL;
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
* @param Doctrine\ORM\EntityManager $entityManager
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
}
/**
* Retrieves the associated EntityManager of this Query instance.
*
* @return Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/**
* Frees the resources used by the query object.
*
* Resets Parameters, Parameter Types and Query Hints.
*
* @return void
*/
public function free()
{
$this->_params = array();
$this->_paramTypes = array();
$this->_hints = array();
}
/**
* Get all defined parameters.
*
* @return array The defined query parameters.
*/
public function getParameters()
{
return $this->_params;
}
/**
* Gets a query parameter.
*
* @param mixed $key The key (index or name) of the bound parameter.
* @return mixed The value of the bound parameter.
*/
public function getParameter($key)
{
return isset($this->_params[$key]) ? $this->_params[$key] : null;
}
/**
* Gets the SQL query that corresponds to this query object.
* The returned SQL syntax depends on the connection driver that is used
* by this query object at the time of this method call.
*
* @return string SQL query
*/
abstract public function getSQL();
/**
* Sets a query parameter.
*
* @param string|integer $key The parameter position or name.
* @param mixed $value The parameter value.
* @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.
*/
public function setParameter($key, $value, $type = null)
{
if ($type !== null) {
$this->_paramTypes[$key] = $type;
}
$this->_params[$key] = $value;
return $this;
}
/**
* Sets a collection of query parameters.
*
* @param array $params
* @param array $types
* @return Doctrine\ORM\AbstractQuery This query instance.
*/
public function setParameters(array $params, array $types = array())
{
foreach ($params as $key => $value) {
if (isset($types[$key])) {
$this->setParameter($key, $value, $types[$key]);
} else {
$this->setParameter($key, $value);
}
}
return $this;
}
/**
* Sets the ResultSetMapping that should be used for hydration.
*
* @param ResultSetMapping $rsm
* @return Doctrine\ORM\AbstractQuery
*/
public function setResultSetMapping(Query\ResultSetMapping $rsm)
{
$this->_resultSetMapping = $rsm;
return $this;
}
/**
* Defines a cache driver to be used for caching result sets.
*
* @param Doctrine\Common\Cache\Cache $driver Cache driver
* @return Doctrine\ORM\AbstractQuery
*/
public function setResultCacheDriver($resultCacheDriver = null)
{
if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
throw ORMException::invalidResultCacheDriver();
}
$this->_resultCacheDriver = $resultCacheDriver;
if ($resultCacheDriver) {
$this->_useResultCache = true;
}
return $this;
}
/**
* Returns the cache driver used for caching result sets.
*
* @return Doctrine\Common\Cache\Cache Cache driver
*/
public function getResultCacheDriver()
{
if ($this->_resultCacheDriver) {
return $this->_resultCacheDriver;
} else {
return $this->_em->getConfiguration()->getResultCacheImpl();
}
}
/**
* Set whether or not to cache the results of this query and if so, for
* how long and which ID to use for the cache entry.
*
* @param boolean $bool
* @param integer $timeToLive
* @param string $resultCacheId
* @return This query instance.
*/
public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
{
$this->_useResultCache = $bool;
if ($timeToLive) {
$this->setResultCacheLifetime($timeToLive);
}
if ($resultCacheId) {
$this->_resultCacheId = $resultCacheId;
}
return $this;
}
/**
* 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.
*/
public function setResultCacheLifetime($timeToLive)
{
if ($timeToLive !== null) {
$timeToLive = (int) $timeToLive;
}
$this->_resultCacheTTL = $timeToLive;
return $this;
}
/**
* Retrieves the lifetime of resultset cache.
*
* @return integer
*/
public function getResultCacheLifetime()
{
return $this->_resultCacheTTL;
}
/**
* 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.
*/
public function expireResultCache($expire = true)
{
$this->_expireResultCache = $expire;
return $this;
}
/**
* Retrieves if the resultset cache is active or not.
*
* @return boolean
*/
public function getExpireResultCache()
{
return $this->_expireResultCache;
}
/**
* 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.
*/
public function setHydrationMode($hydrationMode)
{
$this->_hydrationMode = $hydrationMode;
return $this;
}
/**
* Gets the hydration mode currently used by the query.
*
* @return integer
*/
public function getHydrationMode()
{
return $this->_hydrationMode;
}
/**
* Gets the list of results for the query.
*
* Alias for execute(array(), $hydrationMode = HYDRATE_OBJECT).
*
* @return array
*/
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
{
return $this->execute(array(), $hydrationMode);
}
/**
* Gets the array of results for the query.
*
* Alias for execute(array(), HYDRATE_ARRAY).
*
* @return array
*/
public function getArrayResult()
{
return $this->execute(array(), self::HYDRATE_ARRAY);
}
/**
* Gets the scalar results for the query.
*
* Alias for execute(array(), HYDRATE_SCALAR).
*
* @return array
*/
public function getScalarResult()
{
return $this->execute(array(), self::HYDRATE_SCALAR);
}
/**
* Gets the single result of the query.
*
* Enforces the presence as well as the uniqueness of the result.
*
* If the result is not unique, a NonUniqueResultException is thrown.
* If there is no result, a NoResultException is thrown.
*
* @param integer $hydrationMode
* @return mixed
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult($hydrationMode = null)
{
$result = $this->execute(array(), $hydrationMode);
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
throw new NoResultException;
}
if (is_array($result)) {
if (count($result) > 1) {
throw new NonUniqueResultException;
}
return array_shift($result);
}
return $result;
}
/**
* Gets the single scalar result of the query.
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed
* @throws QueryException If the query result is not unique.
*/
public function getSingleScalarResult()
{
return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
}
/**
* Sets a query hint. If the hint name is not recognized, it is silently ignored.
*
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
* @return Doctrine\ORM\AbstractQuery
*/
public function setHint($name, $value)
{
$this->_hints[$name] = $value;
return $this;
}
/**
* Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
*
* @param string $name The name of the hint.
* @return mixed The value of the hint or FALSE, if the hint name is not recognized.
*/
public function getHint($name)
{
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
}
/**
* Executes the query and returns an IterableResult that can be used to incrementally
* iterate over the result.
*
* @param array $params The query parameters.
* @param integer $hydrationMode The hydration mode to use.
* @return IterableResult
*/
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
{
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
$this->_doExecute($params, $hydrationMode), $this->_resultSetMapping, $this->_hints
);
}
/**
* Executes the query.
*
* @param string $params Any additional query parameters.
* @param integer $hydrationMode Processing mode to be used during the hydration process.
* @return mixed
*/
public function execute($params = array(), $hydrationMode = null)
{
// If there are still pending insertions in the UnitOfWork we need to flush
// in order to guarantee a correct result.
//TODO: Think this over. Its tricky. Not doing this can lead to strange results
// potentially, but doing it could result in endless loops when querying during
// a flush, i.e. inside an event listener.
if ($this->_em->getUnitOfWork()->hasPendingInsertions()) {
$this->_em->flush();
}
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
}
if ($params) {
$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);
if ($cached === false) {
// Cache miss.
$stmt = $this->_doExecute();
$result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
$cacheDriver->save($id, $result, $this->_resultCacheTTL);
return $result;
} else {
// Cache hit.
return $cached;
}
}
$stmt = $this->_doExecute();
if (is_numeric($stmt)) {
return $stmt;
}
return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
}
/**
* Set the result cache id to use to store the result set cache entry.
* If this is not explicitely set by the developer then a hash is automatically
* generated for you.
*
* @param string $id
* @return Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheId($id)
{
$this->_resultCacheId = $id;
return $this;
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
* automatically generated for you.
*
* @return string $id
*/
protected function _getResultCacheId()
{
if ($this->_resultCacheId) {
return $this->_resultCacheId;
} else {
$sql = $this->getSql();
ksort($this->_hints);
return md5(implode(";", (array)$sql) . var_export($this->_params, true) .
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode);
}
}
/**
* Executes the query and returns a the resulting Statement object.
*
* @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
*/
abstract protected function _doExecute();
/**
* Cleanup Query resource when clone is called.
*
* @return void
*/
public function __clone()
{
$this->_params = array();
$this->_paramTypes = array();
$this->_hints = array();
}
}
@@ -0,0 +1,465 @@
<?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;
use Doctrine\Common\Cache\Cache,
Doctrine\ORM\Mapping\Driver\Driver;
/**
* Configuration container for all configuration options of Doctrine.
* It combines all configuration options from DBAL & ORM.
*
* @since 2.0
* @internal When adding a new configuration option just write a getter/setter pair.
* @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 Configuration extends \Doctrine\DBAL\Configuration
{
/**
* Sets the directory where Doctrine generates any necessary proxy class files.
*
* @param string $dir
*/
public function setProxyDir($dir)
{
$this->_attributes['proxyDir'] = $dir;
}
/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*
* @return string
*/
public function getProxyDir()
{
return isset($this->_attributes['proxyDir']) ?
$this->_attributes['proxyDir'] : null;
}
/**
* Gets a boolean flag that indicates whether proxy classes should always be regenerated
* during each script execution.
*
* @return boolean
*/
public function getAutoGenerateProxyClasses()
{
return isset($this->_attributes['autoGenerateProxyClasses']) ?
$this->_attributes['autoGenerateProxyClasses'] : true;
}
/**
* Sets a boolean flag that indicates whether proxy classes should always be regenerated
* during each script execution.
*
* @param boolean $bool
*/
public function setAutoGenerateProxyClasses($bool)
{
$this->_attributes['autoGenerateProxyClasses'] = $bool;
}
/**
* Gets the namespace where proxy classes reside.
*
* @return string
*/
public function getProxyNamespace()
{
return isset($this->_attributes['proxyNamespace']) ?
$this->_attributes['proxyNamespace'] : null;
}
/**
* Sets the namespace where proxy classes reside.
*
* @param string $ns
*/
public function setProxyNamespace($ns)
{
$this->_attributes['proxyNamespace'] = $ns;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
* @param Driver $driverImpl
* @todo Force parameter to be a Closure to ensure lazy evaluation
* (as soon as a metadata cache is in effect, the driver never needs to initialize).
*/
public function setMetadataDriverImpl(Driver $driverImpl)
{
$this->_attributes['metadataDriverImpl'] = $driverImpl;
}
/**
* 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);
}
/**
* Adds a namespace under a certain alias.
*
* @param string $alias
* @param string $namespace
*/
public function addEntityNamespace($alias, $namespace)
{
$this->_attributes['entityNamespaces'][$alias] = $namespace;
}
/**
* Resolves a registered namespace alias to the full namespace.
*
* @param string $entityNamespaceAlias
* @return string
* @throws MappingException
*/
public function getEntityNamespace($entityNamespaceAlias)
{
if ( ! isset($this->_attributes['entityNamespaces'][$entityNamespaceAlias])) {
throw ORMException::unknownEntityNamespace($entityNamespaceAlias);
}
return trim($this->_attributes['entityNamespaces'][$entityNamespaceAlias], '\\');
}
/**
* Set the entity alias map
*
* @param array $entityAliasMap
* @return void
*/
public function setEntityNamespaces(array $entityNamespaces)
{
$this->_attributes['entityNamespaces'] = $entityNamespaces;
}
/**
* Gets the cache driver implementation that is used for the mapping metadata.
*
* @throws ORMException
* @return Mapping\Driver\Driver
*/
public function getMetadataDriverImpl()
{
return isset($this->_attributes['metadataDriverImpl']) ?
$this->_attributes['metadataDriverImpl'] : null;
}
/**
* Gets the cache driver implementation that is used for query result caching.
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getResultCacheImpl()
{
return isset($this->_attributes['resultCacheImpl']) ?
$this->_attributes['resultCacheImpl'] : null;
}
/**
* Sets the cache driver implementation that is used for query result caching.
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setResultCacheImpl(Cache $cacheImpl)
{
$this->_attributes['resultCacheImpl'] = $cacheImpl;
}
/**
* Gets the cache driver implementation that is used for the query cache (SQL cache).
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getQueryCacheImpl()
{
return isset($this->_attributes['queryCacheImpl']) ?
$this->_attributes['queryCacheImpl'] : null;
}
/**
* Sets the cache driver implementation that is used for the query cache (SQL cache).
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setQueryCacheImpl(Cache $cacheImpl)
{
$this->_attributes['queryCacheImpl'] = $cacheImpl;
}
/**
* Gets the cache driver implementation that is used for metadata caching.
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getMetadataCacheImpl()
{
return isset($this->_attributes['metadataCacheImpl']) ?
$this->_attributes['metadataCacheImpl'] : null;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setMetadataCacheImpl(Cache $cacheImpl)
{
$this->_attributes['metadataCacheImpl'] = $cacheImpl;
}
/**
* Adds a named DQL query to the configuration.
*
* @param string $name The name of the query.
* @param string $dql The DQL query string.
*/
public function addNamedQuery($name, $dql)
{
$this->_attributes['namedQueries'][$name] = $dql;
}
/**
* Gets a previously registered named DQL query.
*
* @param string $name The name of the query.
* @return string The DQL query.
*/
public function getNamedQuery($name)
{
if ( ! isset($this->_attributes['namedQueries'][$name])) {
throw ORMException::namedQueryNotFound($name);
}
return $this->_attributes['namedQueries'][$name];
}
/**
* Adds a named native query to the configuration.
*
* @param string $name The name of the query.
* @param string $sql The native SQL query string.
* @param ResultSetMapping $rsm The ResultSetMapping used for the results of the SQL query.
*/
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
{
$this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm);
}
/**
* Gets the components of a previously registered named native query.
*
* @param string $name The name of the query.
* @return array A tuple with the first element being the SQL string and the second
* element being the ResultSetMapping.
*/
public function getNamedNativeQuery($name)
{
if ( ! isset($this->_attributes['namedNativeQueries'][$name])) {
throw ORMException::namedNativeQueryNotFound($name);
}
return $this->_attributes['namedNativeQueries'][$name];
}
/**
* Ensures that this Configuration instance contains settings that are
* suitable for a production environment.
*
* @throws ORMException If a configuration setting has a value that is not
* suitable for a production environment.
*/
public function ensureProductionSettings()
{
if ( !$this->getQueryCacheImpl()) {
throw ORMException::queryCacheNotConfigured();
}
if ( !$this->getMetadataCacheImpl()) {
throw ORMException::metadataCacheNotConfigured();
}
if ($this->getAutoGenerateProxyClasses()) {
throw ORMException::proxyClassesAlwaysRegenerating();
}
}
/**
* Registers a custom DQL function that produces a string value.
* Such a function can then be used in any DQL statement in any place where string
* functions are allowed.
*
* DQL function names are case-insensitive.
*
* @param string $name
* @param string $className
*/
public function addCustomStringFunction($name, $className)
{
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
}
/**
* Gets the implementation class name of a registered custom string DQL function.
*
* @param string $name
* @return string
*/
public function getCustomStringFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customStringFunctions'][$name]) ?
$this->_attributes['customStringFunctions'][$name] : null;
}
/**
* Sets a map of custom DQL string functions.
*
* Keys must be function names and values the FQCN of the implementing class.
* The function names will be case-insensitive in DQL.
*
* Any previously added string functions are discarded.
*
* @param array $functions The map of custom DQL string functions.
*/
public function setCustomStringFunctions(array $functions)
{
$this->_attributes['customStringFunctions'] = array_change_key_case($functions);
}
/**
* Registers a custom DQL function that produces a numeric value.
* Such a function can then be used in any DQL statement in any place where numeric
* functions are allowed.
*
* DQL function names are case-insensitive.
*
* @param string $name
* @param string $className
*/
public function addCustomNumericFunction($name, $className)
{
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
}
/**
* Gets the implementation class name of a registered custom numeric DQL function.
*
* @param string $name
* @return string
*/
public function getCustomNumericFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customNumericFunctions'][$name]) ?
$this->_attributes['customNumericFunctions'][$name] : null;
}
/**
* Sets a map of custom DQL numeric functions.
*
* Keys must be function names and values the FQCN of the implementing class.
* The function names will be case-insensitive in DQL.
*
* Any previously added numeric functions are discarded.
*
* @param array $functions The map of custom DQL numeric functions.
*/
public function setCustomNumericFunctions(array $functions)
{
$this->_attributes['customNumericFunctions'] = array_change_key_case($functions);
}
/**
* Registers a custom DQL function that produces a date/time value.
* Such a function can then be used in any DQL statement in any place where date/time
* functions are allowed.
*
* DQL function names are case-insensitive.
*
* @param string $name
* @param string $className
*/
public function addCustomDatetimeFunction($name, $className)
{
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
}
/**
* Gets the implementation class name of a registered custom date/time DQL function.
*
* @param string $name
* @return string
*/
public function getCustomDatetimeFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customDatetimeFunctions'][$name]) ?
$this->_attributes['customDatetimeFunctions'][$name] : null;
}
/**
* Sets a map of custom DQL date/time functions.
*
* Keys must be function names and values the FQCN of the implementing class.
* The function names will be case-insensitive in DQL.
*
* Any previously added date/time functions are discarded.
*
* @param array $functions The map of custom DQL date/time functions.
*/
public function setCustomDatetimeFunctions(array $functions)
{
$this->_attributes['customDatetimeFunctions'] = array_change_key_case($functions);
}
/**
* Get the hydrator class for the given hydration mode name.
*
* @param string $modeName The hydration mode name.
* @return string $hydrator The hydrator class name.
*/
public function getCustomHydrationMode($modeName)
{
return isset($this->_attributes['customHydrationModes'][$modeName]) ?
$this->_attributes['customHydrationModes'][$modeName] : null;
}
/**
* Add a custom hydration mode.
*
* @param string $modeName The hydration mode name.
* @param string $hydrator The hydrator class name.
*/
public function addCustomHydrationMode($modeName, $hydrator)
{
$this->_attributes['customHydrationModes'][$modeName] = $hydrator;
}
}
@@ -0,0 +1,715 @@
<?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;
use Closure, Exception,
Doctrine\Common\EventManager,
Doctrine\DBAL\Connection,
Doctrine\DBAL\LockMode,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\ClassMetadataFactory,
Doctrine\ORM\Query\ResultSetMapping,
Doctrine\ORM\Proxy\ProxyFactory;
/**
* The EntityManager is the central access point to ORM functionality.
*
* @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 EntityManager
{
/**
* The used Configuration.
*
* @var Doctrine\ORM\Configuration
*/
private $config;
/**
* The database connection used by the EntityManager.
*
* @var Doctrine\DBAL\Connection
*/
private $conn;
/**
* The metadata factory, used to retrieve the ORM metadata of entity classes.
*
* @var Doctrine\ORM\Mapping\ClassMetadataFactory
*/
private $metadataFactory;
/**
* The EntityRepository instances.
*
* @var array
*/
private $repositories = array();
/**
* The UnitOfWork used to coordinate object-level transactions.
*
* @var Doctrine\ORM\UnitOfWork
*/
private $unitOfWork;
/**
* The event manager that is the central point of the event system.
*
* @var Doctrine\Common\EventManager
*/
private $eventManager;
/**
* The maintained (cached) hydrators. One instance per type.
*
* @var array
*/
private $hydrators = array();
/**
* The proxy factory used to create dynamic proxies.
*
* @var Doctrine\ORM\Proxy\ProxyFactory
*/
private $proxyFactory;
/**
* @var ExpressionBuilder The expression builder instance used to generate query expressions.
*/
private $expressionBuilder;
/**
* Whether the EntityManager is closed or not.
*/
private $closed = false;
/**
* 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
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
{
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$this->metadataFactory = new ClassMetadataFactory($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this,
$config->getProxyDir(),
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses());
}
/**
* Gets the database connection object used by the EntityManager.
*
* @return Doctrine\DBAL\Connection
*/
public function getConnection()
{
return $this->conn;
}
/**
* Gets the metadata factory used to gather the metadata of classes.
*
* @return Doctrine\ORM\Mapping\ClassMetadataFactory
*/
public function getMetadataFactory()
{
return $this->metadataFactory;
}
/**
* Gets an ExpressionBuilder used for object-oriented construction of query expressions.
*
* Example:
*
* <code>
* $qb = $em->createQueryBuilder();
* $expr = $em->getExpressionBuilder();
* $qb->select('u')->from('User', 'u')
* ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
* </code>
*
* @return ExpressionBuilder
*/
public function getExpressionBuilder()
{
if ($this->expressionBuilder === null) {
$this->expressionBuilder = new Query\Expr;
}
return $this->expressionBuilder;
}
/**
* Starts a transaction on the underlying database connection.
*
* @deprecated Use {@link getConnection}.beginTransaction().
*/
public function beginTransaction()
{
$this->conn->beginTransaction();
}
/**
* Executes a function in a transaction.
*
* The function gets passed this EntityManager instance as an (optional) parameter.
*
* {@link flush} is invoked prior to transaction commit.
*
* If an exception occurs during execution of the function or flushing or transaction commit,
* the transaction is rolled back, the EntityManager closed and the exception re-thrown.
*
* @param Closure $func The function to execute transactionally.
*/
public function transactional(Closure $func)
{
$this->conn->beginTransaction();
try {
$func($this);
$this->flush();
$this->conn->commit();
} catch (Exception $e) {
$this->close();
$this->conn->rollback();
throw $e;
}
}
/**
* Commits a transaction on the underlying database connection.
*
* @deprecated Use {@link getConnection}.commit().
*/
public function commit()
{
$this->conn->commit();
}
/**
* Performs a rollback on the underlying database connection.
*
* @deprecated Use {@link getConnection}.rollback().
*/
public function rollback()
{
$this->conn->rollback();
}
/**
* Returns the ORM metadata descriptor for a class.
*
* 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
* @internal Performance-sensitive method.
*/
public function getClassMetadata($className)
{
return $this->metadataFactory->getMetadataFor($className);
}
/**
* Creates a new Query object.
*
* @param string The DQL string.
* @return Doctrine\ORM\Query
*/
public function createQuery($dql = "")
{
$query = new Query($this);
if ( ! empty($dql)) {
$query->setDql($dql);
}
return $query;
}
/**
* Creates a Query from a named query.
*
* @param string $name
* @return Doctrine\ORM\Query
*/
public function createNamedQuery($name)
{
return $this->createQuery($this->config->getNamedQuery($name));
}
/**
* Creates a native SQL query.
*
* @param string $sql
* @param ResultSetMapping $rsm The ResultSetMapping to use.
* @return NativeQuery
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
{
$query = new NativeQuery($this);
$query->setSql($sql);
$query->setResultSetMapping($rsm);
return $query;
}
/**
* Creates a NativeQuery from a named native query.
*
* @param string $name
* @return Doctrine\ORM\NativeQuery
*/
public function createNamedNativeQuery($name)
{
list($sql, $rsm) = $this->config->getNamedNativeQuery($name);
return $this->createNativeQuery($sql, $rsm);
}
/**
* Create a QueryBuilder instance
*
* @return QueryBuilder $qb
*/
public function createQueryBuilder()
{
return new QueryBuilder($this);
}
/**
* Flushes all changes to objects that have been queued up to now to the database.
* 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
* makes use of optimistic locking fails.
*/
public function flush()
{
$this->errorIfClosed();
$this->unitOfWork->commit();
}
/**
* Finds an Entity by its identifier.
*
* This is just a convenient shortcut for getRepository($entityName)->find($id).
*
* @param string $entityName
* @param mixed $identifier
* @param int $lockMode
* @param int $lockVersion
* @return object
*/
public function find($entityName, $identifier, $lockMode = LockMode::NONE, $lockVersion = null)
{
return $this->getRepository($entityName)->find($identifier, $lockMode, $lockVersion);
}
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
* @return object The entity reference.
*/
public function getReference($entityName, $identifier)
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
// Check identity map first, if its already in there just return it.
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity;
}
if ($class->subClasses) {
$entity = $this->find($entityName, $identifier);
} else {
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $this->proxyFactory->getProxy($class->name, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
}
return $entity;
}
/**
* Gets a partial reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* The returned reference may be a partial object if the entity is not yet loaded/managed.
* If it is a partial object it will not initialize the rest of the entity state on access.
* Thus you can only ever safely access the identifier of an entity obtained through
* this method.
*
* The use-cases for partial references involve maintaining bidirectional associations
* without loading one side of the association or to update an entity without loading it.
* Note, however, that in the latter case the original (persistent) entity data will
* never be visible to the application (especially not event listeners) as it will
* never be loaded in the first place.
*
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
* @return object The (partial) entity reference.
*/
public function getPartialReference($entityName, $identifier)
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
// Check identity map first, if its already in there just return it.
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity;
}
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
return $entity;
}
/**
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*
* @param string $entityName
*/
public function clear($entityName = null)
{
if ($entityName === null) {
$this->unitOfWork->clear();
} else {
//TODO
throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
}
}
/**
* Closes the EntityManager. All entities that are currently managed
* by this EntityManager become detached. The EntityManager may no longer
* be used after it is closed.
*/
public function close()
{
$this->clear();
$this->closed = true;
}
/**
* Tells the EntityManager to make an instance managed and persistent.
*
* 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.
*
* @param object $object The instance to make managed and persistent.
*/
public function persist($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
$this->unitOfWork->persist($entity);
}
/**
* Removes an entity instance.
*
* A removed entity will be removed from the database at or before transaction commit
* or as a result of the flush operation.
*
* @param object $entity The entity instance to remove.
*/
public function remove($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
$this->unitOfWork->remove($entity);
}
/**
* Refreshes the persistent state of an entity from the database,
* overriding any local changes that have not yet been persisted.
*
* @param object $entity The entity to refresh.
*/
public function refresh($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
$this->unitOfWork->refresh($entity);
}
/**
* Detaches an entity from the EntityManager, causing a managed entity to
* become detached. Unflushed changes made to the entity if any
* (including removal of the entity), will not be synchronized to the database.
* Entities which previously referenced the detached entity will continue to
* reference it.
*
* @param object $entity The entity to detach.
*/
public function detach($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->unitOfWork->detach($entity);
}
/**
* Merges the state of a detached entity into the persistence context
* of this EntityManager and returns the managed copy of the entity.
* The entity passed to merge will not become associated/managed with this EntityManager.
*
* @param object $entity The detached entity to merge into the persistence context.
* @return object The managed copy of the entity.
*/
public function merge($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
return $this->unitOfWork->merge($entity);
}
/**
* Creates a copy of the given entity. Can create a shallow or a deep copy.
*
* @param object $entity The entity to copy.
* @return object The new entity.
* @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e:
* Fatal error: Maximum function nesting level of '100' reached, aborting!
*/
public function copy($entity, $deep = false)
{
throw new \BadMethodCallException("Not implemented.");
}
/**
* Acquire a lock on the given entity.
*
* @param object $entity
* @param int $lockMode
* @param int $lockVersion
* @throws OptimisticLockException
* @throws PessimisticLockException
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
$this->unitOfWork->lock($entity, $lockMode, $lockVersion);
}
/**
* Gets the repository for an entity class.
*
* @param string $entityName The name of the entity.
* @return EntityRepository The repository class.
*/
public function getRepository($entityName)
{
$entityName = ltrim($entityName, '\\');
if (isset($this->repositories[$entityName])) {
return $this->repositories[$entityName];
}
$metadata = $this->getClassMetadata($entityName);
$customRepositoryClassName = $metadata->customRepositoryClassName;
if ($customRepositoryClassName !== null) {
$repository = new $customRepositoryClassName($this, $metadata);
} else {
$repository = new EntityRepository($this, $metadata);
}
$this->repositories[$entityName] = $repository;
return $repository;
}
/**
* Determines whether an entity instance is managed in this EntityManager.
*
* @param object $entity
* @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
*/
public function contains($entity)
{
return $this->unitOfWork->isScheduledForInsert($entity) ||
$this->unitOfWork->isInIdentityMap($entity) &&
! $this->unitOfWork->isScheduledForDelete($entity);
}
/**
* Gets the EventManager used by the EntityManager.
*
* @return Doctrine\Common\EventManager
*/
public function getEventManager()
{
return $this->eventManager;
}
/**
* Gets the Configuration used by the EntityManager.
*
* @return Doctrine\ORM\Configuration
*/
public function getConfiguration()
{
return $this->config;
}
/**
* Throws an exception if the EntityManager is closed or currently not active.
*
* @throws ORMException If the EntityManager is closed.
*/
private function errorIfClosed()
{
if ($this->closed) {
throw ORMException::entityManagerClosed();
}
}
/**
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*
* @return Doctrine\ORM\UnitOfWork
*/
public function getUnitOfWork()
{
return $this->unitOfWork;
}
/**
* Gets a hydrator for the given hydration mode.
*
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @param int $hydrationMode
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function getHydrator($hydrationMode)
{
if ( ! isset($this->hydrators[$hydrationMode])) {
$this->hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
}
return $this->hydrators[$hydrationMode];
}
/**
* Create a new instance for the given hydration mode.
*
* @param int $hydrationMode
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function newHydrator($hydrationMode)
{
switch ($hydrationMode) {
case Query::HYDRATE_OBJECT:
$hydrator = new Internal\Hydration\ObjectHydrator($this);
break;
case Query::HYDRATE_ARRAY:
$hydrator = new Internal\Hydration\ArrayHydrator($this);
break;
case Query::HYDRATE_SCALAR:
$hydrator = new Internal\Hydration\ScalarHydrator($this);
break;
case Query::HYDRATE_SINGLE_SCALAR:
$hydrator = new Internal\Hydration\SingleScalarHydrator($this);
break;
default:
if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
$hydrator = new $class($this);
break;
}
throw ORMException::invalidHydrationMode($hydrationMode);
}
return $hydrator;
}
/**
* Gets the proxy factory used by the EntityManager to create entity proxies.
*
* @return ProxyFactory
*/
public function getProxyFactory()
{
return $this->proxyFactory;
}
/**
* Factory method to create EntityManager instances.
*
* @param mixed $conn An array with the connection parameters or an existing
* Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
* @return EntityManager The created EntityManager.
*/
public static function create($conn, Configuration $config, EventManager $eventManager = null)
{
if (!$config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager()));
} else if ($conn instanceof Connection) {
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
} else {
throw new \InvalidArgumentException("Invalid argument: " . $conn);
}
return new EntityManager($conn, $config, $conn->getEventManager());
}
}
@@ -0,0 +1,34 @@
<?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;
/**
* Exception thrown when a Proxy fails to retrieve an Entity result.
*
* @author robo
* @since 2.0
*/
class EntityNotFoundException extends ORMException
{
public function __construct()
{
parent::__construct('Entity was not found.');
}
}
@@ -0,0 +1,225 @@
<?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;
use Doctrine\DBAL\LockMode;
/**
* An EntityRepository serves as a repository for entities with generic as well as
* business specific methods for retrieving entities.
*
* This class is designed for inheritance and users can subclass this class to
* write their own repositories with business-specific methods to locate entities.
*
* @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 EntityRepository
{
/**
* @var string
*/
protected $_entityName;
/**
* @var EntityManager
*/
protected $_em;
/**
* @var Doctrine\ORM\Mapping\ClassMetadata
*/
protected $_class;
/**
* Initializes a new <tt>EntityRepository</tt>.
*
* @param EntityManager $em The EntityManager to use.
* @param ClassMetadata $classMetadata The class descriptor.
*/
public function __construct($em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
/**
* Create a new QueryBuilder instance that is prepopulated for this entity name
*
* @param string $alias
* @return QueryBuilder $qb
*/
public function createQueryBuilder($alias)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
}
/**
* Clears the repository, causing all managed entities to become detached.
*/
public function clear()
{
$this->_em->clear($this->_class->rootEntityName);
}
/**
* Finds an entity by its primary key / identifier.
*
* @param $id The identifier.
* @param int $lockMode
* @param int $lockVersion
* @return object The entity.
*/
public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
{
// Check identity map first
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
if ($lockMode != LockMode::NONE) {
$this->_em->lock($entity, $lockMode, $lockVersion);
}
return $entity; // Hit!
}
if ( ! is_array($id) || count($id) <= 1) {
// @todo FIXME: Not correct. Relies on specific order.
$value = is_array($id) ? array_values($id) : array($id);
$id = array_combine($this->_class->identifier, $value);
}
if ($lockMode == LockMode::NONE) {
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
} else if ($lockMode == LockMode::OPTIMISTIC) {
if (!$this->_class->isVersioned) {
throw OptimisticLockException::notVersioned($this->_entityName);
}
$entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
$this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion);
return $entity;
} else {
if (!$this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode);
}
}
/**
* Finds all entities in the repository.
*
* @return array The entities.
*/
public function findAll()
{
return $this->findBy(array());
}
/**
* Finds entities by a set of criteria.
*
* @param array $criteria
* @return array
*/
public function findBy(array $criteria)
{
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria);
}
/**
* Finds a single entity by a set of criteria.
*
* @param array $criteria
* @return object
*/
public function findOneBy(array $criteria)
{
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria);
}
/**
* Adds support for magic finders.
*
* @return array|object The found entity/entities.
* @throws BadMethodCallException If the method called is an invalid find* method
* or no find* method at all and therefore an invalid
* method call.
*/
public function __call($method, $arguments)
{
if (substr($method, 0, 6) == 'findBy') {
$by = substr($method, 6, strlen($method));
$method = 'findBy';
} else if (substr($method, 0, 9) == 'findOneBy') {
$by = substr($method, 9, strlen($method));
$method = 'findOneBy';
} else {
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy or findOneBy!"
);
}
if ( !isset($arguments[0])) {
// we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
throw ORMException::findByRequiresParameter($method.$by);
}
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
return $this->$method(array($fieldName => $arguments[0]));
} else {
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
}
}
/**
* @return string
*/
protected function getEntityName()
{
return $this->_entityName;
}
/**
* @return EntityManager
*/
protected function getEntityManager()
{
return $this->_em;
}
/**
* @return Mapping\ClassMetadata
*/
protected function getClassMetadata()
{
return $this->_class;
}
}
@@ -0,0 +1,122 @@
<?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;
/**
* Container for all ORM events.
*
* This class cannot be instantiated.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
final class Events
{
private function __construct() {}
/**
* The preRemove event occurs for a given entity before the respective
* EntityManager remove operation for that entity is executed.
*
* This is an entity lifecycle event.
*
* @var string
*/
const preRemove = 'preRemove';
/**
* The postRemove event occurs for an entity after the entity has
* been deleted. It will be invoked after the database delete operations.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postRemove = 'postRemove';
/**
* The prePersist event occurs for a given entity before the respective
* EntityManager persist operation for that entity is executed.
*
* This is an entity lifecycle event.
*
* @var string
*/
const prePersist = 'prePersist';
/**
* The postPersist event occurs for an entity after the entity has
* been made persistent. It will be invoked after the database insert operations.
* Generated primary key values are available in the postPersist event.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postPersist = 'postPersist';
/**
* The preUpdate event occurs before the database update operations to
* entity data.
*
* This is an entity lifecycle event.
*
* @var string
*/
const preUpdate = 'preUpdate';
/**
* The postUpdate event occurs after the database update operations to
* entity data.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postUpdate = 'postUpdate';
/**
* The postLoad event occurs for an entity after the entity has been loaded
* into the current EntityManager from the database or after the refresh operation
* has been applied to it.
*
* Note that the postLoad event occurs for an entity before any associations have been
* initialized. Therefore it is not safe to access associations in a postLoad callback
* or event handler.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postLoad = 'postLoad';
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (annotations/xml/yaml).
*
* @var string
*/
const loadClassMetadata = 'loadClassMetadata';
/**
* The onFlush event occurs when the EntityManager#flush() operation is invoked,
* after any changes to managed entities have been determined but before any
* actual database operations are executed. The event is only raised if there is
* actually something to do for the underlying UnitOfWork. If nothing needs to be done,
* the onFlush event is not raised.
*
* @var string
*/
const onFlush = 'onFlush';
}
@@ -0,0 +1,73 @@
<?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;
/**
* Represents a native SQL query.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
final class NativeQuery extends AbstractQuery
{
private $_sql;
/**
* Sets the SQL of the query.
*
* @param string $sql
* @return NativeQuery This query instance.
*/
public function setSQL($sql)
{
$this->_sql = $sql;
return $this;
}
/**
* Gets the SQL query.
*
* @return mixed The built SQL query or an array of all SQL queries.
* @override
*/
public function getSQL()
{
return $this->_sql;
}
/**
* {@inheritdoc}
*/
protected function _doExecute()
{
$stmt = $this->_em->getConnection()->prepare($this->_sql);
$params = $this->_params;
foreach ($params as $key => $value) {
if (isset($this->_paramTypes[$key])) {
$stmt->bindValue($key, $value, $this->_paramTypes[$key]);
} else {
$stmt->bindValue($key, $value);
}
}
$stmt->execute();
return $stmt;
}
}
@@ -0,0 +1,34 @@
<?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;
/**
* Exception thrown when an ORM query unexpectedly does not return any results.
*
* @author robo
* @since 2.0
*/
class NoResultException extends ORMException
{
public function __construct()
{
parent::__construct('No result was found for query although at least one row was expected.');
}
}
@@ -0,0 +1,28 @@
<?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;
/**
* Exception thrown when an ORM query unexpectedly returns more than one result.
*
* @author robo
* @since 2.0
*/
class NonUniqueResultException extends ORMException {}
@@ -0,0 +1,118 @@
<?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;
use Exception;
/**
* Base exception class for all ORM exceptions.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
class ORMException extends Exception
{
public static function missingMappingDriverImpl()
{
return new self("It's a requirement to specify a Metadata Driver and pass it ".
"to Doctrine\ORM\Configuration::setMetadataDriverImpl().");
}
public static function entityMissingAssignedId($entity)
{
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
}
public static function unrecognizedField($field)
{
return new self("Unrecognized field: $field");
}
public static function invalidFlushMode($mode)
{
return new self("'$mode' is an invalid flush mode.");
}
public static function entityManagerClosed()
{
return new self("The EntityManager is closed.");
}
public static function invalidHydrationMode($mode)
{
return new self("'$mode' is an invalid hydration mode.");
}
public static function mismatchedEventManager()
{
return new self("Cannot use different EventManager instances for EntityManager and Connection.");
}
public static function findByRequiresParameter($methodName)
{
return new self("You need to pass a parameter to '".$methodName."'");
}
public static function invalidFindByCall($entityName, $fieldName, $method)
{
return new self(
"Entity '".$entityName."' has no field '".$fieldName."'. ".
"You can therefore not call '".$method."' on the entities' repository"
);
}
public static function invalidFindByInverseAssociation($entityName, $associationFieldName)
{
return new self(
"You cannot search for the association field '".$entityName."#".$associationFieldName."', ".
"because it is the inverse side of an association. Find methods only work on owning side associations."
);
}
public static function invalidResultCacheDriver() {
return new self("Invalid result cache driver; it must implement \Doctrine\Common\Cache\Cache.");
}
public static function notSupported() {
return new self("This behaviour is (currently) not supported by Doctrine 2");
}
public static function queryCacheNotConfigured()
{
return new self('Query Cache is not configured.');
}
public static function metadataCacheNotConfigured()
{
return new self('Class Metadata Cache is not configured.');
}
public static function proxyClassesAlwaysRegenerating()
{
return new self('Proxy Classes are always regenerating.');
}
public static function unknownEntityNamespace($entityNamespaceAlias)
{
return new self(
"Unknown Entity namespace alias '$entityNamespaceAlias'."
);
}
}
@@ -0,0 +1,63 @@
<?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;
/**
* An OptimisticLockException is thrown when a version check on an object
* that uses optimistic locking through a version field fails.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class OptimisticLockException extends ORMException
{
private $entity;
public function __construct($msg, $entity)
{
$this->entity = $entity;
}
/**
* Gets the entity that caused the exception.
*
* @return object
*/
public function getEntity()
{
return $this->entity;
}
public static function lockFailed($entity)
{
return new self("The optimistic lock on an entity failed.", $entity);
}
public static function lockFailedVersionMissmatch($entity, $expectedLockVersion, $actualLockVersion)
{
return new self("The optimistic lock failed, version " . $expectedLockVersion . " was expected, but is actually ".$actualLockVersion, $entity);
}
public static function notVersioned($entityName)
{
return new self("Cannot obtain optimistic lock on unversioned entity " . $entityName, null);
}
}
@@ -0,0 +1,681 @@
<?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;
use Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\Common\Collections\Collection,
Closure;
/**
* A PersistentCollection represents a collection of elements that have persistent state.
*
* Collections of entities represent only the associations (links) to those entities.
* That means, if the collection is part of a many-many mapping and you remove
* entities from the collection, only the links in the relation table are removed (on flush).
* Similarly, if you remove entities from a collection that is part of a one-many
* mapping this will only result in the nulling out of the foreign keys on flush.
*
* @since 2.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @todo Design for inheritance to allow custom implementations?
*/
final class PersistentCollection implements Collection
{
/**
* A snapshot of the collection at the moment it was fetched from the database.
* This is used to create a diff of the collection at commit time.
*
* @var array
*/
private $snapshot = array();
/**
* The entity that owns this collection.
*
* @var object
*/
private $owner;
/**
* The association mapping the collection belongs to.
* This is currently either a OneToManyMapping or a ManyToManyMapping.
*
* @var Doctrine\ORM\Mapping\AssociationMapping
*/
private $association;
/**
* The EntityManager that manages the persistence of the collection.
*
* @var Doctrine\ORM\EntityManager
*/
private $em;
/**
* The name of the field on the target entities that points to the owner
* of the collection. This is only set if the association is bi-directional.
*
* @var string
*/
private $backRefFieldName;
/**
* The class descriptor of the collection's entity type.
*/
private $typeClass;
/**
* Whether the collection is dirty and needs to be synchronized with the database
* when the UnitOfWork that manages its persistent state commits.
*
* @var boolean
*/
private $isDirty = false;
/**
* 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.
*/
public function __construct(EntityManager $em, $class, $coll)
{
$this->coll = $coll;
$this->em = $em;
$this->typeClass = $class;
}
/**
* INTERNAL:
* Sets the collection's owning entity together with the AssociationMapping that
* describes the association between the owner and the elements of the collection.
*
* @param object $entity
* @param AssociationMapping $assoc
*/
public function setOwner($entity, array $assoc)
{
$this->owner = $entity;
$this->association = $assoc;
$this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy'];
}
/**
* INTERNAL:
* Gets the collection owner.
*
* @return object
*/
public function getOwner()
{
return $this->owner;
}
public function getTypeClass()
{
return $this->typeClass;
}
/**
* 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)
{
$this->coll->add($element);
// If _backRefFieldName is set and its a one-to-many association,
// we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) {
// Set back reference to owner
$this->typeClass->reflFields[$this->backRefFieldName]
->setValue($element, $this->owner);
$this->em->getUnitOfWork()->setOriginalEntityProperty(
spl_object_hash($element),
$this->backRefFieldName,
$this->owner);
}
}
/**
* INTERNAL:
* Sets a keyed element in the collection during hydration.
*
* @param mixed $key The key to set.
* $param mixed $value The element to set.
*/
public function hydrateSet($key, $element)
{
$this->coll->set($key, $element);
// If _backRefFieldName is set, then the association is bidirectional
// and we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) {
// Set back reference to owner
$this->typeClass->reflFields[$this->backRefFieldName]
->setValue($element, $this->owner);
}
}
/**
* Initializes the collection by loading its contents from the database
* if the collection is not yet initialized.
*/
private function initialize()
{
if ( ! $this->initialized && $this->association) {
if ($this->isDirty) {
// Has NEW objects added through add(). Remember them.
$newObjects = $this->coll->toArray();
}
$this->coll->clear();
$this->em->getUnitOfWork()->loadCollection($this);
$this->takeSnapshot();
// Reattach NEW objects added through add(), if any.
if (isset($newObjects)) {
foreach ($newObjects as $obj) {
$this->coll->add($obj);
}
$this->isDirty = true;
}
$this->initialized = true;
}
}
/**
* INTERNAL:
* Tells this collection to take a snapshot of its current state.
*/
public function takeSnapshot()
{
$this->snapshot = $this->coll->toArray();
$this->isDirty = false;
}
/**
* INTERNAL:
* Returns the last snapshot of the elements in the collection.
*
* @return array The last snapshot of the elements.
*/
public function getSnapshot()
{
return $this->snapshot;
}
/**
* INTERNAL:
* getDeleteDiff
*
* @return array
*/
public function getDeleteDiff()
{
return array_udiff_assoc($this->snapshot, $this->coll->toArray(),
function($a, $b) {return $a === $b ? 0 : 1;});
}
/**
* INTERNAL:
* getInsertDiff
*
* @return array
*/
public function getInsertDiff()
{
return array_udiff_assoc($this->coll->toArray(), $this->snapshot,
function($a, $b) {return $a === $b ? 0 : 1;});
}
/**
* INTERNAL: Gets the association mapping of the collection.
*
* @return Doctrine\ORM\Mapping\AssociationMapping
*/
public function getMapping()
{
return $this->association;
}
/**
* Marks this collection as changed/dirty.
*/
private function changed()
{
if ( ! $this->isDirty) {
$this->isDirty = true;
if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] == ClassMetadata::MANY_TO_MANY &&
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
$this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
}
}
}
/**
* Gets a boolean flag indicating whether this collection is dirty which means
* its state needs to be synchronized with the database.
*
* @return boolean TRUE if the collection is dirty, FALSE otherwise.
*/
public function isDirty()
{
return $this->isDirty;
}
/**
* Sets a boolean flag, indicating whether this collection is dirty.
*
* @param boolean $dirty Whether the collection should be marked dirty or not.
*/
public function setDirty($dirty)
{
$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.
*
* @return boolean
*/
public function isInitialized()
{
return $this->initialized;
}
/** {@inheritdoc} */
public function first()
{
$this->initialize();
return $this->coll->first();
}
/** {@inheritdoc} */
public function last()
{
$this->initialize();
return $this->coll->last();
}
/**
* {@inheritdoc}
*/
public function remove($key)
{
// TODO: If the keys are persistent as well (not yet implemented)
// and the collection is not initialized and orphanRemoval is
// not used we can issue a straight SQL delete/update on the
// association (table). Without initializing the collection.
$this->initialize();
$removed = $this->coll->remove($key);
if ($removed) {
$this->changed();
if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
$this->association['orphanRemoval']) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
}
}
return $removed;
}
/**
* {@inheritdoc}
*/
public function removeElement($element)
{
// TODO: Assuming the identity of entities in a collection is always based
// on their primary key (there is no equals/hashCode in PHP),
// if the collection is not initialized, we could issue a straight
// SQL DELETE/UPDATE on the association (table) without initializing
// the collection.
/*if ( ! $this->initialized) {
$this->em->getUnitOfWork()->getCollectionPersister($this->association)
->deleteRows($this, $element);
}*/
$this->initialize();
$removed = $this->coll->removeElement($element);
if ($removed) {
$this->changed();
if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
$this->association['orphanRemoval']) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
}
}
return $removed;
}
/**
* {@inheritdoc}
*/
public function containsKey($key)
{
$this->initialize();
return $this->coll->containsKey($key);
}
/**
* {@inheritdoc}
*/
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;
}*/
$this->initialize();
return $this->coll->contains($element);
}
/**
* {@inheritdoc}
*/
public function exists(Closure $p)
{
$this->initialize();
return $this->coll->exists($p);
}
/**
* {@inheritdoc}
*/
public function indexOf($element)
{
$this->initialize();
return $this->coll->indexOf($element);
}
/**
* {@inheritdoc}
*/
public function get($key)
{
$this->initialize();
return $this->coll->get($key);
}
/**
* {@inheritdoc}
*/
public function getKeys()
{
$this->initialize();
return $this->coll->getKeys();
}
/**
* {@inheritdoc}
*/
public function getValues()
{
$this->initialize();
return $this->coll->getValues();
}
/**
* {@inheritdoc}
*/
public function count()
{
$this->initialize();
return $this->coll->count();
}
/**
* {@inheritdoc}
*/
public function set($key, $value)
{
$this->initialize();
$this->coll->set($key, $value);
$this->changed();
}
/**
* {@inheritdoc}
*/
public function add($value)
{
$this->coll->add($value);
$this->changed();
return true;
}
/**
* {@inheritdoc}
*/
public function isEmpty()
{
$this->initialize();
return $this->coll->isEmpty();
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
$this->initialize();
return $this->coll->getIterator();
}
/**
* {@inheritdoc}
*/
public function map(Closure $func)
{
$this->initialize();
return $this->coll->map($func);
}
/**
* {@inheritdoc}
*/
public function filter(Closure $p)
{
$this->initialize();
return $this->coll->filter($p);
}
/**
* {@inheritdoc}
*/
public function forAll(Closure $p)
{
$this->initialize();
return $this->coll->forAll($p);
}
/**
* {@inheritdoc}
*/
public function partition(Closure $p)
{
$this->initialize();
return $this->coll->partition($p);
}
/**
* {@inheritdoc}
*/
public function toArray()
{
$this->initialize();
return $this->coll->toArray();
}
/**
* {@inheritdoc}
*/
public function clear()
{
if ($this->initialized && $this->isEmpty()) {
return;
}
if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
foreach ($this->coll as $element) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
}
}
$this->coll->clear();
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.
*
* @internal Tried to implement Serializable first but that did not work well
* with circular references. This solution seems simpler and works well.
*/
public function __sleep()
{
return array('coll', 'initialized');
}
/* ArrayAccess implementation */
/**
* @see containsKey()
*/
public function offsetExists($offset)
{
return $this->containsKey($offset);
}
/**
* @see get()
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* @see add()
* @see set()
*/
public function offsetSet($offset, $value)
{
if ( ! isset($offset)) {
return $this->add($value);
}
return $this->set($offset, $value);
}
/**
* @see remove()
*/
public function offsetUnset($offset)
{
return $this->remove($offset);
}
public function key()
{
return $this->coll->key();
}
/**
* Gets the element of the collection at the current iterator position.
*/
public function current()
{
return $this->coll->current();
}
/**
* Moves the internal iterator position to the next element.
*/
public function next()
{
return $this->coll->next();
}
/**
* Retrieves the wrapped Collection instance.
*/
public function unwrap()
{
return $this->coll;
}
/**
* Extract a slice of $length elements starting at position $offset from the Collection.
*
* If $length is null it returns all elements from $offset to the end of the Collection.
* Keys have to be preserved by this method. Calling this method will only return the
* selected slice and NOT change the elements contained in the collection slice is called on.
*
* @param int $offset
* @param int $length
* @return array
*/
public function slice($offset, $length = null)
{
$this->initialize();
return $this->coll->slice($offset, $length);
}
}
@@ -0,0 +1,40 @@
<?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;
/**
* Pessimistic Lock Exception
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 1.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Roman Borschel <roman@code-factory.org>
*/
class PessimisticLockException extends ORMException
{
public static function lockFailed()
{
return new self("The pessimistic lock failed.");
}
}
@@ -0,0 +1,562 @@
<?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;
use Doctrine\DBAL\LockMode,
Doctrine\ORM\Query\Parser,
Doctrine\ORM\Query\QueryException;
/**
* A Query object represents a DQL query.
*
* @since 1.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
*/
final class Query extends AbstractQuery
{
/* Query STATES */
/**
* A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
*/
const STATE_CLEAN = 1;
/**
* A query object is in state DIRTY when it has DQL parts that have not yet been
* parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
* 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';
/**
* The forcePartialLoad query hint forces a particular query to return
* partial objects.
*
* @var string
* @todo Rename: HINT_OPTIMIZE
*/
const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
/**
* 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
* are iterated and executed after the DQL has been parsed into an AST.
*
* @var string
*/
const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
/**
* 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
*/
const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
//const HINT_READ_ONLY = 'doctrine.readOnly';
/**
* @var string
*/
const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
/**
* @var string
*/
const HINT_LOCK_MODE = 'doctrine.lockMode';
/**
* @var integer $_state The current state of this query.
*/
private $_state = self::STATE_CLEAN;
/**
* @var string $_dql Cached DQL query.
*/
private $_dql = null;
/**
* @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").
*/
private $_maxResults = null;
/**
* @var CacheDriver The cache driver used for caching queries.
*/
private $_queryCache;
/**
* @var boolean Boolean value that indicates whether or not expire the query cache.
*/
private $_expireQueryCache = false;
/**
* @var int Query Cache lifetime.
*/
private $_queryCacheTTL;
/**
* @var boolean Whether to use a query cache, if available. Defaults to TRUE.
*/
private $_useQueryCache = true;
// End of Caching Stuff
/**
* Initializes a new Query instance.
*
* @param Doctrine\ORM\EntityManager $entityManager
*/
/*public function __construct(EntityManager $entityManager)
{
parent::__construct($entityManager);
}*/
/**
* Gets the SQL query/queries that correspond to this DQL query.
*
* @return mixed The built sql query or an array of all sql queries.
* @override
*/
public function getSQL()
{
return $this->_parse()->getSQLExecutor()->getSQLStatements();
}
/**
* Returns the corresponding AST for this DQL query.
*
* @return Doctrine\ORM\Query\AST\SelectStatement |
* Doctrine\ORM\Query\AST\UpdateStatement |
* Doctrine\ORM\Query\AST\DeleteStatement
*/
public function getAST()
{
$parser = new Parser($this);
return $parser->getAST();
}
/**
* Parses the DQL query, if necessary, and stores the parser result.
*
* Note: Populates $this->_parserResult as a side-effect.
*
* @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);
$this->_parserResult = $parser->parse();
$queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
} else {
// Cache hit.
$this->_parserResult = $cached;
}
} else {
$parser = new Parser($this);
$this->_parserResult = $parser->parse();
}
$this->_state = self::STATE_CLEAN;
return $this->_parserResult;
}
/**
* {@inheritdoc}
*/
protected function _doExecute()
{
$executor = $this->_parse()->getSqlExecutor();
// Prepare parameters
$paramMappings = $this->_parserResult->getParameterMappings();
if (count($paramMappings) != count($this->_params)) {
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);
}
if ($this->_resultSetMapping === null) {
$this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
}
return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
}
/**
* Defines a cache driver to be used for caching queries.
*
* @param Doctrine_Cache_Interface|null $driver Cache driver
* @return Query This query instance.
*/
public function setQueryCacheDriver($queryCache)
{
$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.
*/
public function useQueryCache($bool)
{
$this->_useQueryCache = $bool;
return $this;
}
/**
* Returns the cache driver used for query caching.
*
* @return CacheDriver The cache driver used for query caching or NULL, if this
* Query does not use query caching.
*/
public function getQueryCacheDriver()
{
if ($this->_queryCache) {
return $this->_queryCache;
} else {
return $this->_em->getConfiguration()->getQueryCacheImpl();
}
}
/**
* Defines how long the query cache will be active before expire.
*
* @param integer $timeToLive How long the cache entry is valid
* @return Query This query instance.
*/
public function setQueryCacheLifetime($timeToLive)
{
if ($timeToLive !== null) {
$timeToLive = (int) $timeToLive;
}
$this->_queryCacheTTL = $timeToLive;
return $this;
}
/**
* Retrieves the lifetime of resultset cache.
*
* @return int
*/
public function getQueryCacheLifetime()
{
return $this->_queryCacheTTL;
}
/**
* Defines if the query cache is active or not.
*
* @param boolean $expire Whether or not to force query cache expiration.
* @return Query This query instance.
*/
public function expireQueryCache($expire = true)
{
$this->_expireQueryCache = $expire;
return $this;
}
/**
* Retrieves if the query cache is active or not.
*
* @return bool
*/
public function getExpireQueryCache()
{
return $this->_expireQueryCache;
}
/**
* @override
*/
public function free()
{
parent::free();
$this->_dql = null;
$this->_state = self::STATE_CLEAN;
}
/**
* Sets a DQL query string.
*
* @param string $dqlQuery DQL Query
* @return Doctrine\ORM\AbstractQuery
*/
public function setDQL($dqlQuery)
{
if ($dqlQuery !== null) {
$this->_dql = $dqlQuery;
$this->_state = self::STATE_DIRTY;
}
return $this;
}
/**
* Returns the DQL query that is represented by this query object.
*
* @return string DQL query
*/
public function getDQL()
{
return $this->_dql;
}
/**
* Returns the state of this query object
* By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
* part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
*
* @see AbstractQuery::STATE_CLEAN
* @see AbstractQuery::STATE_DIRTY
*
* @return integer Return the query state
*/
public function getState()
{
return $this->_state;
}
/**
* Method to check if an arbitrary piece of DQL exists
*
* @param string $dql Arbitrary piece of DQL to check for
* @return boolean
*/
public function contains($dql)
{
return stripos($this->getDQL(), $dql) === false ? false : true;
}
/**
* Sets the position of the first result to retrieve (the "offset").
*
* @param integer $firstResult The first result to return.
* @return Query This query object.
*/
public function setFirstResult($firstResult)
{
$this->_firstResult = $firstResult;
$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.
*/
public function setMaxResults($maxResults)
{
$this->_maxResults = $maxResults;
$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()
{
return $this->_maxResults;
}
/**
* Executes the query and returns an IterableResult that can be used to incrementally
* iterated over the result.
*
* @param array $params The query parameters.
* @param integer $hydrationMode The hydration mode to use.
* @return IterableResult
*/
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
{
$this->setHint(self::HINT_INTERNAL_ITERATION, true);
return parent::iterate($params, $hydrationMode);
}
/**
* {@inheritdoc}
*/
public function setHint($name, $value)
{
$this->_state = self::STATE_DIRTY;
return parent::setHint($name, $value);
}
/**
* {@inheritdoc}
*/
public function setHydrationMode($hydrationMode)
{
$this->_state = self::STATE_DIRTY;
return parent::setHydrationMode($hydrationMode);
}
/**
* Set the lock mode for this Query.
*
* @see Doctrine\DBAL\LockMode
* @param int $lockMode
* @return Query
*/
public function setLockMode($lockMode)
{
if ($lockMode == LockMode::PESSIMISTIC_READ || $lockMode == LockMode::PESSIMISTIC_WRITE) {
if (!$this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
}
$this->setHint(self::HINT_LOCK_MODE, $lockMode);
return $this;
}
/**
* Get the current lock mode for this query.
*
* @return int
*/
public function getLockMode()
{
$lockMode = $this->getHint(self::HINT_LOCK_MODE);
if (!$lockMode) {
return LockMode::NONE;
}
return $lockMode;
}
/**
* Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
*
* The query cache
*
* @return string
*/
protected function _getQueryCacheId()
{
ksort($this->_hints);
return md5(
$this->getDql() . var_export($this->_hints, true) .
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
);
}
/**
* Cleanup Query resource when clone is called.
*
* @return void
*/
public function __clone()
{
parent::__clone();
$this->_state = self::STATE_DIRTY;
}
}
@@ -0,0 +1,935 @@
<?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;
use Doctrine\ORM\Query\Expr;
/**
* This class is responsible for building DQL query strings via an object oriented
* PHP interface.
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class QueryBuilder
{
/* The query types. */
const SELECT = 0;
const DELETE = 1;
const UPDATE = 2;
/** The builder states. */
const STATE_DIRTY = 0;
const STATE_CLEAN = 1;
/**
* @var EntityManager The EntityManager used by this QueryBuilder.
*/
private $_em;
/**
* @var array The array of DQL parts collected.
*/
private $_dqlParts = array(
'select' => array(),
'from' => array(),
'join' => array(),
'set' => array(),
'where' => null,
'groupBy' => array(),
'having' => null,
'orderBy' => array()
);
/**
* @var integer The type of query this is. Can be select, update or delete.
*/
private $_type = self::SELECT;
/**
* @var integer The state of the query object. Can be dirty or clean.
*/
private $_state = self::STATE_CLEAN;
/**
* @var string The complete DQL string for this query.
*/
private $_dql;
/**
* @var array The query parameters.
*/
private $_params = array();
/**
* @var array The parameter type map of this query.
*/
private $_paramTypes = array();
/**
* @var integer The index of the first result to retrieve.
*/
private $_firstResult = null;
/**
* @var integer The maximum number of results to retrieve.
*/
private $_maxResults = null;
/**
* Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
*
* @param EntityManager $em The EntityManager to use.
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
}
/**
* Gets an ExpressionBuilder used for object-oriented construction of query expressions.
* This producer method is intended for convenient inline usage. Example:
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->where($qb->expr()->eq('u.id', 1));
* </code>
*
* For more complex expression construction, consider storing the expression
* builder object in a local variable.
*
* @return Expr
*/
public function expr()
{
return $this->_em->getExpressionBuilder();
}
/**
* Get the type of the currently built query.
*
* @return integer
*/
public function getType()
{
return $this->_type;
}
/**
* Get the associated EntityManager for this query builder.
*
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/**
* Get the state of this query builder instance.
*
* @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
*/
public function getState()
{
return $this->_state;
}
/**
* Get the complete DQL string formed by the current specifications of this QueryBuilder.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* echo $qb->getDql(); // SELECT u FROM User u
* </code>
*
* @return string The DQL query string.
*/
public function getDQL()
{
if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
return $this->_dql;
}
$dql = '';
switch ($this->_type) {
case self::DELETE:
$dql = $this->_getDQLForDelete();
break;
case self::UPDATE:
$dql = $this->_getDQLForUpdate();
break;
case self::SELECT:
default:
$dql = $this->_getDQLForSelect();
break;
}
$this->_state = self::STATE_CLEAN;
$this->_dql = $dql;
return $dql;
}
/**
* Constructs a Query instance from the current specifications of the builder.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u');
* $q = $qb->getQuery();
* $results = $q->execute();
* </code>
*
* @return Query
*/
public function getQuery()
{
return $this->_em->createQuery($this->getDQL())
->setParameters($this->_params, $this->_paramTypes)
->setFirstResult($this->_firstResult)
->setMaxResults($this->_maxResults);
}
/**
* Gets the 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>
*
* @return string $rootAlias
* @todo Rename/Refactor: getRootAliases(), there can be multiple roots!
*/
public function getRootAlias()
{
return $this->_dqlParts['from'][0]->getAlias();
}
/**
* Sets a query parameter for the query being constructed.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->where('u.id = :user_id')
* ->setParameter(':user_id', 1);
* </code>
*
* @param string|integer $key The parameter position or name.
* @param mixed $value The parameter value.
* @param string|null $type PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
* @return QueryBuilder This QueryBuilder instance.
*/
public function setParameter($key, $value, $type = null)
{
if ($type !== null) {
$this->_paramTypes[$key] = $type;
}
$this->_params[$key] = $value;
return $this;
}
/**
* Sets a collection of query parameters for the query being constructed.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->where('u.id = :user_id1 OR u.id = :user_id2')
* ->setParameters(array(
* ':user_id1' => 1,
* ':user_id2' => 2
* ));
* </code>
*
* @param array $params The query parameters to set.
* @return QueryBuilder This QueryBuilder instance.
*/
public function setParameters(array $params, array $types = array())
{
$this->_paramTypes = $types;
$this->_params = $params;
return $this;
}
/**
* Gets all defined query parameters for the query being constructed.
*
* @return array The currently defined query parameters.
*/
public function getParameters()
{
return $this->_params;
}
/**
* Gets a (previously set) query parameter of the query being constructed.
*
* @param mixed $key The key (index or name) of the bound parameter.
* @return mixed The value of the bound parameter.
*/
public function getParameter($key)
{
return isset($this->_params[$key]) ? $this->_params[$key] : null;
}
/**
* Sets the position of the first result to retrieve (the "offset").
*
* @param integer $firstResult The first result to return.
* @return QueryBuilder This QueryBuilder instance.
*/
public function setFirstResult($firstResult)
{
$this->_firstResult = $firstResult;
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 QueryBuilder.
*
* @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 The maximum number of results to retrieve.
* @return QueryBuilder This QueryBuilder instance.
*/
public function setMaxResults($maxResults)
{
$this->_maxResults = $maxResults;
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 builder.
*
* @return integer Maximum number of results.
*/
public function getMaxResults()
{
return $this->_maxResults;
}
/**
* Either appends to or replaces a single, generic query part.
*
* The available parts are: 'select', 'from', 'join', 'set', 'where',
* 'groupBy', 'having' and 'orderBy'.
*
* @param string $dqlPartName
* @param string $dqlPart
* @param string $append
* @return QueryBuilder This QueryBuilder instance.
*/
public function add($dqlPartName, $dqlPart, $append = false)
{
$isMultiple = is_array($this->_dqlParts[$dqlPartName]);
if ($append && $isMultiple) {
$this->_dqlParts[$dqlPartName][] = $dqlPart;
} else {
$this->_dqlParts[$dqlPartName] = ($isMultiple) ? array($dqlPart) : $dqlPart;
}
$this->_state = self::STATE_DIRTY;
return $this;
}
/**
* Specifies an item that is to be returned in the query result.
* Replaces any previously specified selections, if any.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u', 'p')
* ->from('User', 'u')
* ->leftJoin('u.Phonenumbers', 'p');
* </code>
*
* @param mixed $select The selection expressions.
* @return QueryBuilder This QueryBuilder instance.
*/
public function select($select = null)
{
$this->_type = self::SELECT;
if (empty($select)) {
return $this;
}
$selects = is_array($select) ? $select : func_get_args();
return $this->add('select', new Expr\Select($selects), false);
}
/**
* Adds an item that is to be returned in the query result.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->addSelect('p')
* ->from('User', 'u')
* ->leftJoin('u.Phonenumbers', 'p');
* </code>
*
* @param mixed $select The selection expression.
* @return QueryBuilder This QueryBuilder instance.
*/
public function addSelect($select = null)
{
$this->_type = self::SELECT;
if (empty($select)) {
return $this;
}
$selects = is_array($select) ? $select : func_get_args();
return $this->add('select', new Expr\Select($selects), true);
}
/**
* Turns the query being built into a bulk delete query that ranges over
* a certain entity type.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->delete('User', 'u')
* ->where('u.id = :user_id');
* ->setParameter(':user_id', 1);
* </code>
*
* @param string $delete The class/type whose instances are subject to the deletion.
* @param string $alias The class/type alias used in the constructed query.
* @return QueryBuilder This QueryBuilder instance.
*/
public function delete($delete = null, $alias = null)
{
$this->_type = self::DELETE;
if ( ! $delete) {
return $this;
}
return $this->add('from', new Expr\From($delete, $alias));
}
/**
* Turns the query being built into a bulk update query that ranges over
* a certain entity type.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->update('User', 'u')
* ->set('u.password', md5('password'))
* ->where('u.id = ?');
* </code>
*
* @param string $update The class/type whose instances are subject to the update.
* @param string $alias The class/type alias used in the constructed query.
* @return QueryBuilder This QueryBuilder instance.
*/
public function update($update = null, $alias = null)
{
$this->_type = self::UPDATE;
if ( ! $update) {
return $this;
}
return $this->add('from', new Expr\From($update, $alias));
}
/**
* Create and add a query root corresponding to the entity identified by the given alias,
* forming a cartesian product with any existing query roots.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* </code>
*
* @param string $from The class name.
* @param string $alias The alias of the class.
* @return QueryBuilder This QueryBuilder instance.
*/
public function from($from, $alias)
{
return $this->add('from', new Expr\From($from, $alias), true);
}
/**
* Creates and adds a join over an entity association to the query.
*
* The entities in the joined association will be fetched as part of the query
* result if the alias used for the joined association is placed in the select
* expressions.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
* </code>
*
* @param string $join The relationship to join
* @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
* @return QueryBuilder This QueryBuilder instance.
*/
public function join($join, $alias, $conditionType = null, $condition = null)
{
return $this->innerJoin($join, $alias, $conditionType, $condition);
}
/**
* Creates and adds a join over an entity association to the query.
*
* The entities in the joined association will be fetched as part of the query
* result if the alias used for the joined association is placed in the select
* expressions.
*
* [php]
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
*
* @param string $join The relationship to join
* @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
* @return QueryBuilder This QueryBuilder instance.
*/
public function innerJoin($join, $alias, $conditionType = null, $condition = null)
{
return $this->add('join', new Expr\Join(
Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition
), true);
}
/**
* Creates and adds a left join over an entity association to the query.
*
* The entities in the joined association will be fetched as part of the query
* result if the alias used for the joined association is placed in the select
* expressions.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
* </code>
*
* @param string $join The relationship to join
* @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
* @return QueryBuilder This QueryBuilder instance.
*/
public function leftJoin($join, $alias, $conditionType = null, $condition = null)
{
return $this->add('join', new Expr\Join(
Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition
), true);
}
/**
* Sets a new value for a field in a bulk update query.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->update('User', 'u')
* ->set('u.password', md5('password'))
* ->where('u.id = ?');
* </code>
*
* @param string $key The key/field to set.
* @param string $value The value, expression, placeholder, etc.
* @return QueryBuilder This QueryBuilder instance.
*/
public function set($key, $value)
{
return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
}
/**
* Specifies one or more restrictions to the query result.
* Replaces any previously specified restrictions, if any.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->where('u.id = ?');
*
* // You can optionally programatically build and/or expressions
* $qb = $em->createQueryBuilder();
*
* $or = $qb->expr()->orx();
* $or->add($qb->expr()->eq('u.id', 1));
* $or->add($qb->expr()->eq('u.id', 2));
*
* $qb->update('User', 'u')
* ->set('u.password', md5('password'))
* ->where($or);
* </code>
*
* @param mixed $predicates The restriction predicates.
* @return QueryBuilder This QueryBuilder instance.
*/
public function where($predicates)
{
if ( ! (func_num_args() == 1 && ($predicates instanceof Expr\Andx || $predicates instanceof Expr\Orx))) {
$predicates = new Expr\Andx(func_get_args());
}
return $this->add('where', $predicates);
}
/**
* Adds one or more restrictions to the query results, forming a logical
* conjunction with any previously specified restrictions.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->where('u.username LIKE ?')
* ->andWhere('u.is_active = 1');
* </code>
*
* @param mixed $where The query restrictions.
* @return QueryBuilder This QueryBuilder instance.
* @see where()
*/
public function andWhere($where)
{
$where = $this->getDQLPart('where');
$args = func_get_args();
if ($where instanceof Expr\Andx) {
$where->addMultiple($args);
} else {
array_unshift($args, $where);
$where = new Expr\Andx($args);
}
return $this->add('where', $where, true);
}
/**
* Adds one or more restrictions to the query results, forming a logical
* disjunction with any previously specified restrictions.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->where('u.id = 1')
* ->orWhere('u.id = 2');
* </code>
*
* @param mixed $where The WHERE statement
* @return QueryBuilder $qb
* @see where()
*/
public function orWhere($where)
{
$where = $this->getDqlPart('where');
$args = func_get_args();
if ($where instanceof Expr\Orx) {
$where->addMultiple($args);
} else {
array_unshift($args, $where);
$where = new Expr\Orx($args);
}
return $this->add('where', $where, true);
}
/**
* Specifies a grouping over the results of the query.
* Replaces any previously specified groupings, if any.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->groupBy('u.id');
* </code>
*
* @param string $groupBy The grouping expression.
* @return QueryBuilder This QueryBuilder instance.
*/
public function groupBy($groupBy)
{
return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
}
/**
* Adds a grouping expression to the query.
*
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->groupBy('u.lastLogin');
* ->addGroupBy('u.createdAt')
* </code>
*
* @param string $groupBy The grouping expression.
* @return QueryBuilder This QueryBuilder instance.
*/
public function addGroupBy($groupBy)
{
return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
}
/**
* Specifies a restriction over the groups of the query.
* Replaces any previous having restrictions, if any.
*
* @param mixed $having The restriction over the groups.
* @return QueryBuilder This QueryBuilder instance.
*/
public function having($having)
{
if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
$having = new Expr\Andx(func_get_args());
}
return $this->add('having', $having);
}
/**
* Adds a restriction over the groups of the query, forming a logical
* conjunction with any existing having restrictions.
*
* @param mixed $having The restriction to append.
* @return QueryBuilder This QueryBuilder instance.
*/
public function andHaving($having)
{
$having = $this->getDqlPart('having');
$args = func_get_args();
if ($having instanceof Expr\Andx) {
$having->addMultiple($args);
} else {
array_unshift($args, $having);
$having = new Expr\Andx($args);
}
return $this->add('having', $having);
}
/**
* Adds a restriction over the groups of the query, forming a logical
* disjunction with any existing having restrictions.
*
* @param mixed $having The restriction to add.
* @return QueryBuilder This QueryBuilder instance.
*/
public function orHaving($having)
{
$having = $this->getDqlPart('having');
$args = func_get_args();
if ($having instanceof Expr\Orx) {
$having->addMultiple($args);
} else {
array_unshift($args, $having);
$having = new Expr\Orx($args);
}
return $this->add('having', $having);
}
/**
* Specifies an ordering for the query results.
* Replaces any previously specified orderings, if any.
*
* @param string $sort The ordering expression.
* @param string $order The ordering direction.
* @return QueryBuilder This QueryBuilder instance.
*/
public function orderBy($sort, $order = null)
{
return $this->add('orderBy', $sort instanceof Expr\OrderBy ? $sort
: new Expr\OrderBy($sort, $order));
}
/**
* Adds an ordering to the query results.
*
* @param string $sort The ordering expression.
* @param string $order The ordering direction.
* @return QueryBuilder This QueryBuilder instance.
*/
public function addOrderBy($sort, $order = null)
{
return $this->add('orderBy', new Expr\OrderBy($sort, $order), true);
}
/**
* Get a query part by its name.
*
* @param string $queryPartName
* @return mixed $queryPart
* @todo Rename: getQueryPart (or remove?)
*/
public function getDQLPart($queryPartName)
{
return $this->_dqlParts[$queryPartName];
}
/**
* Get all query parts.
*
* @return array $dqlParts
* @todo Rename: getQueryParts (or remove?)
*/
public function getDQLParts()
{
return $this->_dqlParts;
}
private function _getDQLForDelete()
{
return 'DELETE'
. $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
. $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
}
private function _getDQLForUpdate()
{
return 'UPDATE'
. $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('set', array('pre' => ' SET ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
. $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
}
private function _getDQLForSelect()
{
return 'SELECT'
. $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('from', array('pre' => ' FROM ', 'separator' => ', '))
. $this->_getReducedDQLQueryPart('join', array('pre' => ' ', 'separator' => ' '))
. $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' => ', '));
}
private function _getReducedDQLQueryPart($queryPartName, $options = array())
{
$queryPart = $this->getDQLPart($queryPartName);
if (empty($queryPart)) {
return (isset($options['empty']) ? $options['empty'] : '');
}
return (isset($options['pre']) ? $options['pre'] : '')
. (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
. (isset($options['post']) ? $options['post'] : '');
}
/**
* Reset DQL parts
*
* @param array $parts
* @return QueryBuilder
*/
public function resetDQLParts($parts = null)
{
if (is_null($parts)) {
$parts = array_keys($this->_dqlParts);
}
foreach ($parts as $part) {
$this->resetDQLPart($part);
}
return $this;
}
/**
* Reset single DQL part
*
* @param string $part
* @return QueryBuilder;
*/
public function resetDQLPart($part)
{
if (is_array($this->_dqlParts[$part])) {
$this->_dqlParts[$part] = array();
} else {
$this->_dqlParts[$part] = null;
}
$this->_state = self::STATE_DIRTY;
return $this;
}
/**
* Gets a string representation of this QueryBuilder which corresponds to
* the final DQL query being constructed.
*
* @return string The string representation of this QueryBuilder.
*/
public function __toString()
{
return $this->getDQL();
}
}
@@ -0,0 +1,40 @@
<?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;
/**
* Is thrown when a transaction is required for the current operation, but there is none open.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 1.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Roman Borschel <roman@code-factory.org>
*/
class TransactionRequiredException extends ORMException
{
static public function transactionRequired()
{
return new self('An open transaction is required for this operation.');
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -0,0 +1,55 @@
<?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;
/**
* Class to store and retrieve the version of Doctrine
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Version
{
/**
* Current Doctrine Version
*/
const VERSION = '2.0.0RC1-DEV';
/**
* Compares a Doctrine version with the current one.
*
* @param string $version Doctrine version to compare.
* @return int Returns -1 if older, 0 if it is the same, 1 if version
* passed as argument is newer.
*/
public static function compare($version)
{
$currentVersion = str_replace(' ', '', strtolower(self::VERSION));
$version = str_replace(' ', '', $version);
return version_compare($version, $currentVersion);
}
}
+590
Ver Arquivo
@@ -0,0 +1,590 @@
<?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
* 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;
use Doctrine\DBAL\Types\Type,
Doctrine\ORM\Query\QueryException;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
*/
abstract class AbstractQuery
{
/* Hydration mode constants */
/**
* Hydrates an object graph. This is the default behavior.
*/
const HYDRATE_OBJECT = 1;
/**
* Hydrates an array graph.
*/
const HYDRATE_ARRAY = 2;
/**
* Hydrates a flat, rectangular result set with scalar values.
*/
const HYDRATE_SCALAR = 3;
/**
* Hydrates a single scalar value.
*/
const HYDRATE_SINGLE_SCALAR = 4;
/**
* @var array The parameter map of this query.
*/
protected $_params = array();
/**
* @var array The parameter type map of this query.
*/
protected $_paramTypes = array();
/**
* @var ResultSetMapping The user-specified ResultSetMapping to use.
*/
protected $_resultSetMapping;
/**
* @var Doctrine\ORM\EntityManager The entity manager used by this query object.
*/
protected $_em;
/**
* @var array The map of query hints.
*/
protected $_hints = array();
/**
* @var integer The hydration mode.
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* The locally set cache driver used for caching result sets of this query.
*
* @var CacheDriver
*/
protected $_resultCacheDriver;
/**
* Boolean flag for whether or not to cache the results of this query.
*
* @var boolean
*/
protected $_useResultCache;
/**
* @var string The id to store the result cache entry under.
*/
protected $_resultCacheId;
/**
* @var boolean Boolean value that indicates whether or not expire the result cache.
*/
protected $_expireResultCache = false;
/**
* @var int Result Cache lifetime.
*/
protected $_resultCacheTTL;
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
* @param Doctrine\ORM\EntityManager $entityManager
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
}
/**
* Retrieves the associated EntityManager of this Query instance.
*
* @return Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/**
* Frees the resources used by the query object.
*
* Resets Parameters, Parameter Types and Query Hints.
*
* @return void
*/
public function free()
{
$this->_params = array();
$this->_paramTypes = array();
$this->_hints = array();
}
/**
* Get all defined parameters.
*
* @return array The defined query parameters.
*/
public function getParameters()
{
return $this->_params;
}
/**
* Gets a query parameter.
*
* @param mixed $key The key (index or name) of the bound parameter.
* @return mixed The value of the bound parameter.
*/
public function getParameter($key)
{
return isset($this->_params[$key]) ? $this->_params[$key] : null;
}
/**
* Gets the SQL query that corresponds to this query object.
* The returned SQL syntax depends on the connection driver that is used
* by this query object at the time of this method call.
*
* @return string SQL query
*/
abstract public function getSQL();
/**
* Sets a query parameter.
*
* @param string|integer $key The parameter position or name.
* @param mixed $value The parameter value.
* @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.
*/
public function setParameter($key, $value, $type = null)
{
if ($type !== null) {
$this->_paramTypes[$key] = $type;
}
$this->_params[$key] = $value;
return $this;
}
/**
* Sets a collection of query parameters.
*
* @param array $params
* @param array $types
* @return Doctrine\ORM\AbstractQuery This query instance.
*/
public function setParameters(array $params, array $types = array())
{
foreach ($params as $key => $value) {
if (isset($types[$key])) {
$this->setParameter($key, $value, $types[$key]);
} else {
$this->setParameter($key, $value);
}
}
return $this;
}
/**
* Sets the ResultSetMapping that should be used for hydration.
*
* @param ResultSetMapping $rsm
* @return Doctrine\ORM\AbstractQuery
*/
public function setResultSetMapping(Query\ResultSetMapping $rsm)
{
$this->_resultSetMapping = $rsm;
return $this;
}
/**
* Defines a cache driver to be used for caching result sets.
*
* @param Doctrine\Common\Cache\Cache $driver Cache driver
* @return Doctrine\ORM\AbstractQuery
*/
public function setResultCacheDriver($resultCacheDriver = null)
{
if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
throw ORMException::invalidResultCacheDriver();
}
$this->_resultCacheDriver = $resultCacheDriver;
if ($resultCacheDriver) {
$this->_useResultCache = true;
}
return $this;
}
/**
* Returns the cache driver used for caching result sets.
*
* @return Doctrine\Common\Cache\Cache Cache driver
*/
public function getResultCacheDriver()
{
if ($this->_resultCacheDriver) {
return $this->_resultCacheDriver;
} else {
return $this->_em->getConfiguration()->getResultCacheImpl();
}
}
/**
* Set whether or not to cache the results of this query and if so, for
* how long and which ID to use for the cache entry.
*
* @param boolean $bool
* @param integer $timeToLive
* @param string $resultCacheId
* @return This query instance.
*/
public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
{
$this->_useResultCache = $bool;
if ($timeToLive) {
$this->setResultCacheLifetime($timeToLive);
}
if ($resultCacheId) {
$this->_resultCacheId = $resultCacheId;
}
return $this;
}
/**
* 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.
*/
public function setResultCacheLifetime($timeToLive)
{
if ($timeToLive !== null) {
$timeToLive = (int) $timeToLive;
}
$this->_resultCacheTTL = $timeToLive;
return $this;
}
/**
* Retrieves the lifetime of resultset cache.
*
* @return integer
*/
public function getResultCacheLifetime()
{
return $this->_resultCacheTTL;
}
/**
* 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.
*/
public function expireResultCache($expire = true)
{
$this->_expireResultCache = $expire;
return $this;
}
/**
* Retrieves if the resultset cache is active or not.
*
* @return boolean
*/
public function getExpireResultCache()
{
return $this->_expireResultCache;
}
/**
* 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.
*/
public function setHydrationMode($hydrationMode)
{
$this->_hydrationMode = $hydrationMode;
return $this;
}
/**
* Gets the hydration mode currently used by the query.
*
* @return integer
*/
public function getHydrationMode()
{
return $this->_hydrationMode;
}
/**
* Gets the list of results for the query.
*
* Alias for execute(array(), $hydrationMode = HYDRATE_OBJECT).
*
* @return array
*/
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
{
return $this->execute(array(), $hydrationMode);
}
/**
* Gets the array of results for the query.
*
* Alias for execute(array(), HYDRATE_ARRAY).
*
* @return array
*/
public function getArrayResult()
{
return $this->execute(array(), self::HYDRATE_ARRAY);
}
/**
* Gets the scalar results for the query.
*
* Alias for execute(array(), HYDRATE_SCALAR).
*
* @return array
*/
public function getScalarResult()
{
return $this->execute(array(), self::HYDRATE_SCALAR);
}
/**
* Gets the single result of the query.
*
* Enforces the presence as well as the uniqueness of the result.
*
* If the result is not unique, a NonUniqueResultException is thrown.
* If there is no result, a NoResultException is thrown.
*
* @param integer $hydrationMode
* @return mixed
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult($hydrationMode = null)
{
$result = $this->execute(array(), $hydrationMode);
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
throw new NoResultException;
}
if (is_array($result)) {
if (count($result) > 1) {
throw new NonUniqueResultException;
}
return array_shift($result);
}
return $result;
}
/**
* Gets the single scalar result of the query.
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed
* @throws QueryException If the query result is not unique.
*/
public function getSingleScalarResult()
{
return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
}
/**
* Sets a query hint. If the hint name is not recognized, it is silently ignored.
*
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
* @return Doctrine\ORM\AbstractQuery
*/
public function setHint($name, $value)
{
$this->_hints[$name] = $value;
return $this;
}
/**
* Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
*
* @param string $name The name of the hint.
* @return mixed The value of the hint or FALSE, if the hint name is not recognized.
*/
public function getHint($name)
{
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
}
/**
* Executes the query and returns an IterableResult that can be used to incrementally
* iterate over the result.
*
* @param array $params The query parameters.
* @param integer $hydrationMode The hydration mode to use.
* @return IterableResult
*/
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
{
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
$this->_doExecute($params, $hydrationMode), $this->_resultSetMapping, $this->_hints
);
}
/**
* Executes the query.
*
* @param string $params Any additional query parameters.
* @param integer $hydrationMode Processing mode to be used during the hydration process.
* @return mixed
*/
public function execute($params = array(), $hydrationMode = null)
{
// If there are still pending insertions in the UnitOfWork we need to flush
// in order to guarantee a correct result.
//TODO: Think this over. Its tricky. Not doing this can lead to strange results
// potentially, but doing it could result in endless loops when querying during
// a flush, i.e. inside an event listener.
if ($this->_em->getUnitOfWork()->hasPendingInsertions()) {
$this->_em->flush();
}
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
}
if ($params) {
$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);
if ($cached === false) {
// Cache miss.
$stmt = $this->_doExecute();
$result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
$cacheDriver->save($id, $result, $this->_resultCacheTTL);
return $result;
} else {
// Cache hit.
return $cached;
}
}
$stmt = $this->_doExecute();
if (is_numeric($stmt)) {
return $stmt;
}
return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
}
/**
* Set the result cache id to use to store the result set cache entry.
* If this is not explicitely set by the developer then a hash is automatically
* generated for you.
*
* @param string $id
* @return Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheId($id)
{
$this->_resultCacheId = $id;
return $this;
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
* automatically generated for you.
*
* @return string $id
*/
protected function _getResultCacheId()
{
if ($this->_resultCacheId) {
return $this->_resultCacheId;
} else {
$sql = $this->getSql();
ksort($this->_hints);
return md5(implode(";", (array)$sql) . var_export($this->_params, true) .
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode);
}
}
/**
* Executes the query and returns a the resulting Statement object.
*
* @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
*/
abstract protected function _doExecute();
/**
* Cleanup Query resource when clone is called.
*
* @return void
*/
public function __clone()
{
$this->_params = array();
$this->_paramTypes = array();
$this->_hints = array();
}
}
+465
Ver Arquivo
@@ -0,0 +1,465 @@
<?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;
use Doctrine\Common\Cache\Cache,
Doctrine\ORM\Mapping\Driver\Driver;
/**
* Configuration container for all configuration options of Doctrine.
* It combines all configuration options from DBAL & ORM.
*
* @since 2.0
* @internal When adding a new configuration option just write a getter/setter pair.
* @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 Configuration extends \Doctrine\DBAL\Configuration
{
/**
* Sets the directory where Doctrine generates any necessary proxy class files.
*
* @param string $dir
*/
public function setProxyDir($dir)
{
$this->_attributes['proxyDir'] = $dir;
}
/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*
* @return string
*/
public function getProxyDir()
{
return isset($this->_attributes['proxyDir']) ?
$this->_attributes['proxyDir'] : null;
}
/**
* Gets a boolean flag that indicates whether proxy classes should always be regenerated
* during each script execution.
*
* @return boolean
*/
public function getAutoGenerateProxyClasses()
{
return isset($this->_attributes['autoGenerateProxyClasses']) ?
$this->_attributes['autoGenerateProxyClasses'] : true;
}
/**
* Sets a boolean flag that indicates whether proxy classes should always be regenerated
* during each script execution.
*
* @param boolean $bool
*/
public function setAutoGenerateProxyClasses($bool)
{
$this->_attributes['autoGenerateProxyClasses'] = $bool;
}
/**
* Gets the namespace where proxy classes reside.
*
* @return string
*/
public function getProxyNamespace()
{
return isset($this->_attributes['proxyNamespace']) ?
$this->_attributes['proxyNamespace'] : null;
}
/**
* Sets the namespace where proxy classes reside.
*
* @param string $ns
*/
public function setProxyNamespace($ns)
{
$this->_attributes['proxyNamespace'] = $ns;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
* @param Driver $driverImpl
* @todo Force parameter to be a Closure to ensure lazy evaluation
* (as soon as a metadata cache is in effect, the driver never needs to initialize).
*/
public function setMetadataDriverImpl(Driver $driverImpl)
{
$this->_attributes['metadataDriverImpl'] = $driverImpl;
}
/**
* 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);
}
/**
* Adds a namespace under a certain alias.
*
* @param string $alias
* @param string $namespace
*/
public function addEntityNamespace($alias, $namespace)
{
$this->_attributes['entityNamespaces'][$alias] = $namespace;
}
/**
* Resolves a registered namespace alias to the full namespace.
*
* @param string $entityNamespaceAlias
* @return string
* @throws MappingException
*/
public function getEntityNamespace($entityNamespaceAlias)
{
if ( ! isset($this->_attributes['entityNamespaces'][$entityNamespaceAlias])) {
throw ORMException::unknownEntityNamespace($entityNamespaceAlias);
}
return trim($this->_attributes['entityNamespaces'][$entityNamespaceAlias], '\\');
}
/**
* Set the entity alias map
*
* @param array $entityAliasMap
* @return void
*/
public function setEntityNamespaces(array $entityNamespaces)
{
$this->_attributes['entityNamespaces'] = $entityNamespaces;
}
/**
* Gets the cache driver implementation that is used for the mapping metadata.
*
* @throws ORMException
* @return Mapping\Driver\Driver
*/
public function getMetadataDriverImpl()
{
return isset($this->_attributes['metadataDriverImpl']) ?
$this->_attributes['metadataDriverImpl'] : null;
}
/**
* Gets the cache driver implementation that is used for query result caching.
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getResultCacheImpl()
{
return isset($this->_attributes['resultCacheImpl']) ?
$this->_attributes['resultCacheImpl'] : null;
}
/**
* Sets the cache driver implementation that is used for query result caching.
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setResultCacheImpl(Cache $cacheImpl)
{
$this->_attributes['resultCacheImpl'] = $cacheImpl;
}
/**
* Gets the cache driver implementation that is used for the query cache (SQL cache).
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getQueryCacheImpl()
{
return isset($this->_attributes['queryCacheImpl']) ?
$this->_attributes['queryCacheImpl'] : null;
}
/**
* Sets the cache driver implementation that is used for the query cache (SQL cache).
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setQueryCacheImpl(Cache $cacheImpl)
{
$this->_attributes['queryCacheImpl'] = $cacheImpl;
}
/**
* Gets the cache driver implementation that is used for metadata caching.
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getMetadataCacheImpl()
{
return isset($this->_attributes['metadataCacheImpl']) ?
$this->_attributes['metadataCacheImpl'] : null;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setMetadataCacheImpl(Cache $cacheImpl)
{
$this->_attributes['metadataCacheImpl'] = $cacheImpl;
}
/**
* Adds a named DQL query to the configuration.
*
* @param string $name The name of the query.
* @param string $dql The DQL query string.
*/
public function addNamedQuery($name, $dql)
{
$this->_attributes['namedQueries'][$name] = $dql;
}
/**
* Gets a previously registered named DQL query.
*
* @param string $name The name of the query.
* @return string The DQL query.
*/
public function getNamedQuery($name)
{
if ( ! isset($this->_attributes['namedQueries'][$name])) {
throw ORMException::namedQueryNotFound($name);
}
return $this->_attributes['namedQueries'][$name];
}
/**
* Adds a named native query to the configuration.
*
* @param string $name The name of the query.
* @param string $sql The native SQL query string.
* @param ResultSetMapping $rsm The ResultSetMapping used for the results of the SQL query.
*/
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
{
$this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm);
}
/**
* Gets the components of a previously registered named native query.
*
* @param string $name The name of the query.
* @return array A tuple with the first element being the SQL string and the second
* element being the ResultSetMapping.
*/
public function getNamedNativeQuery($name)
{
if ( ! isset($this->_attributes['namedNativeQueries'][$name])) {
throw ORMException::namedNativeQueryNotFound($name);
}
return $this->_attributes['namedNativeQueries'][$name];
}
/**
* Ensures that this Configuration instance contains settings that are
* suitable for a production environment.
*
* @throws ORMException If a configuration setting has a value that is not
* suitable for a production environment.
*/
public function ensureProductionSettings()
{
if ( !$this->getQueryCacheImpl()) {
throw ORMException::queryCacheNotConfigured();
}
if ( !$this->getMetadataCacheImpl()) {
throw ORMException::metadataCacheNotConfigured();
}
if ($this->getAutoGenerateProxyClasses()) {
throw ORMException::proxyClassesAlwaysRegenerating();
}
}
/**
* Registers a custom DQL function that produces a string value.
* Such a function can then be used in any DQL statement in any place where string
* functions are allowed.
*
* DQL function names are case-insensitive.
*
* @param string $name
* @param string $className
*/
public function addCustomStringFunction($name, $className)
{
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
}
/**
* Gets the implementation class name of a registered custom string DQL function.
*
* @param string $name
* @return string
*/
public function getCustomStringFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customStringFunctions'][$name]) ?
$this->_attributes['customStringFunctions'][$name] : null;
}
/**
* Sets a map of custom DQL string functions.
*
* Keys must be function names and values the FQCN of the implementing class.
* The function names will be case-insensitive in DQL.
*
* Any previously added string functions are discarded.
*
* @param array $functions The map of custom DQL string functions.
*/
public function setCustomStringFunctions(array $functions)
{
$this->_attributes['customStringFunctions'] = array_change_key_case($functions);
}
/**
* Registers a custom DQL function that produces a numeric value.
* Such a function can then be used in any DQL statement in any place where numeric
* functions are allowed.
*
* DQL function names are case-insensitive.
*
* @param string $name
* @param string $className
*/
public function addCustomNumericFunction($name, $className)
{
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
}
/**
* Gets the implementation class name of a registered custom numeric DQL function.
*
* @param string $name
* @return string
*/
public function getCustomNumericFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customNumericFunctions'][$name]) ?
$this->_attributes['customNumericFunctions'][$name] : null;
}
/**
* Sets a map of custom DQL numeric functions.
*
* Keys must be function names and values the FQCN of the implementing class.
* The function names will be case-insensitive in DQL.
*
* Any previously added numeric functions are discarded.
*
* @param array $functions The map of custom DQL numeric functions.
*/
public function setCustomNumericFunctions(array $functions)
{
$this->_attributes['customNumericFunctions'] = array_change_key_case($functions);
}
/**
* Registers a custom DQL function that produces a date/time value.
* Such a function can then be used in any DQL statement in any place where date/time
* functions are allowed.
*
* DQL function names are case-insensitive.
*
* @param string $name
* @param string $className
*/
public function addCustomDatetimeFunction($name, $className)
{
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
}
/**
* Gets the implementation class name of a registered custom date/time DQL function.
*
* @param string $name
* @return string
*/
public function getCustomDatetimeFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customDatetimeFunctions'][$name]) ?
$this->_attributes['customDatetimeFunctions'][$name] : null;
}
/**
* Sets a map of custom DQL date/time functions.
*
* Keys must be function names and values the FQCN of the implementing class.
* The function names will be case-insensitive in DQL.
*
* Any previously added date/time functions are discarded.
*
* @param array $functions The map of custom DQL date/time functions.
*/
public function setCustomDatetimeFunctions(array $functions)
{
$this->_attributes['customDatetimeFunctions'] = array_change_key_case($functions);
}
/**
* Get the hydrator class for the given hydration mode name.
*
* @param string $modeName The hydration mode name.
* @return string $hydrator The hydrator class name.
*/
public function getCustomHydrationMode($modeName)
{
return isset($this->_attributes['customHydrationModes'][$modeName]) ?
$this->_attributes['customHydrationModes'][$modeName] : null;
}
/**
* Add a custom hydration mode.
*
* @param string $modeName The hydration mode name.
* @param string $hydrator The hydrator class name.
*/
public function addCustomHydrationMode($modeName, $hydrator)
{
$this->_attributes['customHydrationModes'][$modeName] = $hydrator;
}
}
+715
Ver Arquivo
@@ -0,0 +1,715 @@
<?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;
use Closure, Exception,
Doctrine\Common\EventManager,
Doctrine\DBAL\Connection,
Doctrine\DBAL\LockMode,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\ClassMetadataFactory,
Doctrine\ORM\Query\ResultSetMapping,
Doctrine\ORM\Proxy\ProxyFactory;
/**
* The EntityManager is the central access point to ORM functionality.
*
* @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 EntityManager
{
/**
* The used Configuration.
*
* @var Doctrine\ORM\Configuration
*/
private $config;
/**
* The database connection used by the EntityManager.
*
* @var Doctrine\DBAL\Connection
*/
private $conn;
/**
* The metadata factory, used to retrieve the ORM metadata of entity classes.
*
* @var Doctrine\ORM\Mapping\ClassMetadataFactory
*/
private $metadataFactory;
/**
* The EntityRepository instances.
*
* @var array
*/
private $repositories = array();
/**
* The UnitOfWork used to coordinate object-level transactions.
*
* @var Doctrine\ORM\UnitOfWork
*/
private $unitOfWork;
/**
* The event manager that is the central point of the event system.
*
* @var Doctrine\Common\EventManager
*/
private $eventManager;
/**
* The maintained (cached) hydrators. One instance per type.
*
* @var array
*/
private $hydrators = array();
/**
* The proxy factory used to create dynamic proxies.
*
* @var Doctrine\ORM\Proxy\ProxyFactory
*/
private $proxyFactory;
/**
* @var ExpressionBuilder The expression builder instance used to generate query expressions.
*/
private $expressionBuilder;
/**
* Whether the EntityManager is closed or not.
*/
private $closed = false;
/**
* 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
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
{
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$this->metadataFactory = new ClassMetadataFactory($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this,
$config->getProxyDir(),
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses());
}
/**
* Gets the database connection object used by the EntityManager.
*
* @return Doctrine\DBAL\Connection
*/
public function getConnection()
{
return $this->conn;
}
/**
* Gets the metadata factory used to gather the metadata of classes.
*
* @return Doctrine\ORM\Mapping\ClassMetadataFactory
*/
public function getMetadataFactory()
{
return $this->metadataFactory;
}
/**
* Gets an ExpressionBuilder used for object-oriented construction of query expressions.
*
* Example:
*
* <code>
* $qb = $em->createQueryBuilder();
* $expr = $em->getExpressionBuilder();
* $qb->select('u')->from('User', 'u')
* ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
* </code>
*
* @return ExpressionBuilder
*/
public function getExpressionBuilder()
{
if ($this->expressionBuilder === null) {
$this->expressionBuilder = new Query\Expr;
}
return $this->expressionBuilder;
}
/**
* Starts a transaction on the underlying database connection.
*
* @deprecated Use {@link getConnection}.beginTransaction().
*/
public function beginTransaction()
{
$this->conn->beginTransaction();
}
/**
* Executes a function in a transaction.
*
* The function gets passed this EntityManager instance as an (optional) parameter.
*
* {@link flush} is invoked prior to transaction commit.
*
* If an exception occurs during execution of the function or flushing or transaction commit,
* the transaction is rolled back, the EntityManager closed and the exception re-thrown.
*
* @param Closure $func The function to execute transactionally.
*/
public function transactional(Closure $func)
{
$this->conn->beginTransaction();
try {
$func($this);
$this->flush();
$this->conn->commit();
} catch (Exception $e) {
$this->close();
$this->conn->rollback();
throw $e;
}
}
/**
* Commits a transaction on the underlying database connection.
*
* @deprecated Use {@link getConnection}.commit().
*/
public function commit()
{
$this->conn->commit();
}
/**
* Performs a rollback on the underlying database connection.
*
* @deprecated Use {@link getConnection}.rollback().
*/
public function rollback()
{
$this->conn->rollback();
}
/**
* Returns the ORM metadata descriptor for a class.
*
* 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
* @internal Performance-sensitive method.
*/
public function getClassMetadata($className)
{
return $this->metadataFactory->getMetadataFor($className);
}
/**
* Creates a new Query object.
*
* @param string The DQL string.
* @return Doctrine\ORM\Query
*/
public function createQuery($dql = "")
{
$query = new Query($this);
if ( ! empty($dql)) {
$query->setDql($dql);
}
return $query;
}
/**
* Creates a Query from a named query.
*
* @param string $name
* @return Doctrine\ORM\Query
*/
public function createNamedQuery($name)
{
return $this->createQuery($this->config->getNamedQuery($name));
}
/**
* Creates a native SQL query.
*
* @param string $sql
* @param ResultSetMapping $rsm The ResultSetMapping to use.
* @return NativeQuery
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
{
$query = new NativeQuery($this);
$query->setSql($sql);
$query->setResultSetMapping($rsm);
return $query;
}
/**
* Creates a NativeQuery from a named native query.
*
* @param string $name
* @return Doctrine\ORM\NativeQuery
*/
public function createNamedNativeQuery($name)
{
list($sql, $rsm) = $this->config->getNamedNativeQuery($name);
return $this->createNativeQuery($sql, $rsm);
}
/**
* Create a QueryBuilder instance
*
* @return QueryBuilder $qb
*/
public function createQueryBuilder()
{
return new QueryBuilder($this);
}
/**
* Flushes all changes to objects that have been queued up to now to the database.
* 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
* makes use of optimistic locking fails.
*/
public function flush()
{
$this->errorIfClosed();
$this->unitOfWork->commit();
}
/**
* Finds an Entity by its identifier.
*
* This is just a convenient shortcut for getRepository($entityName)->find($id).
*
* @param string $entityName
* @param mixed $identifier
* @param int $lockMode
* @param int $lockVersion
* @return object
*/
public function find($entityName, $identifier, $lockMode = LockMode::NONE, $lockVersion = null)
{
return $this->getRepository($entityName)->find($identifier, $lockMode, $lockVersion);
}
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
* @return object The entity reference.
*/
public function getReference($entityName, $identifier)
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
// Check identity map first, if its already in there just return it.
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity;
}
if ($class->subClasses) {
$entity = $this->find($entityName, $identifier);
} else {
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $this->proxyFactory->getProxy($class->name, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
}
return $entity;
}
/**
* Gets a partial reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* The returned reference may be a partial object if the entity is not yet loaded/managed.
* If it is a partial object it will not initialize the rest of the entity state on access.
* Thus you can only ever safely access the identifier of an entity obtained through
* this method.
*
* The use-cases for partial references involve maintaining bidirectional associations
* without loading one side of the association or to update an entity without loading it.
* Note, however, that in the latter case the original (persistent) entity data will
* never be visible to the application (especially not event listeners) as it will
* never be loaded in the first place.
*
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
* @return object The (partial) entity reference.
*/
public function getPartialReference($entityName, $identifier)
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
// Check identity map first, if its already in there just return it.
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity;
}
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
return $entity;
}
/**
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*
* @param string $entityName
*/
public function clear($entityName = null)
{
if ($entityName === null) {
$this->unitOfWork->clear();
} else {
//TODO
throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
}
}
/**
* Closes the EntityManager. All entities that are currently managed
* by this EntityManager become detached. The EntityManager may no longer
* be used after it is closed.
*/
public function close()
{
$this->clear();
$this->closed = true;
}
/**
* Tells the EntityManager to make an instance managed and persistent.
*
* 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.
*
* @param object $object The instance to make managed and persistent.
*/
public function persist($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
$this->unitOfWork->persist($entity);
}
/**
* Removes an entity instance.
*
* A removed entity will be removed from the database at or before transaction commit
* or as a result of the flush operation.
*
* @param object $entity The entity instance to remove.
*/
public function remove($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
$this->unitOfWork->remove($entity);
}
/**
* Refreshes the persistent state of an entity from the database,
* overriding any local changes that have not yet been persisted.
*
* @param object $entity The entity to refresh.
*/
public function refresh($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
$this->unitOfWork->refresh($entity);
}
/**
* Detaches an entity from the EntityManager, causing a managed entity to
* become detached. Unflushed changes made to the entity if any
* (including removal of the entity), will not be synchronized to the database.
* Entities which previously referenced the detached entity will continue to
* reference it.
*
* @param object $entity The entity to detach.
*/
public function detach($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->unitOfWork->detach($entity);
}
/**
* Merges the state of a detached entity into the persistence context
* of this EntityManager and returns the managed copy of the entity.
* The entity passed to merge will not become associated/managed with this EntityManager.
*
* @param object $entity The detached entity to merge into the persistence context.
* @return object The managed copy of the entity.
*/
public function merge($entity)
{
if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity));
}
$this->errorIfClosed();
return $this->unitOfWork->merge($entity);
}
/**
* Creates a copy of the given entity. Can create a shallow or a deep copy.
*
* @param object $entity The entity to copy.
* @return object The new entity.
* @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e:
* Fatal error: Maximum function nesting level of '100' reached, aborting!
*/
public function copy($entity, $deep = false)
{
throw new \BadMethodCallException("Not implemented.");
}
/**
* Acquire a lock on the given entity.
*
* @param object $entity
* @param int $lockMode
* @param int $lockVersion
* @throws OptimisticLockException
* @throws PessimisticLockException
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
$this->unitOfWork->lock($entity, $lockMode, $lockVersion);
}
/**
* Gets the repository for an entity class.
*
* @param string $entityName The name of the entity.
* @return EntityRepository The repository class.
*/
public function getRepository($entityName)
{
$entityName = ltrim($entityName, '\\');
if (isset($this->repositories[$entityName])) {
return $this->repositories[$entityName];
}
$metadata = $this->getClassMetadata($entityName);
$customRepositoryClassName = $metadata->customRepositoryClassName;
if ($customRepositoryClassName !== null) {
$repository = new $customRepositoryClassName($this, $metadata);
} else {
$repository = new EntityRepository($this, $metadata);
}
$this->repositories[$entityName] = $repository;
return $repository;
}
/**
* Determines whether an entity instance is managed in this EntityManager.
*
* @param object $entity
* @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
*/
public function contains($entity)
{
return $this->unitOfWork->isScheduledForInsert($entity) ||
$this->unitOfWork->isInIdentityMap($entity) &&
! $this->unitOfWork->isScheduledForDelete($entity);
}
/**
* Gets the EventManager used by the EntityManager.
*
* @return Doctrine\Common\EventManager
*/
public function getEventManager()
{
return $this->eventManager;
}
/**
* Gets the Configuration used by the EntityManager.
*
* @return Doctrine\ORM\Configuration
*/
public function getConfiguration()
{
return $this->config;
}
/**
* Throws an exception if the EntityManager is closed or currently not active.
*
* @throws ORMException If the EntityManager is closed.
*/
private function errorIfClosed()
{
if ($this->closed) {
throw ORMException::entityManagerClosed();
}
}
/**
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*
* @return Doctrine\ORM\UnitOfWork
*/
public function getUnitOfWork()
{
return $this->unitOfWork;
}
/**
* Gets a hydrator for the given hydration mode.
*
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @param int $hydrationMode
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function getHydrator($hydrationMode)
{
if ( ! isset($this->hydrators[$hydrationMode])) {
$this->hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
}
return $this->hydrators[$hydrationMode];
}
/**
* Create a new instance for the given hydration mode.
*
* @param int $hydrationMode
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function newHydrator($hydrationMode)
{
switch ($hydrationMode) {
case Query::HYDRATE_OBJECT:
$hydrator = new Internal\Hydration\ObjectHydrator($this);
break;
case Query::HYDRATE_ARRAY:
$hydrator = new Internal\Hydration\ArrayHydrator($this);
break;
case Query::HYDRATE_SCALAR:
$hydrator = new Internal\Hydration\ScalarHydrator($this);
break;
case Query::HYDRATE_SINGLE_SCALAR:
$hydrator = new Internal\Hydration\SingleScalarHydrator($this);
break;
default:
if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
$hydrator = new $class($this);
break;
}
throw ORMException::invalidHydrationMode($hydrationMode);
}
return $hydrator;
}
/**
* Gets the proxy factory used by the EntityManager to create entity proxies.
*
* @return ProxyFactory
*/
public function getProxyFactory()
{
return $this->proxyFactory;
}
/**
* Factory method to create EntityManager instances.
*
* @param mixed $conn An array with the connection parameters or an existing
* Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
* @return EntityManager The created EntityManager.
*/
public static function create($conn, Configuration $config, EventManager $eventManager = null)
{
if (!$config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager()));
} else if ($conn instanceof Connection) {
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
} else {
throw new \InvalidArgumentException("Invalid argument: " . $conn);
}
return new EntityManager($conn, $config, $conn->getEventManager());
}
}
@@ -0,0 +1,34 @@
<?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;
/**
* Exception thrown when a Proxy fails to retrieve an Entity result.
*
* @author robo
* @since 2.0
*/
class EntityNotFoundException extends ORMException
{
public function __construct()
{
parent::__construct('Entity was not found.');
}
}
@@ -0,0 +1,225 @@
<?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;
use Doctrine\DBAL\LockMode;
/**
* An EntityRepository serves as a repository for entities with generic as well as
* business specific methods for retrieving entities.
*
* This class is designed for inheritance and users can subclass this class to
* write their own repositories with business-specific methods to locate entities.
*
* @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 EntityRepository
{
/**
* @var string
*/
protected $_entityName;
/**
* @var EntityManager
*/
protected $_em;
/**
* @var Doctrine\ORM\Mapping\ClassMetadata
*/
protected $_class;
/**
* Initializes a new <tt>EntityRepository</tt>.
*
* @param EntityManager $em The EntityManager to use.
* @param ClassMetadata $classMetadata The class descriptor.
*/
public function __construct($em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
/**
* Create a new QueryBuilder instance that is prepopulated for this entity name
*
* @param string $alias
* @return QueryBuilder $qb
*/
public function createQueryBuilder($alias)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
}
/**
* Clears the repository, causing all managed entities to become detached.
*/
public function clear()
{
$this->_em->clear($this->_class->rootEntityName);
}
/**
* Finds an entity by its primary key / identifier.
*
* @param $id The identifier.
* @param int $lockMode
* @param int $lockVersion
* @return object The entity.
*/
public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
{
// Check identity map first
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
if ($lockMode != LockMode::NONE) {
$this->_em->lock($entity, $lockMode, $lockVersion);
}
return $entity; // Hit!
}
if ( ! is_array($id) || count($id) <= 1) {
// @todo FIXME: Not correct. Relies on specific order.
$value = is_array($id) ? array_values($id) : array($id);
$id = array_combine($this->_class->identifier, $value);
}
if ($lockMode == LockMode::NONE) {
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
} else if ($lockMode == LockMode::OPTIMISTIC) {
if (!$this->_class->isVersioned) {
throw OptimisticLockException::notVersioned($this->_entityName);
}
$entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
$this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion);
return $entity;
} else {
if (!$this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode);
}
}
/**
* Finds all entities in the repository.
*
* @return array The entities.
*/
public function findAll()
{
return $this->findBy(array());
}
/**
* Finds entities by a set of criteria.
*
* @param array $criteria
* @return array
*/
public function findBy(array $criteria)
{
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria);
}
/**
* Finds a single entity by a set of criteria.
*
* @param array $criteria
* @return object
*/
public function findOneBy(array $criteria)
{
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria);
}
/**
* Adds support for magic finders.
*
* @return array|object The found entity/entities.
* @throws BadMethodCallException If the method called is an invalid find* method
* or no find* method at all and therefore an invalid
* method call.
*/
public function __call($method, $arguments)
{
if (substr($method, 0, 6) == 'findBy') {
$by = substr($method, 6, strlen($method));
$method = 'findBy';
} else if (substr($method, 0, 9) == 'findOneBy') {
$by = substr($method, 9, strlen($method));
$method = 'findOneBy';
} else {
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy or findOneBy!"
);
}
if ( !isset($arguments[0])) {
// we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
throw ORMException::findByRequiresParameter($method.$by);
}
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
return $this->$method(array($fieldName => $arguments[0]));
} else {
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
}
}
/**
* @return string
*/
protected function getEntityName()
{
return $this->_entityName;
}
/**
* @return EntityManager
*/
protected function getEntityManager()
{
return $this->_em;
}
/**
* @return Mapping\ClassMetadata
*/
protected function getClassMetadata()
{
return $this->_class;
}
}
@@ -0,0 +1,29 @@
K 25
svn:wc:ra_dav:version-url
V 60
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Event
END
OnFlushEventArgs.php
K 25
svn:wc:ra_dav:version-url
V 81
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Event/OnFlushEventArgs.php
END
LifecycleEventArgs.php
K 25
svn:wc:ra_dav:version-url
V 83
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Event/LifecycleEventArgs.php
END
LoadClassMetadataEventArgs.php
K 25
svn:wc:ra_dav:version-url
V 91
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php
END
PreUpdateEventArgs.php
K 25
svn:wc:ra_dav:version-url
V 83
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php
END
+164
Ver Arquivo
@@ -0,0 +1,164 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine/ORM/Event
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
OnFlushEventArgs.php
file
2010-10-22T02:39:43.993323Z
399d419cbfefe77070a704559af33280
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2113
LifecycleEventArgs.php
file
2010-10-22T02:39:43.993323Z
0d0e130c77fb4b674a57cd31d7d84aa6
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1742
LoadClassMetadataEventArgs.php
file
2010-10-22T02:39:43.993323Z
810844e2ae0841a5a19544d66e4f1b22
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
525
PreUpdateEventArgs.php
file
2010-10-22T02:39:43.993323Z
5726ec58fa2e7f21552774e923da65e7
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2166
@@ -0,0 +1,60 @@
<?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;
/**
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
* of entities.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class LifecycleEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var EntityManager
*/
private $_em;
/**
* @var object
*/
private $_entity;
public function __construct($entity, $em)
{
$this->_entity = $entity;
$this->_em = $em;
}
public function getEntity()
{
return $this->_entity;
}
/**
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
}
@@ -0,0 +1,27 @@
<?php
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
/**
* Class that holds event arguments for a loadMetadata event.
*
* @author Jonathan H. Wage <jonwage@gmail.com>
* @since 2.0
*/
class LoadClassMetadataEventArgs extends EventArgs
{
private $_classMetadata;
public function __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata)
{
$this->_classMetadata = $classMetadata;
}
public function getClassMetadata()
{
return $this->_classMetadata;
}
}
@@ -0,0 +1,78 @@
<?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\Event;
/**
* Provides event arguments for the preFlush 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 OnFlushEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var EntityManager
*/
private $_em;
//private $_entitiesToPersist = array();
//private $_entitiesToRemove = array();
public function __construct($em)
{
$this->_em = $em;
}
/**
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/*
public function addEntityToPersist($entity)
{
}
public function addEntityToRemove($entity)
{
}
public function addEntityToUpdate($entity)
{
}
public function getEntitiesToPersist()
{
return $this->_entitiesToPersist;
}
*/
}
@@ -0,0 +1,98 @@
<?php
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs,
Doctrine\ORM\EntityManager;
/**
* Class that holds event arguments for a preInsert/preUpdate event.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class PreUpdateEventArgs extends LifecycleEventArgs
{
/**
* @var array
*/
private $_entityChangeSet;
/**
*
* @param object $entity
* @param EntityManager $em
* @param array $changeSet
*/
public function __construct($entity, $em, array &$changeSet)
{
parent::__construct($entity, $em);
$this->_entityChangeSet = &$changeSet;
}
public function getEntityChangeSet()
{
return $this->_entityChangeSet;
}
/**
* Field has a changeset?
*
* @return bool
*/
public function hasChangedField($field)
{
return isset($this->_entityChangeSet[$field]);
}
/**
* Get the old value of the changeset of the changed field.
*
* @param string $field
* @return mixed
*/
public function getOldValue($field)
{
$this->_assertValidField($field);
return $this->_entityChangeSet[$field][0];
}
/**
* Get the new value of the changeset of the changed field.
*
* @param string $field
* @return mixed
*/
public function getNewValue($field)
{
$this->_assertValidField($field);
return $this->_entityChangeSet[$field][1];
}
/**
* Set the new value of this field.
*
* @param string $field
* @param mixed $value
*/
public function setNewValue($field, $value)
{
$this->_assertValidField($field);
$this->_entityChangeSet[$field][1] = $value;
}
private function _assertValidField($field)
{
if (!isset($this->_entityChangeSet[$field])) {
throw new \InvalidArgumentException(
"Field '".$field."' is not a valid field of the entity ".
"'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs."
);
}
}
}
@@ -0,0 +1,60 @@
<?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;
/**
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
* of entities.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class LifecycleEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var EntityManager
*/
private $_em;
/**
* @var object
*/
private $_entity;
public function __construct($entity, $em)
{
$this->_entity = $entity;
$this->_em = $em;
}
public function getEntity()
{
return $this->_entity;
}
/**
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
}
@@ -0,0 +1,27 @@
<?php
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
/**
* Class that holds event arguments for a loadMetadata event.
*
* @author Jonathan H. Wage <jonwage@gmail.com>
* @since 2.0
*/
class LoadClassMetadataEventArgs extends EventArgs
{
private $_classMetadata;
public function __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata)
{
$this->_classMetadata = $classMetadata;
}
public function getClassMetadata()
{
return $this->_classMetadata;
}
}
@@ -0,0 +1,78 @@
<?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\Event;
/**
* Provides event arguments for the preFlush 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 OnFlushEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var EntityManager
*/
private $_em;
//private $_entitiesToPersist = array();
//private $_entitiesToRemove = array();
public function __construct($em)
{
$this->_em = $em;
}
/**
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/*
public function addEntityToPersist($entity)
{
}
public function addEntityToRemove($entity)
{
}
public function addEntityToUpdate($entity)
{
}
public function getEntitiesToPersist()
{
return $this->_entitiesToPersist;
}
*/
}
@@ -0,0 +1,98 @@
<?php
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs,
Doctrine\ORM\EntityManager;
/**
* Class that holds event arguments for a preInsert/preUpdate event.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class PreUpdateEventArgs extends LifecycleEventArgs
{
/**
* @var array
*/
private $_entityChangeSet;
/**
*
* @param object $entity
* @param EntityManager $em
* @param array $changeSet
*/
public function __construct($entity, $em, array &$changeSet)
{
parent::__construct($entity, $em);
$this->_entityChangeSet = &$changeSet;
}
public function getEntityChangeSet()
{
return $this->_entityChangeSet;
}
/**
* Field has a changeset?
*
* @return bool
*/
public function hasChangedField($field)
{
return isset($this->_entityChangeSet[$field]);
}
/**
* Get the old value of the changeset of the changed field.
*
* @param string $field
* @return mixed
*/
public function getOldValue($field)
{
$this->_assertValidField($field);
return $this->_entityChangeSet[$field][0];
}
/**
* Get the new value of the changeset of the changed field.
*
* @param string $field
* @return mixed
*/
public function getNewValue($field)
{
$this->_assertValidField($field);
return $this->_entityChangeSet[$field][1];
}
/**
* Set the new value of this field.
*
* @param string $field
* @param mixed $value
*/
public function setNewValue($field, $value)
{
$this->_assertValidField($field);
$this->_entityChangeSet[$field][1] = $value;
}
private function _assertValidField($field)
{
if (!isset($this->_entityChangeSet[$field])) {
throw new \InvalidArgumentException(
"Field '".$field."' is not a valid field of the entity ".
"'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs."
);
}
}
}
+122
Ver Arquivo
@@ -0,0 +1,122 @@
<?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;
/**
* Container for all ORM events.
*
* This class cannot be instantiated.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
final class Events
{
private function __construct() {}
/**
* The preRemove event occurs for a given entity before the respective
* EntityManager remove operation for that entity is executed.
*
* This is an entity lifecycle event.
*
* @var string
*/
const preRemove = 'preRemove';
/**
* The postRemove event occurs for an entity after the entity has
* been deleted. It will be invoked after the database delete operations.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postRemove = 'postRemove';
/**
* The prePersist event occurs for a given entity before the respective
* EntityManager persist operation for that entity is executed.
*
* This is an entity lifecycle event.
*
* @var string
*/
const prePersist = 'prePersist';
/**
* The postPersist event occurs for an entity after the entity has
* been made persistent. It will be invoked after the database insert operations.
* Generated primary key values are available in the postPersist event.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postPersist = 'postPersist';
/**
* The preUpdate event occurs before the database update operations to
* entity data.
*
* This is an entity lifecycle event.
*
* @var string
*/
const preUpdate = 'preUpdate';
/**
* The postUpdate event occurs after the database update operations to
* entity data.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postUpdate = 'postUpdate';
/**
* The postLoad event occurs for an entity after the entity has been loaded
* into the current EntityManager from the database or after the refresh operation
* has been applied to it.
*
* Note that the postLoad event occurs for an entity before any associations have been
* initialized. Therefore it is not safe to access associations in a postLoad callback
* or event handler.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postLoad = 'postLoad';
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (annotations/xml/yaml).
*
* @var string
*/
const loadClassMetadata = 'loadClassMetadata';
/**
* The onFlush event occurs when the EntityManager#flush() operation is invoked,
* after any changes to managed entities have been determined but before any
* actual database operations are executed. The event is only raised if there is
* actually something to do for the underlying UnitOfWork. If nothing needs to be done,
* the onFlush event is not raised.
*
* @var string
*/
const onFlush = 'onFlush';
}
@@ -0,0 +1,35 @@
K 25
svn:wc:ra_dav:version-url
V 57
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Id
END
TableGenerator.php
K 25
svn:wc:ra_dav:version-url
V 76
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Id/TableGenerator.php
END
SequenceGenerator.php
K 25
svn:wc:ra_dav:version-url
V 79
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Id/SequenceGenerator.php
END
IdentityGenerator.php
K 25
svn:wc:ra_dav:version-url
V 79
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Id/IdentityGenerator.php
END
AssignedGenerator.php
K 25
svn:wc:ra_dav:version-url
V 79
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Id/AssignedGenerator.php
END
AbstractIdGenerator.php
K 25
svn:wc:ra_dav:version-url
V 81
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Id/AbstractIdGenerator.php
END
+198
Ver Arquivo
@@ -0,0 +1,198 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine/ORM/Id
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
TableGenerator.php
file
2010-10-22T02:39:44.063321Z
ce3e39a253194e50859e403954d42efd
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
3295
SequenceGenerator.php
file
2010-10-22T02:39:44.063321Z
d47f79f138a12fea9514c418de1a23ed
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
3330
IdentityGenerator.php
file
2010-10-22T02:39:44.063321Z
7f02e69f2b5e95505703035ffe3bd58b
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2091
AssignedGenerator.php
file
2010-10-22T02:39:44.043321Z
19b245f5ae46005f9cbda01de3ffa993
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2588
AbstractIdGenerator.php
file
2010-10-22T02:39:44.043321Z
7f51c39a4febb70181fc6652213a3421
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1728
@@ -0,0 +1,48 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
abstract class AbstractIdGenerator
{
/**
* Generates an identifier for an entity.
*
* @param Doctrine\ORM\Entity $entity
* @return mixed
*/
abstract public function generate(EntityManager $em, $entity);
/**
* Gets whether this generator is a post-insert generator which means that
* {@link generate()} must be called after the entity has been inserted
* into the database.
*
* By default, this method returns FALSE. Generators that have this requirement
* must override this method and return TRUE.
*
* @return boolean
*/
public function isPostInsertGenerator()
{
return false;
}
}
@@ -0,0 +1,69 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;
/**
* Special generator for application-assigned identifiers (doesnt really generate anything).
*
* @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 AssignedGenerator extends AbstractIdGenerator
{
/**
* Returns the identifier assigned to the given entity.
*
* @param object $entity
* @return mixed
* @override
*/
public function generate(EntityManager $em, $entity)
{
$class = $em->getClassMetadata(get_class($entity));
$identifier = array();
if ($class->isIdentifierComposite) {
$idFields = $class->getIdentifierFieldNames();
foreach ($idFields as $idField) {
$value = $class->getReflectionProperty($idField)->getValue($entity);
if (isset($value)) {
$identifier[$idField] = $value;
} else {
throw ORMException::entityMissingAssignedId($entity);
}
}
} else {
$idField = $class->identifier[0];
$value = $class->reflFields[$idField]->getValue($entity);
if (isset($value)) {
$identifier[$idField] = $value;
} else {
throw ORMException::entityMissingAssignedId($entity);
}
}
return $identifier;
}
}
@@ -0,0 +1,59 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
* that automatically get a database-generated, auto-incremented identifier on INSERT.
* This generator obtains the last insert id after such an insert.
*/
class IdentityGenerator extends AbstractIdGenerator
{
/** @var string The name of the sequence to pass to lastInsertId(), if any. */
private $_seqName;
/**
* @param string $seqName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.
*/
public function __construct($seqName = null)
{
$this->_seqName = $seqName;
}
/**
* {@inheritdoc}
*/
public function generate(EntityManager $em, $entity)
{
return $em->getConnection()->lastInsertId($this->_seqName);
}
/**
* {@inheritdoc}
*/
public function isPostInsertGenerator()
{
return true;
}
}
@@ -0,0 +1,103 @@
<?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\Id;
use Serializable, Doctrine\ORM\EntityManager;
/**
* Represents an ID generator that uses a database sequence.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
*/
class SequenceGenerator extends AbstractIdGenerator implements Serializable
{
private $_allocationSize;
private $_sequenceName;
private $_nextValue = 0;
private $_maxValue = null;
/**
* Initializes a new sequence generator.
*
* @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.
*/
public function __construct($sequenceName, $allocationSize)
{
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
}
/**
* Generates an ID for the given entity.
*
* @param object $entity
* @return integer|float The generated value.
* @override
*/
public function generate(EntityManager $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) {
// Allocate new values
$conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
$this->_nextValue = $conn->fetchColumn($sql);
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
}
return $this->_nextValue++;
}
/**
* Gets the maximum value of the currently allocated bag of values.
*
* @return integer|float
*/
public function getCurrentMaxValue()
{
return $this->_maxValue;
}
/**
* Gets the next value that will be returned by generate().
*
* @return integer|float
*/
public function getNextValue()
{
return $this->_nextValue;
}
public function serialize()
{
return serialize(array(
'allocationSize' => $this->_allocationSize,
'sequenceName' => $this->_sequenceName
));
}
public function unserialize($serialized)
{
$array = unserialize($serialized);
$this->_sequenceName = $array['sequenceName'];
$this->_allocationSize = $array['allocationSize'];
}
}
@@ -0,0 +1,79 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
/**
* Id generator that uses a single-row database table and a hi/lo algorithm.
*
* @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 TableGenerator extends AbstractIdGenerator
{
private $_tableName;
private $_sequenceName;
private $_allocationSize;
private $_nextValue;
private $_maxValue;
public function __construct($tableName, $sequenceName = 'default', $allocationSize = 10)
{
$this->_tableName = $tableName;
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
}
public function generate(EntityManager $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) {
// Allocate new values
$conn = $em->getConnection();
if ($conn->getTransactionNestingLevel() == 0) {
// use select for update
$sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName);
$currentLevel = $conn->fetchColumn($sql);
if ($currentLevel != null) {
$this->_nextValue = $currentLevel;
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
$updateSql = $conn->getDatabasePlatform()->getTableHiLoUpdateNextValSql(
$this->_tableName, $this->_sequenceName, $this->_allocationSize
);
if ($conn->executeUpdate($updateSql, array(1 => $currentLevel, 2 => $currentLevel+1)) !== 1) {
// no affected rows, concurrency issue, throw exception
}
} else {
// no current level returned, TableGenerator seems to be broken, throw exception
}
} else {
// only table locks help here, implement this or throw exception?
// or do we want to work with table locks exclusively?
}
}
return $this->_nextValue++;
}
}
@@ -0,0 +1,48 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
abstract class AbstractIdGenerator
{
/**
* Generates an identifier for an entity.
*
* @param Doctrine\ORM\Entity $entity
* @return mixed
*/
abstract public function generate(EntityManager $em, $entity);
/**
* Gets whether this generator is a post-insert generator which means that
* {@link generate()} must be called after the entity has been inserted
* into the database.
*
* By default, this method returns FALSE. Generators that have this requirement
* must override this method and return TRUE.
*
* @return boolean
*/
public function isPostInsertGenerator()
{
return false;
}
}
@@ -0,0 +1,69 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;
/**
* Special generator for application-assigned identifiers (doesnt really generate anything).
*
* @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 AssignedGenerator extends AbstractIdGenerator
{
/**
* Returns the identifier assigned to the given entity.
*
* @param object $entity
* @return mixed
* @override
*/
public function generate(EntityManager $em, $entity)
{
$class = $em->getClassMetadata(get_class($entity));
$identifier = array();
if ($class->isIdentifierComposite) {
$idFields = $class->getIdentifierFieldNames();
foreach ($idFields as $idField) {
$value = $class->getReflectionProperty($idField)->getValue($entity);
if (isset($value)) {
$identifier[$idField] = $value;
} else {
throw ORMException::entityMissingAssignedId($entity);
}
}
} else {
$idField = $class->identifier[0];
$value = $class->reflFields[$idField]->getValue($entity);
if (isset($value)) {
$identifier[$idField] = $value;
} else {
throw ORMException::entityMissingAssignedId($entity);
}
}
return $identifier;
}
}
@@ -0,0 +1,59 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
* that automatically get a database-generated, auto-incremented identifier on INSERT.
* This generator obtains the last insert id after such an insert.
*/
class IdentityGenerator extends AbstractIdGenerator
{
/** @var string The name of the sequence to pass to lastInsertId(), if any. */
private $_seqName;
/**
* @param string $seqName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.
*/
public function __construct($seqName = null)
{
$this->_seqName = $seqName;
}
/**
* {@inheritdoc}
*/
public function generate(EntityManager $em, $entity)
{
return $em->getConnection()->lastInsertId($this->_seqName);
}
/**
* {@inheritdoc}
*/
public function isPostInsertGenerator()
{
return true;
}
}
@@ -0,0 +1,103 @@
<?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\Id;
use Serializable, Doctrine\ORM\EntityManager;
/**
* Represents an ID generator that uses a database sequence.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
*/
class SequenceGenerator extends AbstractIdGenerator implements Serializable
{
private $_allocationSize;
private $_sequenceName;
private $_nextValue = 0;
private $_maxValue = null;
/**
* Initializes a new sequence generator.
*
* @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.
*/
public function __construct($sequenceName, $allocationSize)
{
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
}
/**
* Generates an ID for the given entity.
*
* @param object $entity
* @return integer|float The generated value.
* @override
*/
public function generate(EntityManager $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) {
// Allocate new values
$conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
$this->_nextValue = $conn->fetchColumn($sql);
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
}
return $this->_nextValue++;
}
/**
* Gets the maximum value of the currently allocated bag of values.
*
* @return integer|float
*/
public function getCurrentMaxValue()
{
return $this->_maxValue;
}
/**
* Gets the next value that will be returned by generate().
*
* @return integer|float
*/
public function getNextValue()
{
return $this->_nextValue;
}
public function serialize()
{
return serialize(array(
'allocationSize' => $this->_allocationSize,
'sequenceName' => $this->_sequenceName
));
}
public function unserialize($serialized)
{
$array = unserialize($serialized);
$this->_sequenceName = $array['sequenceName'];
$this->_allocationSize = $array['allocationSize'];
}
}
@@ -0,0 +1,79 @@
<?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\Id;
use Doctrine\ORM\EntityManager;
/**
* Id generator that uses a single-row database table and a hi/lo algorithm.
*
* @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 TableGenerator extends AbstractIdGenerator
{
private $_tableName;
private $_sequenceName;
private $_allocationSize;
private $_nextValue;
private $_maxValue;
public function __construct($tableName, $sequenceName = 'default', $allocationSize = 10)
{
$this->_tableName = $tableName;
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
}
public function generate(EntityManager $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) {
// Allocate new values
$conn = $em->getConnection();
if ($conn->getTransactionNestingLevel() == 0) {
// use select for update
$sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName);
$currentLevel = $conn->fetchColumn($sql);
if ($currentLevel != null) {
$this->_nextValue = $currentLevel;
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
$updateSql = $conn->getDatabasePlatform()->getTableHiLoUpdateNextValSql(
$this->_tableName, $this->_sequenceName, $this->_allocationSize
);
if ($conn->executeUpdate($updateSql, array(1 => $currentLevel, 2 => $currentLevel+1)) !== 1) {
// no affected rows, concurrency issue, throw exception
}
} else {
// no current level returned, TableGenerator seems to be broken, throw exception
}
} else {
// only table locks help here, implement this or throw exception?
// or do we want to work with table locks exclusively?
}
}
return $this->_nextValue++;
}
}
@@ -0,0 +1,11 @@
K 25
svn:wc:ra_dav:version-url
V 63
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal
END
CommitOrderCalculator.php
K 25
svn:wc:ra_dav:version-url
V 89
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php
END
@@ -0,0 +1,65 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine/ORM/Internal
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
Hydration
dir
CommitOrderCalculator.php
file
2010-10-22T02:39:44.193323Z
00f23956a125df027fc12ed082376b57
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
3651
@@ -0,0 +1,118 @@
<?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;
/**
* The CommitOrderCalculator is used by the UnitOfWork to sort out the
* correct order in which changes to entities need to be persisted.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
*/
class CommitOrderCalculator
{
const NOT_VISITED = 1;
const IN_PROGRESS = 2;
const VISITED = 3;
private $_nodeStates = array();
private $_classes = array(); // The nodes to sort
private $_relatedClasses = array();
private $_sorted = array();
/**
* Clears the current graph.
*
* @return void
*/
public function clear()
{
$this->_classes =
$this->_relatedClasses = array();
}
/**
* Gets a valid commit order for all current nodes.
*
* Uses a depth-first search (DFS) to traverse the graph.
* The desired topological sorting is the reverse postorder of these searches.
*
* @return array The list of ordered classes.
*/
public function getCommitOrder()
{
// Check whether we need to do anything. 0 or 1 node is easy.
$nodeCount = count($this->_classes);
if ($nodeCount == 0) {
return array();
} else if ($nodeCount == 1) {
return array_values($this->_classes);
}
// Init
foreach ($this->_classes as $node) {
$this->_nodeStates[$node->name] = self::NOT_VISITED;
}
// Go
foreach ($this->_classes as $node) {
if ($this->_nodeStates[$node->name] == self::NOT_VISITED) {
$this->_visitNode($node);
}
}
$sorted = array_reverse($this->_sorted);
$this->_sorted = $this->_nodeStates = array();
return $sorted;
}
private function _visitNode($node)
{
$this->_nodeStates[$node->name] = self::IN_PROGRESS;
if (isset($this->_relatedClasses[$node->name])) {
foreach ($this->_relatedClasses[$node->name] as $relatedNode) {
if ($this->_nodeStates[$relatedNode->name] == self::NOT_VISITED) {
$this->_visitNode($relatedNode);
}
}
}
$this->_nodeStates[$node->name] = self::VISITED;
$this->_sorted[] = $node;
}
public function addDependency($fromClass, $toClass)
{
$this->_relatedClasses[$fromClass->name][] = $toClass;
}
public function hasClass($className)
{
return isset($this->_classes[$className]);
}
public function addClass($class)
{
$this->_classes[$class->name] = $class;
}
}
@@ -0,0 +1,118 @@
<?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;
/**
* The CommitOrderCalculator is used by the UnitOfWork to sort out the
* correct order in which changes to entities need to be persisted.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
*/
class CommitOrderCalculator
{
const NOT_VISITED = 1;
const IN_PROGRESS = 2;
const VISITED = 3;
private $_nodeStates = array();
private $_classes = array(); // The nodes to sort
private $_relatedClasses = array();
private $_sorted = array();
/**
* Clears the current graph.
*
* @return void
*/
public function clear()
{
$this->_classes =
$this->_relatedClasses = array();
}
/**
* Gets a valid commit order for all current nodes.
*
* Uses a depth-first search (DFS) to traverse the graph.
* The desired topological sorting is the reverse postorder of these searches.
*
* @return array The list of ordered classes.
*/
public function getCommitOrder()
{
// Check whether we need to do anything. 0 or 1 node is easy.
$nodeCount = count($this->_classes);
if ($nodeCount == 0) {
return array();
} else if ($nodeCount == 1) {
return array_values($this->_classes);
}
// Init
foreach ($this->_classes as $node) {
$this->_nodeStates[$node->name] = self::NOT_VISITED;
}
// Go
foreach ($this->_classes as $node) {
if ($this->_nodeStates[$node->name] == self::NOT_VISITED) {
$this->_visitNode($node);
}
}
$sorted = array_reverse($this->_sorted);
$this->_sorted = $this->_nodeStates = array();
return $sorted;
}
private function _visitNode($node)
{
$this->_nodeStates[$node->name] = self::IN_PROGRESS;
if (isset($this->_relatedClasses[$node->name])) {
foreach ($this->_relatedClasses[$node->name] as $relatedNode) {
if ($this->_nodeStates[$relatedNode->name] == self::NOT_VISITED) {
$this->_visitNode($relatedNode);
}
}
}
$this->_nodeStates[$node->name] = self::VISITED;
$this->_sorted[] = $node;
}
public function addDependency($fromClass, $toClass)
{
$this->_relatedClasses[$fromClass->name][] = $toClass;
}
public function hasClass($className)
{
return isset($this->_classes[$className]);
}
public function addClass($class)
{
$this->_classes[$class->name] = $class;
}
}
@@ -0,0 +1,47 @@
K 25
svn:wc:ra_dav:version-url
V 73
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration
END
ArrayHydrator.php
K 25
svn:wc:ra_dav:version-url
V 91
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
END
AbstractHydrator.php
K 25
svn:wc:ra_dav:version-url
V 94
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php
END
HydrationException.php
K 25
svn:wc:ra_dav:version-url
V 96
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php
END
ScalarHydrator.php
K 25
svn:wc:ra_dav:version-url
V 92
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php
END
ObjectHydrator.php
K 25
svn:wc:ra_dav:version-url
V 92
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
END
SingleScalarHydrator.php
K 25
svn:wc:ra_dav:version-url
V 98
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php
END
IterableResult.php
K 25
svn:wc:ra_dav:version-url
V 92
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Internal/Hydration/IterableResult.php
END
@@ -0,0 +1,266 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine/ORM/Internal/Hydration
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
ArrayHydrator.php
file
2010-10-22T02:39:44.163323Z
c6d1bab9a64db92bd0d94d40a2c7e28b
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
9876
AbstractHydrator.php
file
2010-10-22T02:39:44.163323Z
cbf1ce2f914306f9087a18b261da2467
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
10702
HydrationException.php
file
2010-10-22T02:39:44.163323Z
045ae36ed0ec1736c13c607a020baa42
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
500
ScalarHydrator.php
file
2010-10-22T02:39:44.163323Z
808fa26250c13367b3cdb44b1fb72925
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1879
ObjectHydrator.php
file
2010-10-22T02:39:44.163323Z
04084fd115371a4b070a52a6a85d3598
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
20161
SingleScalarHydrator.php
file
2010-10-22T02:39:44.193323Z
b5a17f0e636375c7cdefa0f0f15d6b82
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
1798
IterableResult.php
file
2010-10-22T02:39:44.163323Z
c44febe543d481b9ad9feb993cfa224c
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2593
@@ -0,0 +1,278 @@
<?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,
Doctrine\DBAL\Connection,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\EntityManager;
/**
* Base class for all hydrators. A hydrator is a class that provides some form
* of transformation of an SQL result set into another structure.
*
* @since 2.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
*/
abstract class AbstractHydrator
{
/** @var ResultSetMapping The ResultSetMapping. */
protected $_rsm;
/** @var EntityManager The EntityManager instance. */
protected $_em;
/** @var AbstractPlatform The dbms Platform instance */
protected $_platform;
/** @var UnitOfWork The UnitOfWork of the associated EntityManager. */
protected $_uow;
/** @var array The cache used during row-by-row hydration. */
protected $_cache = array();
/** @var Statement The statement that provides the data to hydrate. */
protected $_stmt;
/** @var array The query hints. */
protected $_hints;
/**
* Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
*
* @param Doctrine\ORM\EntityManager $em The EntityManager to use.
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
$this->_platform = $em->getConnection()->getDatabasePlatform();
$this->_uow = $em->getUnitOfWork();
}
/**
* Initiates a row-by-row hydration.
*
* @param object $stmt
* @param object $resultSetMapping
* @return IterableResult
*/
public function iterate($stmt, $resultSetMapping, array $hints = array())
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_prepare();
return new IterableResult($this);
}
/**
* Hydrates all rows returned by the passed statement instance at once.
*
* @param object $stmt
* @param object $resultSetMapping
* @return mixed
*/
public function hydrateAll($stmt, $resultSetMapping, array $hints = array())
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_prepare();
$result = $this->_hydrateAll();
$this->_cleanup();
return $result;
}
/**
* Hydrates a single row returned by the current statement instance during
* row-by-row hydration with {@link iterate()}.
*
* @return mixed
*/
public function hydrateRow()
{
$row = $this->_stmt->fetch(PDO::FETCH_ASSOC);
if ( ! $row) {
$this->_cleanup();
return false;
}
$result = array();
$this->_hydrateRow($row, $this->_cache, $result);
return $result;
}
/**
* Excutes one-time preparation tasks, once each time hydration is started
* through {@link hydrateAll} or {@link iterate()}.
*/
protected function _prepare()
{}
/**
* Excutes one-time cleanup tasks at the end of a hydration that was initiated
* through {@link hydrateAll} or {@link iterate()}.
*/
protected function _cleanup()
{
$this->_rsm = null;
$this->_stmt->closeCursor();
$this->_stmt = null;
}
/**
* Hydrates a single row from the current statement instance.
*
* Template method.
*
* @param array $data The row data.
* @param array $cache The cache to use.
* @param mixed $result The result to fill.
*/
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
throw new HydrationException("_hydrateRow() not implemented by this hydrator.");
}
/**
* Hydrates all rows from the current statement instance at once.
*/
abstract protected function _hydrateAll();
/**
* Processes a row of the result set.
* Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY).
* Puts the elements of a result row into a new array, grouped by the class
* they belong to. The column names in the result set are mapped to their
* field names during this procedure as well as any necessary conversions on
* the values applied.
*
* @return array An array with all the fields (name => value) of the data row,
* grouped by their component alias.
*/
protected function _gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents)
{
$rowData = array();
foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$cache[$key]['isScalar'] = true;
} else if (isset($this->_rsm->fieldMappings[$key])) {
$fieldName = $this->_rsm->fieldMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) {
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue;
} else {
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
}
}
if (isset($cache[$key]['isScalar'])) {
$rowData['scalars'][$cache[$key]['fieldName']] = $value;
continue;
}
$dqlAlias = $cache[$key]['dqlAlias'];
if (isset($cache[$key]['isMetaColumn'])) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
continue;
}
if ($cache[$key]['isIdentifier']) {
$id[$dqlAlias] .= '|' . $value;
}
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
$nonemptyComponents[$dqlAlias] = true;
}
}
return $rowData;
}
/**
* Processes a row of the result set.
* Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
* simply converts column names to field names and properly converts the
* values according to their types. The resulting row has the same number
* of elements as before.
*
* @param array $data
* @param array $cache
* @return array The processed row.
*/
protected function _gatherScalarRowData(&$data, &$cache)
{
$rowData = array();
foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$cache[$key]['isScalar'] = true;
} else if (isset($this->_rsm->fieldMappings[$key])) {
$fieldName = $this->_rsm->fieldMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) {
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue;
} else {
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
}
}
$fieldName = $cache[$key]['fieldName'];
if (isset($cache[$key]['isScalar'])) {
$rowData[$fieldName] = $value;
} else if (isset($cache[$key]['isMetaColumn'])) {
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
} else {
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type']
->convertToPHPValue($value, $this->_platform);
}
}
return $rowData;
}
}
@@ -0,0 +1,235 @@
<?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, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
/**
* The ArrayHydrator produces a nested array "graph" that is often (not always)
* interchangeable with the corresponding object graph for read-only access.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 1.0
*/
class ArrayHydrator extends AbstractHydrator
{
private $_ce = array();
private $_rootAliases = array();
private $_isSimpleQuery = false;
private $_identifierMap = array();
private $_resultPointers = array();
private $_idTemplate = array();
private $_resultCounter = 0;
/** @override */
protected function _prepare()
{
$this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
$this->_identifierMap = array();
$this->_resultPointers = array();
$this->_idTemplate = array();
$this->_resultCounter = 0;
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array();
$this->_resultPointers[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = '';
}
}
/** @override */
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($data, $cache, $result);
}
return $result;
}
/** @override */
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
// 1) Initialize
$id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = array();
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
// Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars'];
unset($rowData['scalars']);
if (empty($rowData)) {
++$this->_resultCounter;
}
}
// 2) Now hydrate the data found in the current row.
foreach ($rowData as $dqlAlias => $data) {
$index = false;
if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
// It's a joined result
$parent = $this->_rsm->parentAliasMap[$dqlAlias];
$path = $parent . '.' . $dqlAlias;
// 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])) {
$first = reset($this->_resultPointers);
// TODO: Exception if $key === null ?
$baseElement =& $this->_resultPointers[$parent][key($first)];
} else if (isset($this->_resultPointers[$parent])) {
$baseElement =& $this->_resultPointers[$parent];
} else {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
continue;
}
$relationAlias = $this->_rsm->relationMap[$dqlAlias];
$relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
// Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
$oneToOne = false;
if (isset($nonemptyComponents[$dqlAlias])) {
if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array();
}
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
if ( ! $indexExists || ! $indexIsValid) {
$element = $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$baseElement[$relationAlias][$element[$field]] = $element;
} else {
$baseElement[$relationAlias][] = $element;
}
end($baseElement[$relationAlias]);
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
key($baseElement[$relationAlias]);
}
} else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array();
}
} else {
$oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = null;
} else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = $data;
}
}
$coll =& $baseElement[$relationAlias];
if ($coll !== null) {
$this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
}
} else {
// It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root
// Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $rowData[$dqlAlias];
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
if ($this->_rsm->isMixed) {
$result[] = array($element[$field] => $element);
++$this->_resultCounter;
} else {
$result[$element[$field]] = $element;
}
} else {
if ($this->_rsm->isMixed) {
$result[] = array($element);
++$this->_resultCounter;
} else {
$result[] = $element;
}
}
end($result);
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
} else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
/*if ($this->_rsm->isMixed) {
$result[] =& $result[$index];
++$this->_resultCounter;
}*/
}
$this->updateResultPointer($result, $index, $dqlAlias, false);
}
}
// Append scalar values to mixed result sets
if (isset($scalars)) {
foreach ($scalars as $name => $value) {
$result[$this->_resultCounter - 1][$name] = $value;
}
}
}
/**
* Updates the result pointer for an Entity. The result pointers point to the
* last seen instance of each Entity type. This is used for graph construction.
*
* @param array $coll The element.
* @param boolean|integer $index Index of the element in the collection.
* @param string $dqlAlias
* @param boolean $oneToOne Whether it is a single-valued association or not.
*/
private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
{
if ($coll === null) {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
return;
}
if ($index !== false) {
$this->_resultPointers[$dqlAlias] =& $coll[$index];
return;
} else {
if ($coll) {
if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll;
} else {
end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
}
}
}
}
private function _getClassMetadata($className)
{
if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className);
}
return $this->_ce[$className];
}
}
@@ -0,0 +1,17 @@
<?php
namespace Doctrine\ORM\Internal\Hydration;
class HydrationException extends \Doctrine\ORM\ORMException
{
public static function nonUniqueResult()
{
return new self("The result returned by the query was not unique.");
}
public static function parentObjectOfRelationNotFound($alias, $parentAlias)
{
return new self("The parent object of entity result with alias '$alias' was not found."
. " The parent alias is '$parentAlias'.");
}
}
@@ -0,0 +1,104 @@
<?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;
/**
* Represents a result structure that can be iterated over, hydrating row-by-row
* during the iteration. An IterableResult is obtained by AbstractHydrator#iterate().
*
* @author robo
* @since 2.0
*/
class IterableResult implements \Iterator
{
/**
* @var Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
private $_hydrator;
/**
* @var boolean
*/
private $_rewinded = false;
/**
* @var integer
*/
private $_key = -1;
/**
* @var object
*/
private $_current = null;
/**
* @param Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator
*/
public function __construct($hydrator)
{
$this->_hydrator = $hydrator;
}
public function rewind()
{
if ($this->_rewinded == true) {
throw new HydrationException("Can only iterate a Result once.");
} else {
$this->_current = $this->next();
$this->_rewinded = true;
}
}
/**
* Gets the next set of results.
*
* @return array
*/
public function next()
{
$this->_current = $this->_hydrator->hydrateRow();
$this->_key++;
return $this->_current;
}
/**
* @return mixed
*/
public function current()
{
return $this->_current;
}
/**
* @return int
*/
public function key()
{
return $this->_key;
}
/**
* @return bool
*/
public function valid()
{
return ($this->_current!=false);
}
}
@@ -0,0 +1,426 @@
<?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,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\PersistentCollection,
Doctrine\ORM\Query,
Doctrine\Common\Collections\ArrayCollection,
Doctrine\Common\Collections\Collection;
/**
* The ObjectHydrator constructs an object graph out of an SQL result set.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
* @internal Highly performance-sensitive code.
*/
class ObjectHydrator extends AbstractHydrator
{
/* Local ClassMetadata cache to avoid going to the EntityManager all the time.
* 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;
private $_resultCounter;
private $_rootAliases = array();
private $_initializedCollections = array();
private $_existingCollections = array();
//private $_createdEntities;
/** @override */
protected function _prepare()
{
$this->_identifierMap =
$this->_resultPointers =
$this->_idTemplate = array();
$this->_resultCounter = 0;
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = '';
$class = $this->_em->getClassMetadata($className);
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])) {
$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;
}
}
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;
} 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;
}
}
}
}
}
}
}
}
}
/**
* {@inheritdoc}
*/
protected function _cleanup()
{
parent::_cleanup();
$this->_identifierMap =
$this->_initializedCollections =
$this->_existingCollections =
$this->_resultPointers = array();
}
/**
* {@inheritdoc}
*/
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($row, $cache, $result);
}
// Take snapshots from all newly initialized collections
foreach ($this->_initializedCollections as $coll) {
$coll->takeSnapshot();
}
return $result;
}
/**
* Initializes a related collection.
*
* @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)
{
$oid = spl_object_hash($entity);
$relation = $class->associationMappings[$fieldName];
$value = $class->reflFields[$fieldName]->getValue($entity);
if ($value === null) {
$value = new ArrayCollection;
}
if ( ! $value instanceof PersistentCollection) {
$value = new PersistentCollection(
$this->_em,
$this->_ce[$relation['targetEntity']],
$value
);
$value->setOwner($entity, $relation);
$class->reflFields[$fieldName]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH])) {
// Is already PersistentCollection, but REFRESH
$value->setDirty(false);
$value->setInitialized(true);
$value->unwrap()->clear();
$this->_initializedCollections[$oid . $fieldName] = $value;
} else {
// Is already PersistentCollection, and DONT REFRESH
$this->_existingCollections[$oid . $fieldName] = $value;
}
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];
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
unset($data[$discrColumn]);
}
return $this->_uow->createEntity($className, $data, $this->_hints);
}
private function _getEntityFromIdentityMap($className, array $data)
{
$class = $this->_ce[$className];
if ($class->isIdentifierComposite) {
$idHash = '';
foreach ($class->identifier as $fieldName) {
$idHash .= $data[$fieldName] . ' ';
}
return $this->_uow->tryGetByIdHash(rtrim($idHash), $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
*/
private function _getClassMetadata($className)
{
if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className);
}
return $this->_ce[$className];
}
/**
* 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.
*
* @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.
*/
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
// Initialize
$id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = array();
// Split the row data into chunks of class data.
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
// Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars'];
unset($rowData['scalars']);
if (empty($rowData)) {
++$this->_resultCounter;
}
}
// 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
$parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
// we need the $path to save into the identifier map which entities were already
// seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias;
// 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)];
} else if (isset($this->_resultPointers[$parentAlias])) {
$parentObject = $this->_resultPointers[$parentAlias];
} else {
// Parent object of relation not found, so skip it.
continue;
}
$parentClass = $this->_ce[$this->_rsm->aliasMap[$parentAlias]];
$oid = spl_object_hash($parentObject);
$relationField = $this->_rsm->relationMap[$dqlAlias];
$relation = $parentClass->associationMappings[$relationField];
$reflField = $parentClass->reflFields[$relationField];
// Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
// 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);
}
$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.
if ($element = $this->_getEntityFromIdentityMap($entityName, $data)) {
$this->_resultPointers[$dqlAlias] = $element;
} else {
unset($this->_resultPointers[$dqlAlias]);
}
} else {
$element = $this->_getEntity($data, $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
$reflFieldValue->hydrateSet($indexValue, $element);
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
} else {
$reflFieldValue->hydrateAdd($element);
$reflFieldValue->last();
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
}
} else {
// Update result pointer
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
}
} else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
$reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
}
} else {
// PATH B: Single-valued association
$reflFieldValue = $reflField->getValue($parentObject);
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH])) {
if (isset($nonemptyComponents[$dqlAlias])) {
$element = $this->_getEntity($data, $dqlAlias);
$reflField->setValue($parentObject, $element);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
$targetClass = $this->_ce[$relation['targetEntity']];
if ($relation['isOwningSide']) {
//TODO: Just check hints['fetched'] here?
// If there is an inverse mapping on the target class its bidirectional
if ($relation['inversedBy']) {
$inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']];
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject);
}
} else if ($parentClass === $targetClass && $relation['mappedBy']) {
// Special case: bi-directional self-referencing one-one on the same class
$targetClass->reflFields[$relationField]->setValue($element, $parentObject);
}
} else {
// For sure bidirectional, as there is no inverse side in unidirectional mappings
$targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject);
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject);
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
}
// else leave $reflFieldValue null for single-valued associations
} else {
// Update result pointer
$this->_resultPointers[$dqlAlias] = $reflFieldValue;
}
}
} else {
// PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$key = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
if ($this->_rsm->isMixed) {
$element = array($key => $element);
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
} else {
$result[$key] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key;
}
} else {
if ($this->_rsm->isMixed) {
$element = array(0 => $element);
}
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
} else {
// Update result pointer
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$this->_resultPointers[$dqlAlias] = $result[$index];
/*if ($this->_rsm->isMixed) {
$result[] = $result[$index];
++$this->_resultCounter;
}*/
}
}
}
// Append scalar values to mixed result sets
if (isset($scalars)) {
foreach ($scalars as $name => $value) {
$result[$this->_resultCounter - 1][$name] = $value;
}
}
}
}
@@ -0,0 +1,50 @@
<?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 Doctrine\DBAL\Connection;
/**
* Hydrator that produces flat, rectangular results of scalar data.
* The created result is almost the same as a regular SQL result set, except
* that column names are mapped to field names and data type conversions take place.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
class ScalarHydrator extends AbstractHydrator
{
/** @override */
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) {
$result[] = $this->_gatherScalarRowData($data, $cache);
}
return $result;
}
/** @override */
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
$result[] = $this->_gatherScalarRowData($data, $cache);
}
}
@@ -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\Internal\Hydration;
use Doctrine\DBAL\Connection;
/**
* Hydrator that hydrates a single scalar value from the result set.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
class SingleScalarHydrator extends AbstractHydrator
{
/** @override */
protected function _hydrateAll()
{
$cache = array();
$result = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC);
$num = count($result);
if ($num == 0) {
throw new \Doctrine\ORM\NoResultException;
} else if ($num > 1 || count($result[key($result)]) > 1) {
throw new \Doctrine\ORM\NonUniqueResultException;
}
$result = $this->_gatherScalarRowData($result[key($result)], $cache);
return array_shift($result);
}
}
@@ -0,0 +1,278 @@
<?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,
Doctrine\DBAL\Connection,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\EntityManager;
/**
* Base class for all hydrators. A hydrator is a class that provides some form
* of transformation of an SQL result set into another structure.
*
* @since 2.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
*/
abstract class AbstractHydrator
{
/** @var ResultSetMapping The ResultSetMapping. */
protected $_rsm;
/** @var EntityManager The EntityManager instance. */
protected $_em;
/** @var AbstractPlatform The dbms Platform instance */
protected $_platform;
/** @var UnitOfWork The UnitOfWork of the associated EntityManager. */
protected $_uow;
/** @var array The cache used during row-by-row hydration. */
protected $_cache = array();
/** @var Statement The statement that provides the data to hydrate. */
protected $_stmt;
/** @var array The query hints. */
protected $_hints;
/**
* Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
*
* @param Doctrine\ORM\EntityManager $em The EntityManager to use.
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
$this->_platform = $em->getConnection()->getDatabasePlatform();
$this->_uow = $em->getUnitOfWork();
}
/**
* Initiates a row-by-row hydration.
*
* @param object $stmt
* @param object $resultSetMapping
* @return IterableResult
*/
public function iterate($stmt, $resultSetMapping, array $hints = array())
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_prepare();
return new IterableResult($this);
}
/**
* Hydrates all rows returned by the passed statement instance at once.
*
* @param object $stmt
* @param object $resultSetMapping
* @return mixed
*/
public function hydrateAll($stmt, $resultSetMapping, array $hints = array())
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_prepare();
$result = $this->_hydrateAll();
$this->_cleanup();
return $result;
}
/**
* Hydrates a single row returned by the current statement instance during
* row-by-row hydration with {@link iterate()}.
*
* @return mixed
*/
public function hydrateRow()
{
$row = $this->_stmt->fetch(PDO::FETCH_ASSOC);
if ( ! $row) {
$this->_cleanup();
return false;
}
$result = array();
$this->_hydrateRow($row, $this->_cache, $result);
return $result;
}
/**
* Excutes one-time preparation tasks, once each time hydration is started
* through {@link hydrateAll} or {@link iterate()}.
*/
protected function _prepare()
{}
/**
* Excutes one-time cleanup tasks at the end of a hydration that was initiated
* through {@link hydrateAll} or {@link iterate()}.
*/
protected function _cleanup()
{
$this->_rsm = null;
$this->_stmt->closeCursor();
$this->_stmt = null;
}
/**
* Hydrates a single row from the current statement instance.
*
* Template method.
*
* @param array $data The row data.
* @param array $cache The cache to use.
* @param mixed $result The result to fill.
*/
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
throw new HydrationException("_hydrateRow() not implemented by this hydrator.");
}
/**
* Hydrates all rows from the current statement instance at once.
*/
abstract protected function _hydrateAll();
/**
* Processes a row of the result set.
* Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY).
* Puts the elements of a result row into a new array, grouped by the class
* they belong to. The column names in the result set are mapped to their
* field names during this procedure as well as any necessary conversions on
* the values applied.
*
* @return array An array with all the fields (name => value) of the data row,
* grouped by their component alias.
*/
protected function _gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents)
{
$rowData = array();
foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$cache[$key]['isScalar'] = true;
} else if (isset($this->_rsm->fieldMappings[$key])) {
$fieldName = $this->_rsm->fieldMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) {
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue;
} else {
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
}
}
if (isset($cache[$key]['isScalar'])) {
$rowData['scalars'][$cache[$key]['fieldName']] = $value;
continue;
}
$dqlAlias = $cache[$key]['dqlAlias'];
if (isset($cache[$key]['isMetaColumn'])) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
continue;
}
if ($cache[$key]['isIdentifier']) {
$id[$dqlAlias] .= '|' . $value;
}
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
$nonemptyComponents[$dqlAlias] = true;
}
}
return $rowData;
}
/**
* Processes a row of the result set.
* Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
* simply converts column names to field names and properly converts the
* values according to their types. The resulting row has the same number
* of elements as before.
*
* @param array $data
* @param array $cache
* @return array The processed row.
*/
protected function _gatherScalarRowData(&$data, &$cache)
{
$rowData = array();
foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$cache[$key]['isScalar'] = true;
} else if (isset($this->_rsm->fieldMappings[$key])) {
$fieldName = $this->_rsm->fieldMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) {
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue;
} else {
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
}
}
$fieldName = $cache[$key]['fieldName'];
if (isset($cache[$key]['isScalar'])) {
$rowData[$fieldName] = $value;
} else if (isset($cache[$key]['isMetaColumn'])) {
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
} else {
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type']
->convertToPHPValue($value, $this->_platform);
}
}
return $rowData;
}
}
@@ -0,0 +1,235 @@
<?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, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
/**
* The ArrayHydrator produces a nested array "graph" that is often (not always)
* interchangeable with the corresponding object graph for read-only access.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 1.0
*/
class ArrayHydrator extends AbstractHydrator
{
private $_ce = array();
private $_rootAliases = array();
private $_isSimpleQuery = false;
private $_identifierMap = array();
private $_resultPointers = array();
private $_idTemplate = array();
private $_resultCounter = 0;
/** @override */
protected function _prepare()
{
$this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
$this->_identifierMap = array();
$this->_resultPointers = array();
$this->_idTemplate = array();
$this->_resultCounter = 0;
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array();
$this->_resultPointers[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = '';
}
}
/** @override */
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($data, $cache, $result);
}
return $result;
}
/** @override */
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
// 1) Initialize
$id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = array();
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
// Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars'];
unset($rowData['scalars']);
if (empty($rowData)) {
++$this->_resultCounter;
}
}
// 2) Now hydrate the data found in the current row.
foreach ($rowData as $dqlAlias => $data) {
$index = false;
if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
// It's a joined result
$parent = $this->_rsm->parentAliasMap[$dqlAlias];
$path = $parent . '.' . $dqlAlias;
// 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])) {
$first = reset($this->_resultPointers);
// TODO: Exception if $key === null ?
$baseElement =& $this->_resultPointers[$parent][key($first)];
} else if (isset($this->_resultPointers[$parent])) {
$baseElement =& $this->_resultPointers[$parent];
} else {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
continue;
}
$relationAlias = $this->_rsm->relationMap[$dqlAlias];
$relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
// Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
$oneToOne = false;
if (isset($nonemptyComponents[$dqlAlias])) {
if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array();
}
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
if ( ! $indexExists || ! $indexIsValid) {
$element = $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$baseElement[$relationAlias][$element[$field]] = $element;
} else {
$baseElement[$relationAlias][] = $element;
}
end($baseElement[$relationAlias]);
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
key($baseElement[$relationAlias]);
}
} else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array();
}
} else {
$oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = null;
} else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = $data;
}
}
$coll =& $baseElement[$relationAlias];
if ($coll !== null) {
$this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
}
} else {
// It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root
// Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $rowData[$dqlAlias];
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
if ($this->_rsm->isMixed) {
$result[] = array($element[$field] => $element);
++$this->_resultCounter;
} else {
$result[$element[$field]] = $element;
}
} else {
if ($this->_rsm->isMixed) {
$result[] = array($element);
++$this->_resultCounter;
} else {
$result[] = $element;
}
}
end($result);
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
} else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
/*if ($this->_rsm->isMixed) {
$result[] =& $result[$index];
++$this->_resultCounter;
}*/
}
$this->updateResultPointer($result, $index, $dqlAlias, false);
}
}
// Append scalar values to mixed result sets
if (isset($scalars)) {
foreach ($scalars as $name => $value) {
$result[$this->_resultCounter - 1][$name] = $value;
}
}
}
/**
* Updates the result pointer for an Entity. The result pointers point to the
* last seen instance of each Entity type. This is used for graph construction.
*
* @param array $coll The element.
* @param boolean|integer $index Index of the element in the collection.
* @param string $dqlAlias
* @param boolean $oneToOne Whether it is a single-valued association or not.
*/
private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
{
if ($coll === null) {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
return;
}
if ($index !== false) {
$this->_resultPointers[$dqlAlias] =& $coll[$index];
return;
} else {
if ($coll) {
if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll;
} else {
end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
}
}
}
}
private function _getClassMetadata($className)
{
if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className);
}
return $this->_ce[$className];
}
}
@@ -0,0 +1,17 @@
<?php
namespace Doctrine\ORM\Internal\Hydration;
class HydrationException extends \Doctrine\ORM\ORMException
{
public static function nonUniqueResult()
{
return new self("The result returned by the query was not unique.");
}
public static function parentObjectOfRelationNotFound($alias, $parentAlias)
{
return new self("The parent object of entity result with alias '$alias' was not found."
. " The parent alias is '$parentAlias'.");
}
}
@@ -0,0 +1,104 @@
<?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;
/**
* Represents a result structure that can be iterated over, hydrating row-by-row
* during the iteration. An IterableResult is obtained by AbstractHydrator#iterate().
*
* @author robo
* @since 2.0
*/
class IterableResult implements \Iterator
{
/**
* @var Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
private $_hydrator;
/**
* @var boolean
*/
private $_rewinded = false;
/**
* @var integer
*/
private $_key = -1;
/**
* @var object
*/
private $_current = null;
/**
* @param Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator
*/
public function __construct($hydrator)
{
$this->_hydrator = $hydrator;
}
public function rewind()
{
if ($this->_rewinded == true) {
throw new HydrationException("Can only iterate a Result once.");
} else {
$this->_current = $this->next();
$this->_rewinded = true;
}
}
/**
* Gets the next set of results.
*
* @return array
*/
public function next()
{
$this->_current = $this->_hydrator->hydrateRow();
$this->_key++;
return $this->_current;
}
/**
* @return mixed
*/
public function current()
{
return $this->_current;
}
/**
* @return int
*/
public function key()
{
return $this->_key;
}
/**
* @return bool
*/
public function valid()
{
return ($this->_current!=false);
}
}
@@ -0,0 +1,426 @@
<?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,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\PersistentCollection,
Doctrine\ORM\Query,
Doctrine\Common\Collections\ArrayCollection,
Doctrine\Common\Collections\Collection;
/**
* The ObjectHydrator constructs an object graph out of an SQL result set.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
* @internal Highly performance-sensitive code.
*/
class ObjectHydrator extends AbstractHydrator
{
/* Local ClassMetadata cache to avoid going to the EntityManager all the time.
* 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;
private $_resultCounter;
private $_rootAliases = array();
private $_initializedCollections = array();
private $_existingCollections = array();
//private $_createdEntities;
/** @override */
protected function _prepare()
{
$this->_identifierMap =
$this->_resultPointers =
$this->_idTemplate = array();
$this->_resultCounter = 0;
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = '';
$class = $this->_em->getClassMetadata($className);
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])) {
$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;
}
}
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;
} 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;
}
}
}
}
}
}
}
}
}
/**
* {@inheritdoc}
*/
protected function _cleanup()
{
parent::_cleanup();
$this->_identifierMap =
$this->_initializedCollections =
$this->_existingCollections =
$this->_resultPointers = array();
}
/**
* {@inheritdoc}
*/
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($row, $cache, $result);
}
// Take snapshots from all newly initialized collections
foreach ($this->_initializedCollections as $coll) {
$coll->takeSnapshot();
}
return $result;
}
/**
* Initializes a related collection.
*
* @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)
{
$oid = spl_object_hash($entity);
$relation = $class->associationMappings[$fieldName];
$value = $class->reflFields[$fieldName]->getValue($entity);
if ($value === null) {
$value = new ArrayCollection;
}
if ( ! $value instanceof PersistentCollection) {
$value = new PersistentCollection(
$this->_em,
$this->_ce[$relation['targetEntity']],
$value
);
$value->setOwner($entity, $relation);
$class->reflFields[$fieldName]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH])) {
// Is already PersistentCollection, but REFRESH
$value->setDirty(false);
$value->setInitialized(true);
$value->unwrap()->clear();
$this->_initializedCollections[$oid . $fieldName] = $value;
} else {
// Is already PersistentCollection, and DONT REFRESH
$this->_existingCollections[$oid . $fieldName] = $value;
}
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];
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
unset($data[$discrColumn]);
}
return $this->_uow->createEntity($className, $data, $this->_hints);
}
private function _getEntityFromIdentityMap($className, array $data)
{
$class = $this->_ce[$className];
if ($class->isIdentifierComposite) {
$idHash = '';
foreach ($class->identifier as $fieldName) {
$idHash .= $data[$fieldName] . ' ';
}
return $this->_uow->tryGetByIdHash(rtrim($idHash), $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
*/
private function _getClassMetadata($className)
{
if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className);
}
return $this->_ce[$className];
}
/**
* 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.
*
* @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.
*/
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
// Initialize
$id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = array();
// Split the row data into chunks of class data.
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
// Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars'];
unset($rowData['scalars']);
if (empty($rowData)) {
++$this->_resultCounter;
}
}
// 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
$parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
// we need the $path to save into the identifier map which entities were already
// seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias;
// 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)];
} else if (isset($this->_resultPointers[$parentAlias])) {
$parentObject = $this->_resultPointers[$parentAlias];
} else {
// Parent object of relation not found, so skip it.
continue;
}
$parentClass = $this->_ce[$this->_rsm->aliasMap[$parentAlias]];
$oid = spl_object_hash($parentObject);
$relationField = $this->_rsm->relationMap[$dqlAlias];
$relation = $parentClass->associationMappings[$relationField];
$reflField = $parentClass->reflFields[$relationField];
// Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
// 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);
}
$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.
if ($element = $this->_getEntityFromIdentityMap($entityName, $data)) {
$this->_resultPointers[$dqlAlias] = $element;
} else {
unset($this->_resultPointers[$dqlAlias]);
}
} else {
$element = $this->_getEntity($data, $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
$reflFieldValue->hydrateSet($indexValue, $element);
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
} else {
$reflFieldValue->hydrateAdd($element);
$reflFieldValue->last();
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
}
} else {
// Update result pointer
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
}
} else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
$reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
}
} else {
// PATH B: Single-valued association
$reflFieldValue = $reflField->getValue($parentObject);
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH])) {
if (isset($nonemptyComponents[$dqlAlias])) {
$element = $this->_getEntity($data, $dqlAlias);
$reflField->setValue($parentObject, $element);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
$targetClass = $this->_ce[$relation['targetEntity']];
if ($relation['isOwningSide']) {
//TODO: Just check hints['fetched'] here?
// If there is an inverse mapping on the target class its bidirectional
if ($relation['inversedBy']) {
$inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']];
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject);
}
} else if ($parentClass === $targetClass && $relation['mappedBy']) {
// Special case: bi-directional self-referencing one-one on the same class
$targetClass->reflFields[$relationField]->setValue($element, $parentObject);
}
} else {
// For sure bidirectional, as there is no inverse side in unidirectional mappings
$targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject);
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject);
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
}
// else leave $reflFieldValue null for single-valued associations
} else {
// Update result pointer
$this->_resultPointers[$dqlAlias] = $reflFieldValue;
}
}
} else {
// PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$key = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
if ($this->_rsm->isMixed) {
$element = array($key => $element);
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
} else {
$result[$key] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key;
}
} else {
if ($this->_rsm->isMixed) {
$element = array(0 => $element);
}
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
} else {
// Update result pointer
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$this->_resultPointers[$dqlAlias] = $result[$index];
/*if ($this->_rsm->isMixed) {
$result[] = $result[$index];
++$this->_resultCounter;
}*/
}
}
}
// Append scalar values to mixed result sets
if (isset($scalars)) {
foreach ($scalars as $name => $value) {
$result[$this->_resultCounter - 1][$name] = $value;
}
}
}
}
@@ -0,0 +1,50 @@
<?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 Doctrine\DBAL\Connection;
/**
* Hydrator that produces flat, rectangular results of scalar data.
* The created result is almost the same as a regular SQL result set, except
* that column names are mapped to field names and data type conversions take place.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
class ScalarHydrator extends AbstractHydrator
{
/** @override */
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) {
$result[] = $this->_gatherScalarRowData($data, $cache);
}
return $result;
}
/** @override */
protected function _hydrateRow(array $data, array &$cache, array &$result)
{
$result[] = $this->_gatherScalarRowData($data, $cache);
}
}
@@ -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\Internal\Hydration;
use Doctrine\DBAL\Connection;
/**
* Hydrator that hydrates a single scalar value from the result set.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
class SingleScalarHydrator extends AbstractHydrator
{
/** @override */
protected function _hydrateAll()
{
$cache = array();
$result = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC);
$num = count($result);
if ($num == 0) {
throw new \Doctrine\ORM\NoResultException;
} else if ($num > 1 || count($result[key($result)]) > 1) {
throw new \Doctrine\ORM\NonUniqueResultException;
}
$result = $this->_gatherScalarRowData($result[key($result)], $cache);
return array_shift($result);
}
}
@@ -0,0 +1,29 @@
K 25
svn:wc:ra_dav:version-url
V 62
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping
END
MappingException.php
K 25
svn:wc:ra_dav:version-url
V 83
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/MappingException.php
END
ClassMetadataFactory.php
K 25
svn:wc:ra_dav:version-url
V 87
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
END
ClassMetadataInfo.php
K 25
svn:wc:ra_dav:version-url
V 84
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
END
ClassMetadata.php
K 25
svn:wc:ra_dav:version-url
V 80
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/ClassMetadata.php
END
@@ -0,0 +1,167 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine/ORM/Mapping
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
MappingException.php
file
2010-10-22T02:39:44.363322Z
9a9c3701509d1cd2cb935e3182689326
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
8184
ClassMetadataFactory.php
file
2010-10-22T02:39:44.363322Z
f819a9a54ee9c3eadb0d812bfa1a6f32
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
16579
ClassMetadataInfo.php
file
2010-10-22T02:39:44.363322Z
874133c96ecae5d722b82e141168eb23
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
52601
Driver
dir
ClassMetadata.php
file
2010-10-22T02:39:44.363322Z
b4c5c00534a1665a99419e016d2ada25
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
12240
@@ -0,0 +1,376 @@
<?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\Mapping;
use ReflectionClass, ReflectionProperty;
/**
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
* of an entity and it's associations.
*
* Once populated, ClassMetadata instances are usually cached in a serialized form.
*
* <b>IMPORTANT NOTE:</b>
*
* The fields of this class are only public for 2 reasons:
* 1) To allow fast READ access.
* 2) To drastically reduce the size of a serialized instance (private/protected members
* get the whole class name, namespace inclusive, prepended to every property in
* the serialized representation).
*
* @author Roman Borschel <roman@code-factory.org>
* @author Jonathan H. Wage <jonwage@gmail.com>
* @since 2.0
*/
class ClassMetadata extends ClassMetadataInfo
{
/**
* The ReflectionProperty instances of the mapped class.
*
* @var array
*/
public $reflFields = array();
/**
* The prototype from which new instances of the mapped class are created.
*
* @var object
*/
private $_prototype;
/**
* Initializes a new ClassMetadata instance that will hold the object-relational mapping
* metadata of the class with the given name.
*
* @param string $entityName The name of the entity class the new instance is used for.
*/
public function __construct($entityName)
{
parent::__construct($entityName);
$this->reflClass = new ReflectionClass($entityName);
$this->namespace = $this->reflClass->getNamespaceName();
$this->table['name'] = $this->reflClass->getShortName();
}
/**
* Gets the ReflectionPropertys of the mapped class.
*
* @return array An array of ReflectionProperty instances.
*/
public function getReflectionProperties()
{
return $this->reflFields;
}
/**
* Gets a ReflectionProperty for a specific field of the mapped class.
*
* @param string $name
* @return ReflectionProperty
*/
public function getReflectionProperty($name)
{
return $this->reflFields[$name];
}
/**
* Gets the ReflectionProperty for the single identifier field.
*
* @return ReflectionProperty
* @throws BadMethodCallException If the class has a composite identifier.
*/
public function getSingleIdReflectionProperty()
{
if ($this->isIdentifierComposite) {
throw new \BadMethodCallException("Class " . $this->name . " has a composite identifier.");
}
return $this->reflFields[$this->identifier[0]];
}
/**
* Validates & completes the given field mapping.
*
* @param array $mapping The field mapping to validated & complete.
* @return array The validated and completed field mapping.
*
* @throws MappingException
*/
protected function _validateAndCompleteFieldMapping(array &$mapping)
{
parent::_validateAndCompleteFieldMapping($mapping);
// Store ReflectionProperty of mapped field
$refProp = $this->reflClass->getProperty($mapping['fieldName']);
$refProp->setAccessible(true);
$this->reflFields[$mapping['fieldName']] = $refProp;
}
/**
* Extracts the identifier values of an entity of this class.
*
* For composite identifiers, the identifier values are returned as an array
* with the same order as the field order in {@link identifier}.
*
* @param object $entity
* @return array
*/
public function getIdentifierValues($entity)
{
if ($this->isIdentifierComposite) {
$id = array();
foreach ($this->identifier as $idField) {
$value = $this->reflFields[$idField]->getValue($entity);
if ($value !== null) {
$id[$idField] = $value;
}
}
return $id;
} else {
$value = $this->reflFields[$this->identifier[0]]->getValue($entity);
if ($value !== null) {
return array($this->identifier[0] => $value);
}
return array();
}
}
/**
* Populates the entity identifier of an entity.
*
* @param object $entity
* @param mixed $id
* @todo Rename to assignIdentifier()
*/
public function setIdentifierValues($entity, array $id)
{
foreach ($id as $idField => $idValue) {
$this->reflFields[$idField]->setValue($entity, $idValue);
}
}
/**
* Sets the specified field to the specified value on the given entity.
*
* @param object $entity
* @param string $field
* @param mixed $value
*/
public function setFieldValue($entity, $field, $value)
{
$this->reflFields[$field]->setValue($entity, $value);
}
/**
* Gets the specified field's value off the given entity.
*
* @param object $entity
* @param string $field
*/
public function getFieldValue($entity, $field)
{
return $this->reflFields[$field]->getValue($entity);
}
/**
* Stores the association mapping.
*
* @param AssociationMapping $assocMapping
*/
protected function _storeAssociationMapping(array $assocMapping)
{
parent::_storeAssociationMapping($assocMapping);
// Store ReflectionProperty of mapped field
$sourceFieldName = $assocMapping['fieldName'];
$refProp = $this->reflClass->getProperty($sourceFieldName);
$refProp->setAccessible(true);
$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.
*
* @return string The string representation of this instance.
* @todo Construct meaningful string representation.
*/
public function __toString()
{
return __CLASS__ . '@' . spl_object_hash($this);
}
/**
* Determines which fields get serialized.
*
* It is only serialized what is necessary for best unserialization performance.
* That means any metadata properties that are not set or empty or simply have
* their default value are NOT serialized.
*
* Parts that are also NOT serialized because they can not be properly unserialized:
* - reflClass (ReflectionClass)
* - reflFields (ReflectionProperty array)
*
* @return array The names of all the fields that should be serialized.
*/
public function __sleep()
{
// This metadata is always serialized/cached.
$serialized = array(
'associationMappings',
'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName']
'fieldMappings',
'fieldNames',
'identifier',
'isIdentifierComposite', // TODO: REMOVE
'name',
'namespace', // TODO: REMOVE
'table',
'rootEntityName',
'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime.
);
// The rest of the metadata is only serialized if necessary.
if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) {
$serialized[] = 'changeTrackingPolicy';
}
if ($this->customRepositoryClassName) {
$serialized[] = 'customRepositoryClassName';
}
if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) {
$serialized[] = 'inheritanceType';
$serialized[] = 'discriminatorColumn';
$serialized[] = 'discriminatorValue';
$serialized[] = 'discriminatorMap';
$serialized[] = 'parentClasses';
$serialized[] = 'subClasses';
}
if ($this->generatorType != self::GENERATOR_TYPE_NONE) {
$serialized[] = 'generatorType';
if ($this->generatorType == self::GENERATOR_TYPE_SEQUENCE) {
$serialized[] = 'sequenceGeneratorDefinition';
}
}
if ($this->isMappedSuperclass) {
$serialized[] = 'isMappedSuperclass';
}
if ($this->isVersioned) {
$serialized[] = 'isVersioned';
$serialized[] = 'versionField';
}
if ($this->lifecycleCallbacks) {
$serialized[] = 'lifecycleCallbacks';
}
return $serialized;
}
/**
* Restores some state that can not be serialized/unserialized.
*
* @return void
*/
public function __wakeup()
{
// Restore ReflectionClass and properties
$this->reflClass = new ReflectionClass($this->name);
foreach ($this->fieldMappings as $field => $mapping) {
if (isset($mapping['declared'])) {
$reflField = new ReflectionProperty($mapping['declared'], $field);
} else {
$reflField = $this->reflClass->getProperty($field);
}
$reflField->setAccessible(true);
$this->reflFields[$field] = $reflField;
}
foreach ($this->associationMappings as $field => $mapping) {
if (isset($mapping['declared'])) {
$reflField = new ReflectionProperty($mapping['declared'], $field);
} else {
$reflField = $this->reflClass->getProperty($field);
}
$reflField->setAccessible(true);
$this->reflFields[$field] = $reflField;
}
}
/**
* Creates a new instance of the mapped class, without invoking the constructor.
*
* @return object
*/
public function newInstance()
{
if ($this->_prototype === null) {
$this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
}
return clone $this->_prototype;
}
}
@@ -0,0 +1,453 @@
<?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\Mapping;
use ReflectionException,
Doctrine\ORM\ORMException,
Doctrine\ORM\EntityManager,
Doctrine\DBAL\Platforms,
Doctrine\ORM\Events;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
* metadata mapping informations of a class which describes how a class should be mapped
* to a relational database.
*
* @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 ClassMetadataFactory
{
/**
* @var EntityManager
*/
private $em;
/**
* @var AbstractPlatform
*/
private $targetPlatform;
/**
* @var Driver\Driver
*/
private $driver;
/**
* @var \Doctrine\Common\EventManager
*/
private $evm;
/**
* @var \Doctrine\Common\Cache\Cache
*/
private $cacheDriver;
/**
* @var array
*/
private $loadedMetadata = array();
/**
* @var bool
*/
private $initialized = false;
/**
* Creates a new factory instance that uses the given metadata driver implementation.
*
* @param $driver The metadata driver to use.
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Sets the cache driver used by the factory to cache ClassMetadata instances.
*
* @param Doctrine\Common\Cache\Cache $cacheDriver
*/
public function setCacheDriver($cacheDriver)
{
$this->cacheDriver = $cacheDriver;
}
/**
* Gets the cache driver used by the factory to cache ClassMetadata instances.
*
* @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()
{
if ( ! $this->initialized) {
$this->initialize();
}
$metadata = array();
foreach ($this->driver->getAllClassNames() as $className) {
$metadata[] = $this->getMetadataFor($className);
}
return $metadata;
}
/**
* Lazy initialization of this stuff, especially the metadata driver,
* since these are not needed at all when a metadata cache is active.
*/
private function initialize()
{
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
$this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
$this->evm = $this->em->getEventManager();
$this->initialized = true;
}
/**
* Gets the class metadata descriptor for a class.
*
* @param string $className The name of the class.
* @return Doctrine\ORM\Mapping\ClassMetadata
*/
public function getMetadataFor($className)
{
if ( ! isset($this->loadedMetadata[$className])) {
$realClassName = $className;
// Check for namespace alias
if (strpos($className, ':') !== false) {
list($namespaceAlias, $simpleClassName) = explode(':', $className);
$realClassName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
if (isset($this->loadedMetadata[$realClassName])) {
// We do not have the alias name in the map, include it
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
return $this->loadedMetadata[$realClassName];
}
}
if ($this->cacheDriver) {
if (($cached = $this->cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) {
$this->loadedMetadata[$realClassName] = $cached;
} else {
foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
$this->cacheDriver->save(
"$loadedClassName\$CLASSMETADATA", $this->loadedMetadata[$loadedClassName], null
);
}
}
} else {
$this->loadMetadata($realClassName);
}
if ($className != $realClassName) {
// We do not have the alias name in the map, include it
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
}
}
return $this->loadedMetadata[$className];
}
/**
* 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.
*/
public function hasMetadataFor($className)
{
return isset($this->loadedMetadata[$className]);
}
/**
* 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
* @param ClassMetadata $class
*/
public function setMetadataFor($className, $class)
{
$this->loadedMetadata[$className] = $class;
}
/**
* Get array of parent classes for the given entity class
*
* @param string $name
* @return array $parentClasses
*/
protected function getParentClasses($name)
{
// Collect parent classes, ignoring transient (not-mapped) classes.
$parentClasses = array();
foreach (array_reverse(class_parents($name)) as $parentClass) {
if ( ! $this->driver->isTransient($parentClass)) {
$parentClasses[] = $parentClass;
}
}
return $parentClasses;
}
/**
* Loads the metadata of the class in question and all it's ancestors whose metadata
* is still not loaded.
*
* @param string $name The name of the class for which the metadata should get loaded.
* @param array $tables The metadata collection to which the loaded metadata is added.
*/
protected function loadMetadata($name)
{
if ( ! $this->initialized) {
$this->initialize();
}
$loaded = array();
$parentClasses = $this->getParentClasses($name);
$parentClasses[] = $name;
// Move down the hierarchy of parent classes, starting from the topmost class
$parent = null;
$visited = array();
foreach ($parentClasses as $className) {
if (isset($this->loadedMetadata[$className])) {
$parent = $this->loadedMetadata[$className];
if ( ! $parent->isMappedSuperclass) {
array_unshift($visited, $className);
}
continue;
}
$class = $this->newClassMetadataInstance($className);
if ($parent) {
$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);
$class->setDiscriminatorMap($parent->discriminatorMap);
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
}
// Invoke driver
try {
$this->driver->loadMetadataForClass($className, $class);
} catch (ReflectionException $e) {
throw MappingException::reflectionFailure($className, $e);
}
// Verify & complete identifier mapping
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
throw MappingException::identifierRequired($className);
}
if ($parent && ! $parent->isMappedSuperclass) {
if ($parent->isIdGeneratorSequence()) {
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
} else if ($parent->isIdGeneratorTable()) {
$class->getTableGeneratorDefinition($parent->tableGeneratorDefinition);
}
if ($parent->generatorType) {
$class->setIdGeneratorType($parent->generatorType);
}
if ($parent->idGenerator) {
$class->setIdGenerator($parent->idGenerator);
}
} else {
$this->completeIdGeneratorMapping($class);
}
if ($parent && $parent->isInheritanceTypeSingleTable()) {
$class->setPrimaryTable($parent->table);
}
$class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class);
$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->loadedMetadata[$className] = $class;
$parent = $class;
if ( ! $class->isMappedSuperclass) {
array_unshift($visited, $className);
}
$loaded[] = $className;
}
return $loaded;
}
/**
* Creates a new ClassMetadata instance for the given class name.
*
* @param string $className
* @return Doctrine\ORM\Mapping\ClassMetadata
*/
protected function newClassMetadataInstance($className)
{
return new ClassMetadata($className);
}
/**
* Adds inherited fields to the subclass mapping.
*
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
*/
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if ( ! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedFieldMapping($mapping);
}
foreach ($parentClass->reflFields as $name => $field) {
$subClass->reflFields[$name] = $field;
}
}
/**
* Adds inherited association mappings to the subclass mapping.
*
* @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) {
$mapping['sourceEntity'] = $subClass->name;
}
//$subclassMapping = $mapping;
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if ( ! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedAssociationMapping($mapping);
}
}
/**
* 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
*/
private function completeIdGeneratorMapping(ClassMetadataInfo $class)
{
$idGenType = $class->generatorType;
if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) {
if ($this->targetPlatform->prefersSequences()) {
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE);
} else if ($this->targetPlatform->prefersIdentityColumns()) {
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
} else {
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE);
}
}
// Create & assign an appropriate ID generator instance
switch ($class->generatorType) {
case ClassMetadata::GENERATOR_TYPE_IDENTITY:
// For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to
// <table>_<column>_seq in PostgreSQL for SERIAL columns.
// Not pretty but necessary and the simplest solution that currently works.
$seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ?
$class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' :
null;
$class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName));
break;
case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
// If there is no sequence definition yet, create a default definition
$definition = $class->sequenceGeneratorDefinition;
if ( ! $definition) {
$sequenceName = $class->getTableName() . '_' . $class->getSingleIdentifierColumnName() . '_seq';
$definition['sequenceName'] = $this->targetPlatform->fixSchemaElementName($sequenceName);
$definition['allocationSize'] = 1;
$definition['initialValue'] = 1;
$class->setSequenceGeneratorDefinition($definition);
}
$sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator(
$definition['sequenceName'],
$definition['allocationSize']
);
$class->setIdGenerator($sequenceGenerator);
break;
case ClassMetadata::GENERATOR_TYPE_NONE:
$class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
break;
case ClassMetadata::GENERATOR_TYPE_TABLE:
throw new ORMException("TableGenerator not yet implemented.");
break;
default:
throw new ORMException("Unknown generator type: " . $class->generatorType);
}
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -0,0 +1,219 @@
<?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.phpdoctrine.org>.
*/
namespace Doctrine\ORM\Mapping;
/**
* A MappingException indicates that something is wrong with the mapping setup.
*
* @since 2.0
*/
class MappingException extends \Doctrine\ORM\ORMException
{
public static function pathRequired()
{
return new self("Specifying the paths to your entities is required ".
"in the AnnotationDriver to retrieve all class names.");
}
public static function identifierRequired($entityName)
{
return new self("No identifier/primary key specified for Entity '$entityName'."
. " Every Entity must have an identifier/primary key.");
}
public static function invalidInheritanceType($entityName, $type)
{
return new self("The inheritance type '$type' specified for '$entityName' does not exist.");
}
public static function generatorNotAllowedWithCompositeId()
{
return new self("Id generators can't be used with a composite id.");
}
public static function missingFieldName()
{
return new self("The association mapping misses the 'fieldName' attribute.");
}
public static function missingTargetEntity($fieldName)
{
return new self("The association mapping '$fieldName' misses the 'targetEntity' attribute.");
}
public static function missingSourceEntity($fieldName)
{
return new self("The association mapping '$fieldName' misses the 'sourceEntity' attribute.");
}
public static function mappingFileNotFound($entityName, $fileName)
{
return new self("No mapping file found named '$fileName' for class '$entityName'.");
}
public static function mappingNotFound($fieldName)
{
return new self("No mapping found for field '$fieldName'.");
}
public static function oneToManyRequiresMappedBy($fieldName)
{
return new self("OneToMany mapping on field '$fieldName' requires the 'mappedBy' attribute.");
}
public static function joinTableRequired($fieldName)
{
return new self("The mapping of field '$fieldName' requires an the 'joinTable' attribute.");
}
/**
* Called if a required option was not found but is required
*
* @param string $field which field cannot be processed?
* @param string $expectedOption which option is required
* @param string $hint Can optionally be used to supply a tip for common mistakes,
* e.g. "Did you think of the plural s?"
* @return MappingException
*/
static function missingRequiredOption($field, $expectedOption, $hint = '')
{
$message = "The mapping of field '{$field}' is invalid: The option '{$expectedOption}' is required.";
if ( ! empty($hint)) {
$message .= ' (Hint: ' . $hint . ')';
}
return new self($message);
}
/**
* Generic exception for invalid mappings.
*
* @param string $fieldName
*/
public static function invalidMapping($fieldName)
{
return new self("The mapping of field '$fieldName' is invalid.");
}
/**
* Exception for reflection exceptions - adds the entity name,
* because there might be long classnames that will be shortened
* within the stacktrace
*
* @param string $entity The entity's name
* @param \ReflectionException $previousException
*/
public static function reflectionFailure($entity, \ReflectionException $previousException)
{
return new self('An error occurred in ' . $entity, 0, $previousException);
}
public static function joinColumnMustPointToMappedField($className, $joinColumn)
{
return new self('The column ' . $joinColumn . ' must be mapped to a field in class '
. $className . ' since it is referenced by a join column of another class.');
}
public static function classIsNotAValidEntityOrMappedSuperClass($className)
{
return new self('Class '.$className.' is not a valid entity or mapped super class.');
}
public static function propertyTypeIsRequired($className, $propertyName)
{
return new self("The attribute 'type' is required for the column description of property ".$className."::\$".$propertyName.".");
}
public static function tableIdGeneratorNotImplemented($className)
{
return new self("TableIdGenerator is not yet implemented for use with class ".$className);
}
/**
*
* @param string $entity The entity's name
* @param string $fieldName The name of the field that was already declared
*/
public static function duplicateFieldMapping($entity, $fieldName) {
return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once');
}
public static function duplicateAssociationMapping($entity, $fieldName) {
return new self('Property "'.$fieldName.'" 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);
}
public static function unsupportedOptimisticLockingType($entity, $fieldName, $unsupportedType) {
return new self('Locking type "'.$unsupportedType.'" (specified in "'.$entity.'", field "'.$fieldName.'") '
.'is not supported by Doctrine.'
);
}
public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null)
{
if ( ! empty($path)) {
$path = '[' . $path . ']';
}
return new self(
'File mapping drivers must have a valid directory path, ' .
'however the given path ' . $path . ' seems to be incorrect!'
);
}
/**
* Throws an exception that indicates that a class used in a discriminator map does not exist.
* An example would be an outdated (maybe renamed) classname.
*
* @param string $className The class that could not be found
* @param string $owningClass The class that declares the discriminator map.
* @return self
*/
public static function invalidClassInDiscriminatorMap($className, $owningClass) {
return new self(
"Entity class '$className' used in the discriminator map of class '$owningClass' ".
"does not exist."
);
}
public static function missingDiscriminatorMap($className)
{
return new self("Entity class '$className' is using inheritance but no discriminator map was defined.");
}
public static function missingDiscriminatorColumn($className)
{
return new self("Entity class '$className' is using inheritance but no discriminator column was defined.");
}
/**
* @param string $className
* @param string $columnName
* @return self
*/
public static function duplicateColumnName($className, $columnName)
{
return new self("Duplicate definition of column '".$columnName."' on entity '".$className."' in a field or discriminator column mapping.");
}
}
@@ -0,0 +1,376 @@
<?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\Mapping;
use ReflectionClass, ReflectionProperty;
/**
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
* of an entity and it's associations.
*
* Once populated, ClassMetadata instances are usually cached in a serialized form.
*
* <b>IMPORTANT NOTE:</b>
*
* The fields of this class are only public for 2 reasons:
* 1) To allow fast READ access.
* 2) To drastically reduce the size of a serialized instance (private/protected members
* get the whole class name, namespace inclusive, prepended to every property in
* the serialized representation).
*
* @author Roman Borschel <roman@code-factory.org>
* @author Jonathan H. Wage <jonwage@gmail.com>
* @since 2.0
*/
class ClassMetadata extends ClassMetadataInfo
{
/**
* The ReflectionProperty instances of the mapped class.
*
* @var array
*/
public $reflFields = array();
/**
* The prototype from which new instances of the mapped class are created.
*
* @var object
*/
private $_prototype;
/**
* Initializes a new ClassMetadata instance that will hold the object-relational mapping
* metadata of the class with the given name.
*
* @param string $entityName The name of the entity class the new instance is used for.
*/
public function __construct($entityName)
{
parent::__construct($entityName);
$this->reflClass = new ReflectionClass($entityName);
$this->namespace = $this->reflClass->getNamespaceName();
$this->table['name'] = $this->reflClass->getShortName();
}
/**
* Gets the ReflectionPropertys of the mapped class.
*
* @return array An array of ReflectionProperty instances.
*/
public function getReflectionProperties()
{
return $this->reflFields;
}
/**
* Gets a ReflectionProperty for a specific field of the mapped class.
*
* @param string $name
* @return ReflectionProperty
*/
public function getReflectionProperty($name)
{
return $this->reflFields[$name];
}
/**
* Gets the ReflectionProperty for the single identifier field.
*
* @return ReflectionProperty
* @throws BadMethodCallException If the class has a composite identifier.
*/
public function getSingleIdReflectionProperty()
{
if ($this->isIdentifierComposite) {
throw new \BadMethodCallException("Class " . $this->name . " has a composite identifier.");
}
return $this->reflFields[$this->identifier[0]];
}
/**
* Validates & completes the given field mapping.
*
* @param array $mapping The field mapping to validated & complete.
* @return array The validated and completed field mapping.
*
* @throws MappingException
*/
protected function _validateAndCompleteFieldMapping(array &$mapping)
{
parent::_validateAndCompleteFieldMapping($mapping);
// Store ReflectionProperty of mapped field
$refProp = $this->reflClass->getProperty($mapping['fieldName']);
$refProp->setAccessible(true);
$this->reflFields[$mapping['fieldName']] = $refProp;
}
/**
* Extracts the identifier values of an entity of this class.
*
* For composite identifiers, the identifier values are returned as an array
* with the same order as the field order in {@link identifier}.
*
* @param object $entity
* @return array
*/
public function getIdentifierValues($entity)
{
if ($this->isIdentifierComposite) {
$id = array();
foreach ($this->identifier as $idField) {
$value = $this->reflFields[$idField]->getValue($entity);
if ($value !== null) {
$id[$idField] = $value;
}
}
return $id;
} else {
$value = $this->reflFields[$this->identifier[0]]->getValue($entity);
if ($value !== null) {
return array($this->identifier[0] => $value);
}
return array();
}
}
/**
* Populates the entity identifier of an entity.
*
* @param object $entity
* @param mixed $id
* @todo Rename to assignIdentifier()
*/
public function setIdentifierValues($entity, array $id)
{
foreach ($id as $idField => $idValue) {
$this->reflFields[$idField]->setValue($entity, $idValue);
}
}
/**
* Sets the specified field to the specified value on the given entity.
*
* @param object $entity
* @param string $field
* @param mixed $value
*/
public function setFieldValue($entity, $field, $value)
{
$this->reflFields[$field]->setValue($entity, $value);
}
/**
* Gets the specified field's value off the given entity.
*
* @param object $entity
* @param string $field
*/
public function getFieldValue($entity, $field)
{
return $this->reflFields[$field]->getValue($entity);
}
/**
* Stores the association mapping.
*
* @param AssociationMapping $assocMapping
*/
protected function _storeAssociationMapping(array $assocMapping)
{
parent::_storeAssociationMapping($assocMapping);
// Store ReflectionProperty of mapped field
$sourceFieldName = $assocMapping['fieldName'];
$refProp = $this->reflClass->getProperty($sourceFieldName);
$refProp->setAccessible(true);
$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.
*
* @return string The string representation of this instance.
* @todo Construct meaningful string representation.
*/
public function __toString()
{
return __CLASS__ . '@' . spl_object_hash($this);
}
/**
* Determines which fields get serialized.
*
* It is only serialized what is necessary for best unserialization performance.
* That means any metadata properties that are not set or empty or simply have
* their default value are NOT serialized.
*
* Parts that are also NOT serialized because they can not be properly unserialized:
* - reflClass (ReflectionClass)
* - reflFields (ReflectionProperty array)
*
* @return array The names of all the fields that should be serialized.
*/
public function __sleep()
{
// This metadata is always serialized/cached.
$serialized = array(
'associationMappings',
'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName']
'fieldMappings',
'fieldNames',
'identifier',
'isIdentifierComposite', // TODO: REMOVE
'name',
'namespace', // TODO: REMOVE
'table',
'rootEntityName',
'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime.
);
// The rest of the metadata is only serialized if necessary.
if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) {
$serialized[] = 'changeTrackingPolicy';
}
if ($this->customRepositoryClassName) {
$serialized[] = 'customRepositoryClassName';
}
if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) {
$serialized[] = 'inheritanceType';
$serialized[] = 'discriminatorColumn';
$serialized[] = 'discriminatorValue';
$serialized[] = 'discriminatorMap';
$serialized[] = 'parentClasses';
$serialized[] = 'subClasses';
}
if ($this->generatorType != self::GENERATOR_TYPE_NONE) {
$serialized[] = 'generatorType';
if ($this->generatorType == self::GENERATOR_TYPE_SEQUENCE) {
$serialized[] = 'sequenceGeneratorDefinition';
}
}
if ($this->isMappedSuperclass) {
$serialized[] = 'isMappedSuperclass';
}
if ($this->isVersioned) {
$serialized[] = 'isVersioned';
$serialized[] = 'versionField';
}
if ($this->lifecycleCallbacks) {
$serialized[] = 'lifecycleCallbacks';
}
return $serialized;
}
/**
* Restores some state that can not be serialized/unserialized.
*
* @return void
*/
public function __wakeup()
{
// Restore ReflectionClass and properties
$this->reflClass = new ReflectionClass($this->name);
foreach ($this->fieldMappings as $field => $mapping) {
if (isset($mapping['declared'])) {
$reflField = new ReflectionProperty($mapping['declared'], $field);
} else {
$reflField = $this->reflClass->getProperty($field);
}
$reflField->setAccessible(true);
$this->reflFields[$field] = $reflField;
}
foreach ($this->associationMappings as $field => $mapping) {
if (isset($mapping['declared'])) {
$reflField = new ReflectionProperty($mapping['declared'], $field);
} else {
$reflField = $this->reflClass->getProperty($field);
}
$reflField->setAccessible(true);
$this->reflFields[$field] = $reflField;
}
}
/**
* Creates a new instance of the mapped class, without invoking the constructor.
*
* @return object
*/
public function newInstance()
{
if ($this->_prototype === null) {
$this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
}
return clone $this->_prototype;
}
}
@@ -0,0 +1,453 @@
<?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\Mapping;
use ReflectionException,
Doctrine\ORM\ORMException,
Doctrine\ORM\EntityManager,
Doctrine\DBAL\Platforms,
Doctrine\ORM\Events;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
* metadata mapping informations of a class which describes how a class should be mapped
* to a relational database.
*
* @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 ClassMetadataFactory
{
/**
* @var EntityManager
*/
private $em;
/**
* @var AbstractPlatform
*/
private $targetPlatform;
/**
* @var Driver\Driver
*/
private $driver;
/**
* @var \Doctrine\Common\EventManager
*/
private $evm;
/**
* @var \Doctrine\Common\Cache\Cache
*/
private $cacheDriver;
/**
* @var array
*/
private $loadedMetadata = array();
/**
* @var bool
*/
private $initialized = false;
/**
* Creates a new factory instance that uses the given metadata driver implementation.
*
* @param $driver The metadata driver to use.
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Sets the cache driver used by the factory to cache ClassMetadata instances.
*
* @param Doctrine\Common\Cache\Cache $cacheDriver
*/
public function setCacheDriver($cacheDriver)
{
$this->cacheDriver = $cacheDriver;
}
/**
* Gets the cache driver used by the factory to cache ClassMetadata instances.
*
* @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()
{
if ( ! $this->initialized) {
$this->initialize();
}
$metadata = array();
foreach ($this->driver->getAllClassNames() as $className) {
$metadata[] = $this->getMetadataFor($className);
}
return $metadata;
}
/**
* Lazy initialization of this stuff, especially the metadata driver,
* since these are not needed at all when a metadata cache is active.
*/
private function initialize()
{
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
$this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
$this->evm = $this->em->getEventManager();
$this->initialized = true;
}
/**
* Gets the class metadata descriptor for a class.
*
* @param string $className The name of the class.
* @return Doctrine\ORM\Mapping\ClassMetadata
*/
public function getMetadataFor($className)
{
if ( ! isset($this->loadedMetadata[$className])) {
$realClassName = $className;
// Check for namespace alias
if (strpos($className, ':') !== false) {
list($namespaceAlias, $simpleClassName) = explode(':', $className);
$realClassName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
if (isset($this->loadedMetadata[$realClassName])) {
// We do not have the alias name in the map, include it
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
return $this->loadedMetadata[$realClassName];
}
}
if ($this->cacheDriver) {
if (($cached = $this->cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) {
$this->loadedMetadata[$realClassName] = $cached;
} else {
foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
$this->cacheDriver->save(
"$loadedClassName\$CLASSMETADATA", $this->loadedMetadata[$loadedClassName], null
);
}
}
} else {
$this->loadMetadata($realClassName);
}
if ($className != $realClassName) {
// We do not have the alias name in the map, include it
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
}
}
return $this->loadedMetadata[$className];
}
/**
* 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.
*/
public function hasMetadataFor($className)
{
return isset($this->loadedMetadata[$className]);
}
/**
* 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
* @param ClassMetadata $class
*/
public function setMetadataFor($className, $class)
{
$this->loadedMetadata[$className] = $class;
}
/**
* Get array of parent classes for the given entity class
*
* @param string $name
* @return array $parentClasses
*/
protected function getParentClasses($name)
{
// Collect parent classes, ignoring transient (not-mapped) classes.
$parentClasses = array();
foreach (array_reverse(class_parents($name)) as $parentClass) {
if ( ! $this->driver->isTransient($parentClass)) {
$parentClasses[] = $parentClass;
}
}
return $parentClasses;
}
/**
* Loads the metadata of the class in question and all it's ancestors whose metadata
* is still not loaded.
*
* @param string $name The name of the class for which the metadata should get loaded.
* @param array $tables The metadata collection to which the loaded metadata is added.
*/
protected function loadMetadata($name)
{
if ( ! $this->initialized) {
$this->initialize();
}
$loaded = array();
$parentClasses = $this->getParentClasses($name);
$parentClasses[] = $name;
// Move down the hierarchy of parent classes, starting from the topmost class
$parent = null;
$visited = array();
foreach ($parentClasses as $className) {
if (isset($this->loadedMetadata[$className])) {
$parent = $this->loadedMetadata[$className];
if ( ! $parent->isMappedSuperclass) {
array_unshift($visited, $className);
}
continue;
}
$class = $this->newClassMetadataInstance($className);
if ($parent) {
$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);
$class->setDiscriminatorMap($parent->discriminatorMap);
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
}
// Invoke driver
try {
$this->driver->loadMetadataForClass($className, $class);
} catch (ReflectionException $e) {
throw MappingException::reflectionFailure($className, $e);
}
// Verify & complete identifier mapping
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
throw MappingException::identifierRequired($className);
}
if ($parent && ! $parent->isMappedSuperclass) {
if ($parent->isIdGeneratorSequence()) {
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
} else if ($parent->isIdGeneratorTable()) {
$class->getTableGeneratorDefinition($parent->tableGeneratorDefinition);
}
if ($parent->generatorType) {
$class->setIdGeneratorType($parent->generatorType);
}
if ($parent->idGenerator) {
$class->setIdGenerator($parent->idGenerator);
}
} else {
$this->completeIdGeneratorMapping($class);
}
if ($parent && $parent->isInheritanceTypeSingleTable()) {
$class->setPrimaryTable($parent->table);
}
$class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class);
$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->loadedMetadata[$className] = $class;
$parent = $class;
if ( ! $class->isMappedSuperclass) {
array_unshift($visited, $className);
}
$loaded[] = $className;
}
return $loaded;
}
/**
* Creates a new ClassMetadata instance for the given class name.
*
* @param string $className
* @return Doctrine\ORM\Mapping\ClassMetadata
*/
protected function newClassMetadataInstance($className)
{
return new ClassMetadata($className);
}
/**
* Adds inherited fields to the subclass mapping.
*
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
*/
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if ( ! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedFieldMapping($mapping);
}
foreach ($parentClass->reflFields as $name => $field) {
$subClass->reflFields[$name] = $field;
}
}
/**
* Adds inherited association mappings to the subclass mapping.
*
* @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) {
$mapping['sourceEntity'] = $subClass->name;
}
//$subclassMapping = $mapping;
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if ( ! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedAssociationMapping($mapping);
}
}
/**
* 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
*/
private function completeIdGeneratorMapping(ClassMetadataInfo $class)
{
$idGenType = $class->generatorType;
if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) {
if ($this->targetPlatform->prefersSequences()) {
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE);
} else if ($this->targetPlatform->prefersIdentityColumns()) {
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
} else {
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE);
}
}
// Create & assign an appropriate ID generator instance
switch ($class->generatorType) {
case ClassMetadata::GENERATOR_TYPE_IDENTITY:
// For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to
// <table>_<column>_seq in PostgreSQL for SERIAL columns.
// Not pretty but necessary and the simplest solution that currently works.
$seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ?
$class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' :
null;
$class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName));
break;
case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
// If there is no sequence definition yet, create a default definition
$definition = $class->sequenceGeneratorDefinition;
if ( ! $definition) {
$sequenceName = $class->getTableName() . '_' . $class->getSingleIdentifierColumnName() . '_seq';
$definition['sequenceName'] = $this->targetPlatform->fixSchemaElementName($sequenceName);
$definition['allocationSize'] = 1;
$definition['initialValue'] = 1;
$class->setSequenceGeneratorDefinition($definition);
}
$sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator(
$definition['sequenceName'],
$definition['allocationSize']
);
$class->setIdGenerator($sequenceGenerator);
break;
case ClassMetadata::GENERATOR_TYPE_NONE:
$class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
break;
case ClassMetadata::GENERATOR_TYPE_TABLE:
throw new ORMException("TableGenerator not yet implemented.");
break;
default:
throw new ORMException("Unknown generator type: " . $class->generatorType);
}
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -0,0 +1,65 @@
K 25
svn:wc:ra_dav:version-url
V 69
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver
END
Driver.php
K 25
svn:wc:ra_dav:version-url
V 80
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/Driver.php
END
StaticPHPDriver.php
K 25
svn:wc:ra_dav:version-url
V 89
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php
END
XmlDriver.php
K 25
svn:wc:ra_dav:version-url
V 83
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
END
DriverChain.php
K 25
svn:wc:ra_dav:version-url
V 85
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php
END
YamlDriver.php
K 25
svn:wc:ra_dav:version-url
V 84
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
END
AbstractFileDriver.php
K 25
svn:wc:ra_dav:version-url
V 92
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/AbstractFileDriver.php
END
DatabaseDriver.php
K 25
svn:wc:ra_dav:version-url
V 88
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php
END
PHPDriver.php
K 25
svn:wc:ra_dav:version-url
V 83
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php
END
DoctrineAnnotations.php
K 25
svn:wc:ra_dav:version-url
V 93
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php
END
AnnotationDriver.php
K 25
svn:wc:ra_dav:version-url
V 90
/doctrine/doctrine2.git/!svn/ver/4932/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
END
@@ -0,0 +1,368 @@
10
dir
4932
http://svn.github.com/doctrine/doctrine2.git/lib/Doctrine/ORM/Mapping/Driver
http://svn.github.com/doctrine/doctrine2.git
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
27cf1600-089b-f978-97ba-ce57b7db7bff
Driver.php
file
2010-10-22T02:39:44.333323Z
1164a8c82a6ccd86e1c0edda58a8e1b0
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2006
StaticPHPDriver.php
file
2010-10-22T02:39:44.333323Z
742c3b5f272cdaccbd5e405f434a9218
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
3932
XmlDriver.php
file
2010-10-22T02:39:44.333323Z
22bcccb9d4b1623ac2c10b404a3371ab
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
20650
DriverChain.php
file
2010-10-22T02:39:44.333323Z
c0f8f0d9698186a347be595d588e4163
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
3683
YamlDriver.php
file
2010-10-22T02:39:44.333323Z
6acb2d13021cf0da9dcd283153644038
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
18062
AbstractFileDriver.php
file
2010-10-22T02:39:44.333323Z
7cc68a19effc751c4f4ca706820d43bc
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
6637
DatabaseDriver.php
file
2010-10-22T02:39:44.333323Z
fa2a8566ec4174a816968a8278d96d25
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
10459
PHPDriver.php
file
2010-10-22T02:39:44.333323Z
e9e942bc8f189a50963f1fc405126368
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
2397
DoctrineAnnotations.php
file
2010-10-22T02:39:44.333323Z
a6b799d58cddca5f37d96683765dc3c5
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
4343
AnnotationDriver.php
file
2010-10-22T02:39:44.333323Z
1a41314fee129d566a3f00805f776911
2010-10-11T13:15:18.000000Z
4932
benjamin.eberlei
20947
@@ -0,0 +1,210 @@
<?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\Mapping\Driver;
use Doctrine\ORM\Mapping\MappingException;
/**
* Base driver for file-based metadata drivers.
*
* A file driver operates in a mode where it loads the mapping files of individual
* classes on demand. This requires the user to adhere to the convention of 1 mapping
* file per class and the file names of the mapping files must correspond to the full
* class name, including namespace, with the namespace delimiters '\', replaced by dots '.'.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.0
* @version $Revision$
* @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>
*/
abstract class AbstractFileDriver implements Driver
{
/**
* The paths where to look for mapping files.
*
* @var array
*/
protected $_paths = array();
/**
* The file extension of mapping documents.
*
* @var string
*/
protected $_fileExtension;
/**
* Initializes a new FileDriver that looks in the given path(s) for mapping
* documents and operates in the specified operating mode.
*
* @param string|array $paths One or multiple paths where mapping documents can be found.
*/
public function __construct($paths)
{
$this->addPaths((array) $paths);
}
/**
* Append lookup paths to metadata driver.
*
* @param array $paths
*/
public function addPaths(array $paths)
{
$this->_paths = array_unique(array_merge($this->_paths, $paths));
}
/**
* Retrieve the defined metadata lookup paths.
*
* @return array
*/
public function getPaths()
{
return $this->_paths;
}
/**
* Get the file extension used to look for mapping files under
*
* @return void
*/
public function getFileExtension()
{
return $this->_fileExtension;
}
/**
* Set the file extension used to look for mapping files under
*
* @param string $fileExtension The file extension to set
* @return void
*/
public function setFileExtension($fileExtension)
{
$this->_fileExtension = $fileExtension;
}
/**
* Get the element of schema meta data for the class from the mapping file.
* This will lazily load the mapping file if it is not loaded yet
*
* @return array $element The element of schema meta data
*/
public function getElement($className)
{
$result = $this->_loadMappingFile($this->_findMappingFile($className));
return $result[$className];
}
/**
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a
* MappedSuperclass.
*
* @param string $className
* @return boolean
*/
public function isTransient($className)
{
$fileName = str_replace('\\', '.', $className) . $this->_fileExtension;
// Check whether file exists
foreach ((array) $this->_paths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . $fileName)) {
return false;
}
}
return true;
}
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array The names of all mapped classes known to this driver.
*/
public function getAllClassNames()
{
$classes = array();
if ($this->_paths) {
foreach ((array) $this->_paths as $path) {
if ( ! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
continue;
}
// NOTE: All files found here means classes are not transient!
$classes[] = str_replace('.', '\\', $fileName);
}
}
}
return $classes;
}
/**
* Finds the mapping file for the class with the given name by searching
* through the configured paths.
*
* @param $className
* @return string The (absolute) file name.
* @throws MappingException
*/
protected function _findMappingFile($className)
{
$fileName = str_replace('\\', '.', $className) . $this->_fileExtension;
// Check whether file exists
foreach ((array) $this->_paths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . $fileName)) {
return $path . DIRECTORY_SEPARATOR . $fileName;
}
}
throw MappingException::mappingFileNotFound($className, $fileName);
}
/**
* Loads a mapping file with the given name and returns a map
* from class/entity names to their corresponding elements.
*
* @param string $file The mapping file to load.
* @return array
*/
abstract protected function _loadMappingFile($file);
}
@@ -0,0 +1,489 @@
<?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\Mapping\Driver;
use Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException;
require __DIR__ . '/DoctrineAnnotations.php';
/**
* The AnnotationDriver reads the mapping metadata from docblock annotations.
*
* @since 2.0
* @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 AnnotationDriver implements Driver
{
/**
* The AnnotationReader.
*
* @var AnnotationReader
*/
private $_reader;
/**
* The paths where to look for mapping files.
*
* @var array
*/
protected $_paths = array();
/**
* The file extension of mapping documents.
*
* @var string
*/
protected $_fileExtension = '.php';
/**
* @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.
*/
public function __construct(AnnotationReader $reader, $paths = null)
{
$this->_reader = $reader;
if ($paths) {
$this->addPaths((array) $paths);
}
}
/**
* Append lookup paths to metadata driver.
*
* @param array $paths
*/
public function addPaths(array $paths)
{
$this->_paths = array_unique(array_merge($this->_paths, $paths));
}
/**
* Retrieve the defined metadata lookup paths.
*
* @return array
*/
public function getPaths()
{
return $this->_paths;
}
/**
* Get the file extension used to look for mapping files under
*
* @return void
*/
public function getFileExtension()
{
return $this->_fileExtension;
}
/**
* Set the file extension used to look for mapping files under
*
* @param string $fileExtension The file extension to set
* @return void
*/
public function setFileExtension($fileExtension)
{
$this->_fileExtension = $fileExtension;
}
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$class = $metadata->getReflectionClass();
$classAnnotations = $this->_reader->getClassAnnotations($class);
// Evaluate Entity annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
$entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity'];
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
} else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) {
$metadata->isMappedSuperclass = true;
} else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
}
// Evaluate Table annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) {
$tableAnnot = $classAnnotations['Doctrine\ORM\Mapping\Table'];
$primaryTable = array(
'name' => $tableAnnot->name,
'schema' => $tableAnnot->schema
);
if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$primaryTable['indexes'][$indexAnnot->name] = array(
'columns' => $indexAnnot->columns
);
}
}
if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraint) {
$primaryTable['uniqueConstraints'][$uniqueConstraint->name] = array(
'columns' => $uniqueConstraint->columns
);
}
}
$metadata->setPrimaryTable($primaryTable);
}
// Evaluate InheritanceType annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) {
$inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType'];
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value));
if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'])) {
$discrColumnAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'];
$metadata->setDiscriminatorColumn(array(
'name' => $discrColumnAnnot->name,
'type' => $discrColumnAnnot->type,
'length' => $discrColumnAnnot->length
));
} else {
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
}
// Evaluate DiscriminatorMap annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'])) {
$discrMapAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'];
$metadata->setDiscriminatorMap($discrMapAnnot->value);
}
}
}
// Evaluate DoctrineChangeTrackingPolicy annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy'])) {
$changeTrackingAnnot = $classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy'];
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAnnot->value));
}
// Evaluate annotations on properties/fields
foreach ($class->getProperties() as $property) {
if ($metadata->isMappedSuperclass && ! $property->isPrivate()
||
$metadata->isInheritedField($property->name)
||
$metadata->isInheritedAssociation($property->name)) {
continue;
}
$mapping = array();
$mapping['fieldName'] = $property->getName();
// Check for JoinColummn/JoinColumns annotations
$joinColumns = array();
if ($joinColumnAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumn')) {
$joinColumns[] = array(
'name' => $joinColumnAnnot->name,
'referencedColumnName' => $joinColumnAnnot->referencedColumnName,
'unique' => $joinColumnAnnot->unique,
'nullable' => $joinColumnAnnot->nullable,
'onDelete' => $joinColumnAnnot->onDelete,
'onUpdate' => $joinColumnAnnot->onUpdate,
'columnDefinition' => $joinColumnAnnot->columnDefinition,
);
} else if ($joinColumnsAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) {
foreach ($joinColumnsAnnot->value as $joinColumn) {
$joinColumns[] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'onUpdate' => $joinColumn->onUpdate,
'columnDefinition' => $joinColumn->columnDefinition,
);
}
}
// Field can only be annotated with one of:
// @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
if ($columnAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Column')) {
if ($columnAnnot->type == null) {
throw MappingException::propertyTypeIsRequired($className, $property->getName());
}
$mapping['type'] = $columnAnnot->type;
$mapping['length'] = $columnAnnot->length;
$mapping['precision'] = $columnAnnot->precision;
$mapping['scale'] = $columnAnnot->scale;
$mapping['nullable'] = $columnAnnot->nullable;
$mapping['unique'] = $columnAnnot->unique;
if ($columnAnnot->options) {
$mapping['options'] = $columnAnnot->options;
}
if (isset($columnAnnot->name)) {
$mapping['columnName'] = $columnAnnot->name;
}
if (isset($columnAnnot->columnDefinition)) {
$mapping['columnDefinition'] = $columnAnnot->columnDefinition;
}
if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) {
$mapping['id'] = true;
}
if ($generatedValueAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\GeneratedValue')) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy));
}
if ($versionAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Version')) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
// Check for SequenceGenerator/TableGenerator definition
if ($seqGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\SequenceGenerator')) {
$metadata->setSequenceGeneratorDefinition(array(
'sequenceName' => $seqGeneratorAnnot->sequenceName,
'allocationSize' => $seqGeneratorAnnot->allocationSize,
'initialValue' => $seqGeneratorAnnot->initialValue
));
} else if ($tblGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) {
throw MappingException::tableIdGeneratorNotImplemented($className);
}
} else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) {
$mapping['targetEntity'] = $oneToOneAnnot->targetEntity;
$mapping['joinColumns'] = $joinColumns;
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy;
$mapping['inversedBy'] = $oneToOneAnnot->inversedBy;
$mapping['cascade'] = $oneToOneAnnot->cascade;
$mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneAnnot->fetch);
$metadata->mapOneToOne($mapping);
} else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) {
$mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
$mapping['targetEntity'] = $oneToManyAnnot->targetEntity;
$mapping['cascade'] = $oneToManyAnnot->cascade;
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyAnnot->fetch);
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
$mapping['orderBy'] = $orderByAnnot->value;
}
$metadata->mapOneToMany($mapping);
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) {
$mapping['joinColumns'] = $joinColumns;
$mapping['cascade'] = $manyToOneAnnot->cascade;
$mapping['inversedBy'] = $manyToOneAnnot->inversedBy;
$mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneAnnot->fetch);
$metadata->mapManyToOne($mapping);
} else if ($manyToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) {
$joinTable = array();
if ($joinTableAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinTable')) {
$joinTable = array(
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema
);
foreach ($joinTableAnnot->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'onUpdate' => $joinColumn->onUpdate,
'columnDefinition' => $joinColumn->columnDefinition,
);
}
foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'onUpdate' => $joinColumn->onUpdate,
'columnDefinition' => $joinColumn->columnDefinition,
);
}
}
$mapping['joinTable'] = $joinTable;
$mapping['targetEntity'] = $manyToManyAnnot->targetEntity;
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
$mapping['cascade'] = $manyToManyAnnot->cascade;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyAnnot->fetch);
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
$mapping['orderBy'] = $orderByAnnot->value;
}
$metadata->mapManyToMany($mapping);
}
}
// Evaluate @HasLifecycleCallbacks annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
foreach ($class->getMethods() as $method) {
if ($method->isPublic()) {
$annotations = $this->_reader->getMethodAnnotations($method);
if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostPersist'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postPersist);
}
if (isset($annotations['Doctrine\ORM\Mapping\PreUpdate'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preUpdate);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostUpdate'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postUpdate);
}
if (isset($annotations['Doctrine\ORM\Mapping\PreRemove'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preRemove);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostRemove'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postRemove);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad);
}
}
}
}
}
/**
* Whether the class with the specified name is transient. Only non-transient
* classes, that is entities and mapped superclasses, should have their metadata loaded.
* A class is non-transient if it is annotated with either @Entity or
* @MappedSuperclass in the class doc block.
*
* @param string $className
* @return boolean
*/
public function isTransient($className)
{
$classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
return ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity']) &&
! isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']);
}
/**
* {@inheritDoc}
*/
public function getAllClassNames()
{
if ($this->_classNames !== null) {
return $this->_classNames;
}
if (!$this->_paths) {
throw MappingException::pathRequired();
}
$classes = array();
$includedFiles = array();
foreach ($this->_paths as $path) {
if ( ! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
continue;
}
$sourceFile = realpath($file->getPathName());
require_once $sourceFile;
$includedFiles[] = $sourceFile;
}
}
$declared = get_declared_classes();
foreach ($declared as $className) {
$rc = new \ReflectionClass($className);
$sourceFile = $rc->getFileName();
if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
$classes[] = $className;
}
}
$this->_classNames = $classes;
return $classes;
}
/**
* Factory method for the Annotation Driver
*
* @param array|string $paths
* @param AnnotationReader $reader
* @return AnnotationDriver
*/
static public function create($paths = array(), AnnotationReader $reader = null)
{
if ($reader == null) {
$reader = new AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
}
return new self($reader, $paths);
}
}

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais