Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
doctrine-dbal
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tomáš Trávníček
doctrine-dbal
Commits
d0ea5705
Commit
d0ea5705
authored
May 17, 2008
by
romanb
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial support for scalar hydration (HYDRATE_SCALAR). EntityPersisters born.
parent
613d08f9
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
2137 additions
and
170 deletions
+2137
-170
Doctrine.php
lib/Doctrine.php
+5
-5
Connection.php
lib/Doctrine/Connection.php
+9
-23
EntityManager.php
lib/Doctrine/EntityManager.php
+551
-0
Exception.php
lib/Doctrine/EntityManager/Exception.php
+24
-0
Abstract.php
lib/Doctrine/EntityPersister/Abstract.php
+827
-0
Exception.php
lib/Doctrine/EntityPersister/Exception.php
+6
-0
JoinedSubclass.php
lib/Doctrine/EntityPersister/JoinedSubclass.php
+318
-0
Standard.php
lib/Doctrine/EntityPersister/Standard.php
+120
-0
HydratorNew.php
lib/Doctrine/HydratorNew.php
+110
-29
BasicHydrationTest.php
tests/Orm/Hydration/BasicHydrationTest.php
+167
-113
No files found.
lib/Doctrine.php
View file @
d0ea5705
...
...
@@ -473,11 +473,11 @@ final class Doctrine
const
HYDRATE_NONE
=
4
;
/* new hydration modes. move to Query class when it's time. */
//const HYDRATE_IDENTITY_OBJECT = 1
; // default, auto-adds PKs, produces object graphs
//const HYDRATE_IDENTITY_ARRAY = 2
; // auto-adds PKs, produces array graphs
//const HYDRATE_SCALAR = 3
; // produces flat result list with scalar values
//const HYDRATE_SINGLE_SCALAR = 4
; // produces a single scalar value
//const HYDRATE_NONE =
5
; // produces a result set as it's returned by the db
const
HYDRATE_IDENTITY_OBJECT
=
2
;
// default, auto-adds PKs, produces object graphs
const
HYDRATE_IDENTITY_ARRAY
=
3
;
// auto-adds PKs, produces array graphs
const
HYDRATE_SCALAR
=
5
;
// produces flat result list with scalar values
const
HYDRATE_SINGLE_SCALAR
=
6
;
// produces a single scalar value
//const HYDRATE_NONE =
4
; // produces a result set as it's returned by the db
/**
...
...
lib/Doctrine/Connection.php
View file @
d0ea5705
...
...
@@ -200,8 +200,9 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
public
function
__construct
(
$adapter
,
$user
=
null
,
$pass
=
null
)
{
if
(
is_object
(
$adapter
))
{
if
(
!
(
$adapter
instanceof
PDO
)
&&
!
in_array
(
'Doctrine_Adapter_Interface'
,
class_implements
(
$adapter
)))
{
throw
new
Doctrine_Connection_Exception
(
'First argument should be an instance of PDO or implement Doctrine_Adapter_Interface'
);
if
(
!
$adapter
instanceof
PDO
)
{
throw
new
Doctrine_Connection_Exception
(
'First argument should be an instance of PDO or implement Doctrine_Adapter_Interface'
);
}
$this
->
dbh
=
$adapter
;
$this
->
isConnected
=
true
;
...
...
@@ -216,12 +217,7 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
if
(
isset
(
$adapter
[
'other'
]))
{
$this
->
options
[
'other'
]
=
array
(
Doctrine
::
ATTR_PERSISTENT
=>
$adapter
[
'persistent'
]);
}
}
$this
->
setAttribute
(
Doctrine
::
ATTR_CASE
,
Doctrine
::
CASE_NATURAL
);
$this
->
setAttribute
(
Doctrine
::
ATTR_ERRMODE
,
Doctrine
::
ERRMODE_EXCEPTION
);
$this
->
getAttribute
(
Doctrine
::
ATTR_LISTENER
)
->
onOpen
(
$this
);
}
...
...
@@ -327,27 +323,17 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
$this
->
getListener
()
->
preConnect
(
$event
);
$e
=
explode
(
':'
,
$this
->
options
[
'dsn'
]);
$found
=
false
;
if
(
extension_loaded
(
'pdo'
))
{
if
(
in_array
(
$e
[
0
],
PDO
::
getAvailableDrivers
()))
{
$this
->
dbh
=
new
PDO
(
$this
->
options
[
'dsn'
],
$this
->
options
[
'username'
],
$this
->
dbh
=
new
PDO
(
$this
->
options
[
'dsn'
],
$this
->
options
[
'username'
],
$this
->
options
[
'password'
],
$this
->
options
[
'other'
]);
$this
->
dbh
->
setAttribute
(
PDO
::
ATTR_ERRMODE
,
PDO
::
ERRMODE_EXCEPTION
);
$
found
=
true
;
$
this
->
dbh
->
setAttribute
(
PDO
::
ATTR_CASE
,
PDO
::
CASE_LOWER
)
;
}
}
if
(
!
$found
)
{
$class
=
'Doctrine_Adapter_'
.
ucwords
(
$e
[
0
]);
if
(
class_exists
(
$class
))
{
$this
->
dbh
=
new
$class
(
$this
->
options
[
'dsn'
],
$this
->
options
[
'username'
],
$this
->
options
[
'password'
]);
}
else
{
throw
new
Doctrine_Connection_Exception
(
"Couldn't locate driver named "
.
$e
[
0
]);
}
}
// attach the pending attributes to adapter
foreach
(
$this
->
pendingAttributes
as
$attr
=>
$value
)
{
...
...
lib/Doctrine/EntityManager.php
0 → 100644
View file @
d0ea5705
<?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.phpdoctrine.org>.
*/
/**
* The EntityManager is a central access point to ORM functionality.
*
* @package Doctrine
* @subpackage EntityManager
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.org>
* @todo package:orm
*/
class
Doctrine_EntityManager
{
/**
* The unique name of the EntityManager. The name is used to bind entity classes
* to certain EntityManagers.
*
* @var string
*/
private
$_name
;
/**
* The database connection used by the EntityManager.
*
* @var Doctrine_Connection
*/
private
$_conn
;
/**
* Flush modes enumeration.
*/
private
static
$_flushModes
=
array
(
// auto: Flush occurs automatically after each operation that issues database
// queries. No operations are queued.
'auto'
,
// commit: Flush occurs automatically at transaction commit.
'commit'
,
// manual: Flush occurs never automatically.
'manual'
);
/**
* The metadata factory, used to retrieve the metadata of entity classes.
*
* @var Doctrine_ClassMetadata_Factory
*/
private
$_metadataFactory
;
/**
* The EntityPersister instances.
* @todo Implementation.
*
* @var array
*/
private
$_persisters
=
array
();
/**
* The EntityRepository instances.
*
* @var array
*/
private
$_repositories
=
array
();
/**
* The currently used flush mode. Defaults to 'commit'.
*
* @var string
*/
private
$_flushMode
=
'commit'
;
/**
* Map of all EntityManagers, keys are the names.
*
* @var array
*/
private
static
$_ems
=
array
();
/**
* EntityManager to Entity bindings.
*
* @var array
*/
private
static
$_emBindings
=
array
();
/**
* The unit of work.
*
* @var UnitOfWork
*/
private
$_unitOfWork
;
/**
* Enter description here...
*
* @var unknown_type
*/
//private $_dataTemplates = array();
/**
* Creates a new EntityManager that operates on the given database connection.
*
* @param Doctrine_Connection $conn
* @param string $name
*/
public
function
__construct
(
Doctrine_Connection
$conn
,
$name
=
null
)
{
$this
->
_conn
=
$conn
;
$this
->
_name
=
$name
;
$this
->
_metadataFactory
=
new
Doctrine_ClassMetadata_Factory
(
$this
,
new
Doctrine_ClassMetadata_CodeDriver
());
$this
->
_unitOfWork
=
new
Doctrine_Connection_UnitOfWork
(
$conn
);
if
(
$name
!==
null
)
{
self
::
$_ems
[
$name
]
=
$this
;
}
else
{
self
::
$_ems
[]
=
$this
;
}
}
/**
* Gets the EntityManager that is responsible for the Entity.
*
* @param string $entityName
* @return EntityManager
* @throws Doctrine_EntityManager_Exception If a suitable manager can not be found.
*/
public
static
function
getManager
(
$entityName
=
null
)
{
if
(
!
is_null
(
$entityName
)
&&
isset
(
self
::
$_emBindings
[
$entityName
]))
{
$emName
=
self
::
$_emBindings
[
$entityName
];
if
(
isset
(
self
::
$_ems
[
$emName
]))
{
return
self
::
$_ems
[
$emName
];
}
else
{
throw
Doctrine_EntityManager_Exception
::
noManagerWithName
(
$emName
);
}
}
else
if
(
self
::
$_ems
)
{
return
current
(
self
::
$_ems
);
}
else
{
throw
Doctrine_EntityManager_Exception
::
noEntityManagerAvailable
();
}
}
/**
* Enter description here...
*
* @param unknown_type $entityName
* @param unknown_type $emName
*/
public
static
function
bindEntityToManager
(
$entityName
,
$emName
)
{
if
(
isset
(
self
::
$_emBindings
[
$entityName
]))
{
throw
Doctrine_EntityManager_Exception
::
entityAlreadyBound
(
$entityName
);
}
self
::
$_emBindings
[
$entityName
]
=
$emName
;
}
/**
* Clears all bindings between Entities and EntityManagers.
*/
public
static
function
unbindAllManagers
()
{
self
::
$_emBindings
=
array
();
}
/**
* Releases all EntityManagers.
*
*/
public
static
function
releaseAllManagers
()
{
self
::
$_ems
=
array
();
}
/**
* Gets the database connection object used by the EntityManager.
*
* @return Doctrine_Connection
*/
public
function
getConnection
()
{
return
$this
->
_conn
;
}
/**
* Returns the metadata for a class. Alias for getClassMetadata().
*
* @return Doctrine_Metadata
* @todo package:orm
*/
public
function
getMetadata
(
$className
)
{
return
$this
->
getClassMetadata
(
$className
);
}
/**
* Returns the metadata for a class.
*
* @return Doctrine_Metadata
*/
public
function
getClassMetadata
(
$className
)
{
return
$this
->
_metadataFactory
->
getMetadataFor
(
$className
);
}
/**
* Sets the driver that is used to obtain metadata informations about entity
* classes.
*
* @param $driver The driver to use.
*/
public
function
setClassMetadataDriver
(
$driver
)
{
$this
->
_metadataFactory
->
setDriver
(
$driver
);
}
/**
* Creates a new Doctrine_Query object that operates on this connection.
*
* @return Doctrine_Query
* @todo package:orm
*/
public
function
createQuery
(
$dql
=
""
)
{
$query
=
new
Doctrine_Query
(
$this
);
if
(
!
empty
(
$dql
))
{
$query
->
parseQuery
(
$dql
);
}
return
$query
;
}
/**
* Enter description here...
*
* @param unknown_type $entityName
* @return unknown
*/
public
function
getEntityPersister
(
$entityName
)
{
if
(
!
isset
(
$this
->
_persisters
[
$entityName
]))
{
$class
=
$this
->
getClassMetadata
(
$entityName
);
if
(
$class
->
getInheritanceType
()
==
Doctrine
::
INHERITANCE_TYPE_JOINED
)
{
$persister
=
new
Doctrine_EntityPersister_JoinedSubclass
(
$this
,
$class
);
}
else
{
$persister
=
new
Doctrine_EntityPersister_Standard
(
$this
,
$class
);
}
$this
->
_persisters
[
$entityName
]
=
$persister
;
}
return
$this
->
_persisters
[
$entityName
];
}
/**
* Detaches an entity from the manager. It's lifecycle is no longer managed.
*
* @param Doctrine_Entity $entity
* @return unknown
*/
public
function
detach
(
Doctrine_Entity
$entity
)
{
return
$this
->
_unitOfWork
->
unregisterIdentity
(
$entity
);
}
/**
* Returns the current internal transaction nesting level.
*
* @return integer The nesting level. A value of 0 means theres no active transaction.
* @todo package:orm???
*/
public
function
getInternalTransactionLevel
()
{
return
$this
->
transaction
->
getInternalTransactionLevel
();
}
/**
* Initiates a transaction.
*
* This method must only be used by Doctrine itself to initiate transactions.
* Userland-code must use {@link beginTransaction()}.
*
* @todo package:orm???
*/
public
function
beginInternalTransaction
(
$savepoint
=
null
)
{
return
$this
->
transaction
->
beginInternalTransaction
(
$savepoint
);
}
/**
* Creates a query with the specified name.
*
* @todo Implementation.
* @throws SomeException If there is no query registered with the given name.
*/
public
function
createNamedQuery
(
$name
)
{
//...
}
/**
* @todo Implementation.
*/
public
function
createNativeQuery
(
$sql
=
""
)
{
//...
}
/**
* @todo Implementation.
*/
public
function
createNamedNativeQuery
(
$name
)
{
//...
}
/**
* @todo Implementation.
*/
public
function
createCriteria
()
{
//...
}
/**
* Flushes all changes to objects that have been queued up to now to the database.
*
* @todo package:orm
*/
public
function
flush
()
{
$this
->
beginInternalTransaction
();
$this
->
_unitOfWork
->
flush
();
$this
->
commit
();
}
/**
* Sets the flush mode.
*
* @param string $flushMode
*/
public
function
setFlushMode
(
$flushMode
)
{
if
(
!
in_array
(
$flushMode
,
self
::
$_flushModes
))
{
throw
Doctrine_EntityManager_Exception
::
invalidFlushMode
();
}
$this
->
_flushMode
=
$flushMode
;
}
/**
* Gets the currently used flush mode.
*
* @return string
*/
public
function
getFlushMode
()
{
return
$this
->
_flushMode
;
}
/**
* Clears the persistence context, detaching all entities.
*
* @return void
* @todo package:orm
*/
public
function
clear
(
$entityName
=
null
)
{
if
(
$entityName
===
null
)
{
$this
->
_unitOfWork
->
detachAll
();
foreach
(
$this
->
_mappers
as
$mapper
)
{
$mapper
->
clear
();
// clear identity map of each mapper
}
}
else
{
$this
->
getMapper
(
$entityName
)
->
clear
();
}
}
/**
* Releases the EntityManager.
*
*/
public
function
close
()
{
}
/**
* getResultCacheDriver
*
* @return Doctrine_Cache_Interface
* @todo package:orm
*/
public
function
getResultCacheDriver
()
{
if
(
!
$this
->
getAttribute
(
Doctrine
::
ATTR_RESULT_CACHE
))
{
throw
new
Doctrine_Exception
(
'Result Cache driver not initialized.'
);
}
return
$this
->
getAttribute
(
Doctrine
::
ATTR_RESULT_CACHE
);
}
/**
* getQueryCacheDriver
*
* @return Doctrine_Cache_Interface
* @todo package:orm
*/
public
function
getQueryCacheDriver
()
{
if
(
!
$this
->
getAttribute
(
Doctrine
::
ATTR_QUERY_CACHE
))
{
throw
new
Doctrine_Exception
(
'Query Cache driver not initialized.'
);
}
return
$this
->
getAttribute
(
Doctrine
::
ATTR_QUERY_CACHE
);
}
/**
* Saves the given entity, persisting it's state.
*/
public
function
save
(
Doctrine_Entity
$entity
)
{
//...
}
/**
* Removes the given entity from the persistent store.
*/
public
function
delete
(
Doctrine_Entity
$entity
)
{
//...
}
/**
* Gets the repository for the given entity name.
*
* @return Doctrine_EntityRepository The repository.
* @todo Implementation.
*/
public
function
getRepository
(
$entityName
)
{
if
(
isset
(
$this
->
_repositories
[
$entityName
]))
{
return
$this
->
_repositories
[
$entityName
];
}
$metadata
=
$this
->
getClassMetadata
(
$entityName
);
$customRepositoryClassName
=
$metadata
->
getCustomRepositoryClass
();
if
(
$customRepositoryClassName
!==
null
)
{
$repository
=
new
$customRepositoryClassName
(
$entityName
,
$metadata
);
}
else
{
$repository
=
new
Doctrine_EntityRepository
(
$entityName
,
$metadata
);
}
$this
->
_repositories
[
$entityName
]
=
$repository
;
return
$repository
;
}
/**
* Creates an entity. Used to reconstitution as well as new creation.
*
* @param
* @param
* @return Doctrine_Entity
*/
public
function
createEntity
(
$className
,
array
$data
)
{
$className
=
$this
->
_getClassnameToReturn
(
$data
,
$className
);
$classMetadata
=
$this
->
getClassMetadata
(
$className
);
if
(
!
empty
(
$data
))
{
$identifierFieldNames
=
$classMetadata
->
getIdentifier
();
$isNew
=
false
;
foreach
(
$identifierFieldNames
as
$fieldName
)
{
if
(
!
isset
(
$data
[
$fieldName
]))
{
// id field not found return new entity
$isNew
=
true
;
break
;
}
$id
[]
=
$data
[
$fieldName
];
}
if
(
$isNew
)
{
return
new
$className
(
true
,
$data
);
}
$idHash
=
$this
->
_unitOfWork
->
getIdentifierHash
(
$id
);
if
(
$entity
=
$this
->
_unitOfWork
->
tryGetByIdHash
(
$idHash
,
$classMetadata
->
getRootClassName
()))
{
return
$entity
;
}
else
{
$entity
=
new
$className
(
false
,
$data
);
$this
->
_unitOfWork
->
registerIdentity
(
$entity
);
}
$data
=
array
();
}
else
{
$entity
=
new
$className
(
true
,
$data
);
}
return
$entity
;
}
/**
* Check the dataset for a discriminator column to determine the correct
* class to instantiate. If no discriminator column is found, the given
* classname will be returned.
*
* @return string The name of the class to instantiate.
* @todo Can be optimized performance-wise.
* @todo Move to EntityManager::createEntity()
*/
private
function
_getClassnameToReturn
(
array
$data
,
$className
)
{
$class
=
$this
->
getClassMetadata
(
$className
);
$discCol
=
$class
->
getInheritanceOption
(
'discriminatorColumn'
);
if
(
!
$discCol
)
{
return
$className
;
}
$discMap
=
$class
->
getInheritanceOption
(
'discriminatorMap'
);
if
(
isset
(
$data
[
$discCol
],
$discMap
[
$data
[
$discCol
]]))
{
return
$discMap
[
$data
[
$discCol
]];
}
else
{
return
$className
;
}
}
public
function
getUnitOfWork
()
{
return
$this
->
_unitOfWork
;
}
}
?>
\ No newline at end of file
lib/Doctrine/EntityManager/Exception.php
0 → 100644
View file @
d0ea5705
<?php
class
Doctrine_EntityManager_Exception
extends
Doctrine_Exception
{
public
static
function
invalidFlushMode
()
{
return
new
self
(
"Invalid flush mode."
);
}
public
static
function
noEntityManagerAvailable
()
{
return
new
self
(
"No EntityManager available."
);
}
public
static
function
entityAlreadyBound
(
$entityName
)
{
return
new
self
(
"The entity '
$entityName
' is already bound."
);
}
public
static
function
noManagerWithName
(
$emName
)
{
return
new
self
(
"EntityManager named '
$emName
' not found."
);
}
}
\ No newline at end of file
lib/Doctrine/EntityPersister/Abstract.php
0 → 100644
View file @
d0ea5705
<?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.phpdoctrine.org>.
*/
/**
*
*
* @author Roman Borschel <roman@code-factory.org>
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version $Revision: 3406 $
* @link www.phpdoctrine.org
* @since 2.0
*/
abstract
class
Doctrine_EntityPersister_Abstract
{
/**
* The names of all the fields that are available on entities created by this mapper.
*/
protected
$_fieldNames
=
array
();
/**
* Metadata object that descibes the mapping of the mapped entity class.
*
* @var Doctrine_ClassMetadata
*/
protected
$_classMetadata
;
/**
* The name of the domain class this mapper is used for.
*/
protected
$_domainClassName
;
/**
* The Doctrine_Connection object that the database connection of this mapper.
*
* @var Doctrine_Connection $conn
*/
protected
$_conn
;
/**
* The EntityManager.
*
* @var unknown_type
*/
protected
$_em
;
/**
* The concrete mapping strategy that is used.
*/
protected
$_mappingStrategy
;
/**
* Null object.
*/
private
$_nullObject
;
/**
* A list of registered entity listeners.
*/
private
$_entityListeners
=
array
();
/**
* Enter description here...
*
* @var unknown_type
* @todo To EntityManager.
*/
private
$_dataTemplate
=
array
();
/**
* Constructs a new mapper.
*
* @param string $name The name of the domain class this mapper is used for.
* @param Doctrine_Table $table The table object used for the mapping procedure.
* @throws Doctrine_Connection_Exception if there are no opened connections
*/
public
function
__construct
(
Doctrine_EntityManager
$em
,
Doctrine_ClassMetadata
$classMetadata
)
{
$this
->
_em
=
$em
;
$this
->
_domainClassName
=
$classMetadata
->
getClassName
();
$this
->
_conn
=
$classMetadata
->
getConnection
();
$this
->
_classMetadata
=
$classMetadata
;
$this
->
_nullObject
=
Doctrine_Null
::
$INSTANCE
;
}
/**
* Assumes that the keys of the given field array are field names and converts
* them to column names.
*
* @return array
*/
protected
function
_convertFieldToColumnNames
(
array
$fields
,
Doctrine_ClassMetadata
$class
)
{
$converted
=
array
();
foreach
(
$fields
as
$fieldName
=>
$value
)
{
$converted
[
$class
->
getColumnName
(
$fieldName
)]
=
$value
;
}
return
$converted
;
}
/**
* deletes all related composites
* this method is always called internally when a record is deleted
*
* @throws PDOException if something went wrong at database level
* @return void
*/
protected
function
_deleteComposites
(
Doctrine_Entity
$record
)
{
$classMetadata
=
$this
->
_classMetadata
;
foreach
(
$classMetadata
->
getRelations
()
as
$fk
)
{
if
(
$fk
->
isComposite
())
{
$obj
=
$record
->
get
(
$fk
->
getAlias
());
if
(
$obj
instanceof
Doctrine_Entity
&&
$obj
->
state
()
!=
Doctrine_Entity
::
STATE_LOCKED
)
{
$obj
->
delete
(
$this
->
_mapper
->
getConnection
());
}
}
}
}
/**
* sets the connection for this class
*
* @params Doctrine_Connection a connection object
* @return Doctrine_Table this object
* @todo refactor
*/
/*public function setConnection(Doctrine_Connection $conn)
{
$this->_conn = $conn;
return $this;
}*/
/**
* Returns the connection the mapper is currently using.
*
* @return Doctrine_Connection|null The connection object.
*/
public
function
getConnection
()
{
return
$this
->
_conn
;
}
public
function
getEntityManager
()
{
return
$this
->
_em
;
}
/**
* creates a new record
*
* @param $array an array where keys are field names and
* values representing field values
* @return Doctrine_Entity the created record object
* @todo To EntityManager.
*/
public
function
create
(
array
$array
=
array
())
{
$record
=
new
$this
->
_domainClassName
();
$record
->
fromArray
(
$array
);
return
$record
;
}
public
function
addEntityListener
(
Doctrine_Record_Listener
$listener
)
{
if
(
!
in_array
(
$listener
,
$this
->
_entityListeners
))
{
$this
->
_entityListeners
[]
=
$listener
;
return
true
;
}
return
false
;
}
public
function
removeEntityListener
(
Doctrine_Record_Listener
$listener
)
{
if
(
$key
=
array_search
(
$listener
,
$this
->
_entityListeners
,
true
))
{
unset
(
$this
->
_entityListeners
[
$key
]);
return
true
;
}
return
false
;
}
public
function
notifyEntityListeners
(
Doctrine_Entity
$entity
,
$callback
,
$eventType
)
{
if
(
$this
->
_entityListeners
)
{
$event
=
new
Doctrine_Event
(
$entity
,
$eventType
);
foreach
(
$this
->
_entityListeners
as
$listener
)
{
$listener
->
$callback
(
$event
);
}
}
}
/**
* Enter description here...
*
* @param Doctrine_Entity $entity
* @return unknown
* @todo To EntityManager
*/
public
function
detach
(
Doctrine_Entity
$entity
)
{
return
$this
->
_conn
->
getUnitOfWork
()
->
detach
(
$entity
);
}
/**
* clear
* clears the first level cache (identityMap)
*
* @return void
* @todo what about a more descriptive name? clearIdentityMap?
* @todo To EntityManager
*/
public
function
clear
()
{
$this
->
_conn
->
getUnitOfWork
()
->
clearIdentitiesForEntity
(
$this
->
_classMetadata
->
getRootClassName
());
}
/**
* addRecord
* adds a record to identity map
*
* @param Doctrine_Entity $record record to be added
* @return boolean
* @todo Better name? registerRecord? Move elsewhere to the new location of the identity maps.
* @todo Remove.
*/
public
function
addRecord
(
Doctrine_Entity
$record
)
{
if
(
$this
->
_conn
->
unitOfWork
->
contains
(
$record
))
{
return
false
;
}
$this
->
_conn
->
unitOfWork
->
registerIdentity
(
$record
);
return
true
;
}
/**
* Tells the mapper to manage the entity if it's not already managed.
*
* @return boolean TRUE if the entity was previously not managed and is now managed,
* FALSE otherwise (the entity is already managed).
* @todo Remove.
*/
public
function
manage
(
Doctrine_Entity
$record
)
{
return
$this
->
_conn
->
unitOfWork
->
manage
(
$record
);
}
/**
* removeRecord
* removes a record from the identity map, returning true if the record
* was found and removed and false if the record wasn't found.
*
* @param Doctrine_Entity $record record to be removed
* @return boolean
* @todo Move elsewhere to the new location of the identity maps.
*/
public
function
removeRecord
(
Doctrine_Entity
$record
)
{
if
(
$this
->
_conn
->
unitOfWork
->
contains
(
$record
))
{
$this
->
_conn
->
unitOfWork
->
unregisterIdentity
(
$record
);
return
true
;
}
return
false
;
}
/**
* getRecord
* First checks if record exists in identityMap, if not
* returns a new record.
*
* @return Doctrine_Entity
* @todo To EntityManager.
*/
public
function
getRecord
(
array
$data
)
{
if
(
!
empty
(
$data
))
{
$identifierFieldNames
=
$this
->
_classMetadata
->
getIdentifier
();
$found
=
false
;
foreach
(
$identifierFieldNames
as
$fieldName
)
{
if
(
!
isset
(
$data
[
$fieldName
]))
{
// primary key column not found return new record
$found
=
true
;
break
;
}
$id
[]
=
$data
[
$fieldName
];
}
if
(
$found
)
{
return
new
$this
->
_domainClassName
(
true
,
$data
);
}
$idHash
=
$this
->
_conn
->
unitOfWork
->
getIdentifierHash
(
$id
);
if
(
$record
=
$this
->
_conn
->
unitOfWork
->
tryGetByIdHash
(
$idHash
,
$this
->
_classMetadata
->
getRootClassName
()))
{
$record
->
hydrate
(
$data
);
}
else
{
$record
=
new
$this
->
_domainClassName
(
false
,
$data
);
$this
->
_conn
->
unitOfWork
->
registerIdentity
(
$record
);
}
$data
=
array
();
}
else
{
$record
=
new
$this
->
_domainClassName
(
true
,
$data
);
}
return
$record
;
}
/**
* @param $id database row id
* @todo Looks broken. Figure out an implementation and decide whether its needed.
*/
final
public
function
getProxy
(
$id
=
null
)
{
if
(
$id
!==
null
)
{
$identifierColumnNames
=
$this
->
_classMetadata
->
getIdentifierColumnNames
();
$query
=
'SELECT '
.
implode
(
', '
,
$identifierColumnNames
)
.
' FROM '
.
$this
->
_classMetadata
->
getTableName
()
.
' WHERE '
.
implode
(
' = ? && '
,
$identifierColumnNames
)
.
' = ?'
;
$query
=
$this
->
applyInheritance
(
$query
);
$params
=
array_merge
(
array
(
$id
),
array
());
$data
=
$this
->
_conn
->
execute
(
$query
,
$params
)
->
fetch
(
PDO
::
FETCH_ASSOC
);
if
(
$data
===
false
)
{
return
false
;
}
}
return
$this
->
getRecord
(
$data
);
}
/**
* applyInheritance
* @param $where query where part to be modified
* @return string query where part with column aggregation inheritance added
* @todo What to do with this? Remove if possible.
*/
final
public
function
applyInheritance
(
$where
)
{
$discCol
=
$this
->
_classMetadata
->
getInheritanceOption
(
'discriminatorColumn'
);
if
(
!
$discCol
)
{
return
$where
;
}
$discMap
=
$this
->
_classMetadata
->
getInheritanceOption
(
'discriminatorMap'
);
$inheritanceMap
=
array
(
$discCol
=>
array_search
(
$this
->
_domainClassName
,
$discMap
));
if
(
!
empty
(
$inheritanceMap
))
{
$a
=
array
();
foreach
(
$inheritanceMap
as
$column
=>
$value
)
{
$a
[]
=
$column
.
' = ?'
;
}
$i
=
implode
(
' AND '
,
$a
);
$where
.=
' AND '
.
$i
;
}
return
$where
;
}
/**
* prepareValue
* this method performs special data preparation depending on
* the type of the given column
*
* 1. It unserializes array and object typed columns
* 2. Uncompresses gzip typed columns
* 3. Gets the appropriate enum values for enum typed columns
* 4. Initializes special null object pointer for null values (for fast column existence checking purposes)
*
* example:
* <code type='php'>
* $field = 'name';
* $value = null;
* $table->prepareValue($field, $value); // Doctrine_Null
* </code>
*
* @throws Doctrine_Table_Exception if unserialization of array/object typed column fails or
* @throws Doctrine_Table_Exception if uncompression of gzip typed column fails *
* @param string $field the name of the field
* @param string $value field value
* @param string $typeHint A hint on the type of the value. If provided, the type lookup
* for the field can be skipped. Used i.e. during hydration to
* improve performance on large and/or complex results.
* @return mixed prepared value
* @todo To EntityManager. Make private and use in createEntity().
* .. Or, maybe better: Move to hydrator for performance reasons.
*/
public
function
prepareValue
(
$fieldName
,
$value
,
$typeHint
=
null
)
{
if
(
$value
===
$this
->
_nullObject
)
{
return
$this
->
_nullObject
;
}
else
if
(
$value
===
null
)
{
return
null
;
}
else
{
$type
=
is_null
(
$typeHint
)
?
$this
->
_classMetadata
->
getTypeOf
(
$fieldName
)
:
$typeHint
;
switch
(
$type
)
{
case
'integer'
:
case
'string'
;
// don't do any casting here PHP INT_MAX is smaller than what the databases support
break
;
case
'enum'
:
return
$this
->
_classMetadata
->
enumValue
(
$fieldName
,
$value
);
break
;
case
'boolean'
:
return
(
boolean
)
$value
;
break
;
case
'array'
:
case
'object'
:
if
(
is_string
(
$value
))
{
$value
=
unserialize
(
$value
);
if
(
$value
===
false
)
{
throw
new
Doctrine_Mapper_Exception
(
'Unserialization of '
.
$fieldName
.
' failed.'
);
}
return
$value
;
}
break
;
case
'gzip'
:
$value
=
gzuncompress
(
$value
);
if
(
$value
===
false
)
{
throw
new
Doctrine_Mapper_Exception
(
'Uncompressing of '
.
$fieldName
.
' failed.'
);
}
return
$value
;
break
;
}
}
return
$value
;
}
/**
* getComponentName
*
* @return void
* @deprecated Use getMappedClassName()
*/
public
function
getComponentName
()
{
return
$this
->
_domainClassName
;
}
/**
* Gets the name of the class the mapper is used for.
*/
public
function
getMappedClassName
()
{
return
$this
->
_domainClassName
;
}
/**
* Saves an entity and all it's related entities.
*
* @param Doctrine_Entity $record The entity to save.
* @param Doctrine_Connection $conn The connection to use. Will default to the mapper's
* connection.
* @throws Doctrine_Mapper_Exception If the mapper is unable to save the given entity.
*/
public
function
save
(
Doctrine_Entity
$record
,
Doctrine_Connection
$conn
=
null
)
{
if
(
!
(
$record
instanceof
$this
->
_domainClassName
))
{
throw
new
Doctrine_Mapper_Exception
(
"Mapper of type "
.
$this
->
_domainClassName
.
"
can't save instances of type"
.
get_class
(
$record
)
.
"."
);
}
if
(
$conn
===
null
)
{
$conn
=
$this
->
_conn
;
}
$state
=
$record
->
state
();
if
(
$state
===
Doctrine_Entity
::
STATE_LOCKED
)
{
return
false
;
}
$record
->
state
(
Doctrine_Entity
::
STATE_LOCKED
);
try
{
$conn
->
beginInternalTransaction
();
$saveLater
=
$this
->
_saveRelated
(
$record
);
$record
->
state
(
$state
);
if
(
$record
->
isValid
())
{
$this
->
_insertOrUpdate
(
$record
);
}
else
{
$conn
->
transaction
->
addInvalid
(
$record
);
}
$state
=
$record
->
state
();
$record
->
state
(
Doctrine_Entity
::
STATE_LOCKED
);
foreach
(
$saveLater
as
$fk
)
{
$alias
=
$fk
->
getAlias
();
if
(
$record
->
hasReference
(
$alias
))
{
$obj
=
$record
->
$alias
;
// check that the related object is not an instance of Doctrine_Null
if
(
!
(
$obj
instanceof
Doctrine_Null
))
{
$obj
->
save
(
$conn
);
}
}
}
// save the MANY-TO-MANY associations
$this
->
saveAssociations
(
$record
);
// reset state
$record
->
state
(
$state
);
$conn
->
commit
();
}
catch
(
Exception
$e
)
{
$conn
->
rollback
();
throw
$e
;
}
return
true
;
}
/**
* Inserts or updates an entity, depending on it's state.
*
* @param Doctrine_Entity $record The entity to insert/update.
*/
protected
function
_insertOrUpdate
(
Doctrine_Entity
$record
)
{
$record
->
preSave
();
$this
->
notifyEntityListeners
(
$record
,
'preSave'
,
Doctrine_Event
::
RECORD_SAVE
);
switch
(
$record
->
state
())
{
case
Doctrine_Entity
::
STATE_TDIRTY
:
$this
->
_insert
(
$record
);
break
;
case
Doctrine_Entity
::
STATE_DIRTY
:
case
Doctrine_Entity
::
STATE_PROXY
:
$this
->
_update
(
$record
);
break
;
case
Doctrine_Entity
::
STATE_CLEAN
:
case
Doctrine_Entity
::
STATE_TCLEAN
:
// do nothing
break
;
}
$record
->
postSave
();
$this
->
notifyEntityListeners
(
$record
,
'postSave'
,
Doctrine_Event
::
RECORD_SAVE
);
}
/**
* saves the given record
*
* @param Doctrine_Entity $record
* @return void
*/
public
function
saveSingleRecord
(
Doctrine_Entity
$record
)
{
$this
->
_insertOrUpdate
(
$record
);
}
/**
* _saveRelated
* saves all related records to $record
*
* @throws PDOException if something went wrong at database level
* @param Doctrine_Entity $record
*/
protected
function
_saveRelated
(
Doctrine_Entity
$record
)
{
$saveLater
=
array
();
foreach
(
$record
->
getReferences
()
as
$k
=>
$v
)
{
$rel
=
$record
->
getTable
()
->
getRelation
(
$k
);
$local
=
$rel
->
getLocal
();
$foreign
=
$rel
->
getForeign
();
if
(
$rel
instanceof
Doctrine_Relation_ForeignKey
)
{
$saveLater
[
$k
]
=
$rel
;
}
else
if
(
$rel
instanceof
Doctrine_Relation_LocalKey
)
{
// ONE-TO-ONE relationship
$obj
=
$record
->
get
(
$rel
->
getAlias
());
// Protection against infinite function recursion before attempting to save
if
(
$obj
instanceof
Doctrine_Entity
&&
$obj
->
isModified
())
{
$obj
->
save
(
$this
->
_conn
);
/** Can this be removed?
$id = array_values($obj->identifier());
foreach ((array) $rel->getLocal() as $k => $field) {
$record->set($field, $id[$k]);
}
*/
}
}
}
return
$saveLater
;
}
/**
* saveAssociations
*
* this method takes a diff of one-to-many / many-to-many original and
* current collections and applies the changes
*
* for example if original many-to-many related collection has records with
* primary keys 1,2 and 3 and the new collection has records with primary keys
* 3, 4 and 5, this method would first destroy the associations to 1 and 2 and then
* save new associations to 4 and 5
*
* @throws Doctrine_Connection_Exception if something went wrong at database level
* @param Doctrine_Entity $record
* @return void
*/
public
function
saveAssociations
(
Doctrine_Entity
$record
)
{
foreach
(
$record
->
getReferences
()
as
$relationName
=>
$relatedObject
)
{
if
(
$relatedObject
===
Doctrine_Null
::
$INSTANCE
)
{
continue
;
}
$rel
=
$record
->
getTable
()
->
getRelation
(
$relationName
);
if
(
$rel
instanceof
Doctrine_Relation_Association
)
{
$relatedObject
->
save
(
$this
->
_conn
);
$assocTable
=
$rel
->
getAssociationTable
();
foreach
(
$relatedObject
->
getDeleteDiff
()
as
$r
)
{
$query
=
'DELETE FROM '
.
$assocTable
->
getTableName
()
.
' WHERE '
.
$rel
->
getForeign
()
.
' = ?'
.
' AND '
.
$rel
->
getLocal
()
.
' = ?'
;
// FIXME: composite key support
$ids1
=
$r
->
identifier
();
$id1
=
count
(
$ids1
)
>
0
?
array_pop
(
$ids1
)
:
null
;
$ids2
=
$record
->
identifier
();
$id2
=
count
(
$ids2
)
>
0
?
array_pop
(
$ids2
)
:
null
;
$this
->
_conn
->
execute
(
$query
,
array
(
$id1
,
$id2
));
}
$assocMapper
=
$this
->
_conn
->
getMapper
(
$assocTable
->
getComponentName
());
foreach
(
$relatedObject
->
getInsertDiff
()
as
$r
)
{
$assocRecord
=
$assocMapper
->
create
();
$assocRecord
->
set
(
$assocTable
->
getFieldName
(
$rel
->
getForeign
()),
$r
);
$assocRecord
->
set
(
$assocTable
->
getFieldName
(
$rel
->
getLocal
()),
$record
);
$assocMapper
->
save
(
$assocRecord
);
}
}
}
}
/**
* Updates an entity.
*
* @param Doctrine_Entity $record record to be updated
* @return boolean whether or not the update was successful
* @todo Move to Doctrine_Table (which will become Doctrine_Mapper).
*/
protected
function
_update
(
Doctrine_Entity
$record
)
{
$record
->
preUpdate
();
$this
->
notifyEntityListeners
(
$record
,
'preUpdate'
,
Doctrine_Event
::
RECORD_UPDATE
);
$table
=
$this
->
_classMetadata
;
$this
->
_doUpdate
(
$record
);
$record
->
postUpdate
();
$this
->
notifyEntityListeners
(
$record
,
'postUpdate'
,
Doctrine_Event
::
RECORD_UPDATE
);
return
true
;
}
abstract
protected
function
_doUpdate
(
Doctrine_Entity
$entity
);
/**
* Inserts an entity.
*
* @param Doctrine_Entity $record record to be inserted
* @return boolean
*/
protected
function
_insert
(
Doctrine_Entity
$record
)
{
$record
->
preInsert
();
$this
->
notifyEntityListeners
(
$record
,
'preInsert'
,
Doctrine_Event
::
RECORD_INSERT
);
$this
->
_doInsert
(
$record
);
$this
->
addRecord
(
$record
);
$record
->
postInsert
();
$this
->
notifyEntityListeners
(
$record
,
'postInsert'
,
Doctrine_Event
::
RECORD_INSERT
);
return
true
;
}
abstract
protected
function
_doInsert
(
Doctrine_Entity
$entity
);
/**
* Deletes given entity and all it's related entities.
*
* Triggered Events: onPreDelete, onDelete.
*
* @return boolean true on success, false on failure
* @throws Doctrine_Mapper_Exception
*/
public
function
delete
(
Doctrine_Entity
$record
,
Doctrine_Connection
$conn
=
null
)
{
if
(
!
$record
->
exists
())
{
return
false
;
}
if
(
!
(
$record
instanceof
$this
->
_domainClassName
))
{
throw
new
Doctrine_Mapper_Exception
(
"Mapper of type "
.
$this
->
_domainClassName
.
"
can't save instances of type"
.
get_class
(
$record
)
.
"."
);
}
if
(
$conn
==
null
)
{
$conn
=
$this
->
_conn
;
}
$record
->
preDelete
();
$this
->
notifyEntityListeners
(
$record
,
'preDelete'
,
Doctrine_Event
::
RECORD_DELETE
);
$table
=
$this
->
_classMetadata
;
$state
=
$record
->
state
();
$record
->
state
(
Doctrine_Entity
::
STATE_LOCKED
);
$this
->
_doDelete
(
$record
);
$record
->
postDelete
();
$this
->
notifyEntityListeners
(
$record
,
'postDelete'
,
Doctrine_Event
::
RECORD_DELETE
);
return
true
;
}
abstract
protected
function
_doDelete
(
Doctrine_Entity
$entity
);
/**
* Inserts a row into a table.
*
* @todo This method could be used to allow mapping to secondary table(s).
* @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable
*/
protected
function
_insertRow
(
$tableName
,
array
$data
)
{
$this
->
_conn
->
insert
(
$tableName
,
$data
);
}
/**
* Deletes rows of a table.
*
* @todo This method could be used to allow mapping to secondary table(s).
* @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable
*/
protected
function
_deleteRow
(
$tableName
,
array
$identifierToMatch
)
{
$this
->
_conn
->
delete
(
$tableName
,
$identifierToMatch
);
}
/**
* Deletes rows of a table.
*
* @todo This method could be used to allow mapping to secondary table(s).
* @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable
*/
protected
function
_updateRow
(
$tableName
,
array
$data
,
array
$identifierToMatch
)
{
$this
->
_conn
->
update
(
$tableName
,
$data
,
$identifierToMatch
);
}
public
function
getClassMetadata
()
{
return
$this
->
_classMetadata
;
}
public
function
getFieldName
(
$columnName
)
{
return
$this
->
_classMetadata
->
getFieldName
(
$columnName
);
}
public
function
getFieldNames
()
{
if
(
$this
->
_fieldNames
)
{
return
$this
->
_fieldNames
;
}
$this
->
_fieldNames
=
$this
->
_classMetadata
->
getFieldNames
();
return
$this
->
_fieldNames
;
}
public
function
getOwningClass
(
$fieldName
)
{
return
$this
->
_classMetadata
;
}
/* Hooks used during SQL query construction to manipulate the query. */
/**
* Callback that is invoked during the SQL construction process.
*/
public
function
getCustomJoins
()
{
return
array
();
}
/**
* Callback that is invoked during the SQL construction process.
*/
public
function
getCustomFields
()
{
return
array
();
}
}
lib/Doctrine/EntityPersister/Exception.php
0 → 100644
View file @
d0ea5705
<?php
class
Doctrine_EntityPersister_Exception
extends
Doctrine_Exception
{
}
?>
\ No newline at end of file
lib/Doctrine/EntityPersister/JoinedSubclass.php
0 → 100644
View file @
d0ea5705
<?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.phpdoctrine.org>.
*/
/**
* The joined mapping strategy maps a single entity instance to several tables in the
* database as it is defined by <tt>Class Table Inheritance</tt>.
*
* @author Roman Borschel <roman@code-factory.org>
* @package Doctrine
* @subpackage JoinedSubclass
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version $Revision$
* @link www.phpdoctrine.org
* @since 2.0
*/
class
Doctrine_EntityPersister_JoinedSubclass
extends
Doctrine_EntityPersister_Abstract
{
protected
$_columnNameFieldNameMap
=
array
();
/**
* Inserts an entity that is part of a Class Table Inheritance hierarchy.
*
* @param Doctrine_Entity $record record to be inserted
* @return boolean
*/
protected
function
_doInsert
(
Doctrine_Entity
$record
)
{
$class
=
$this
->
_classMetadata
;
$conn
=
$this
->
_conn
;
$dataSet
=
$this
->
_groupFieldsByDefiningClass
(
$record
);
$component
=
$class
->
getClassName
();
$classes
=
$class
->
getParentClasses
();
array_unshift
(
$classes
,
$component
);
try
{
$conn
->
beginInternalTransaction
();
$identifier
=
null
;
foreach
(
array_reverse
(
$classes
)
as
$k
=>
$parent
)
{
$parentClass
=
$conn
->
getClassMetadata
(
$parent
);
if
(
$k
==
0
)
{
$identifierType
=
$parentClass
->
getIdentifierType
();
if
(
$identifierType
==
Doctrine
::
IDENTIFIER_AUTOINC
)
{
$this
->
_insertRow
(
$parentClass
->
getTableName
(),
$dataSet
[
$parent
]);
$identifier
=
$conn
->
sequence
->
lastInsertId
();
}
else
if
(
$identifierType
==
Doctrine
::
IDENTIFIER_SEQUENCE
)
{
$seq
=
$record
->
getClassMetadata
()
->
getTableOption
(
'sequenceName'
);
if
(
!
empty
(
$seq
))
{
$id
=
$conn
->
sequence
->
nextId
(
$seq
);
$identifierFields
=
(
array
)
$parentClass
->
getIdentifier
();
$dataSet
[
$parent
][
$identifierFields
[
0
]]
=
$id
;
$this
->
_insertRow
(
$parentClass
->
getTableName
(),
$dataSet
[
$parent
]);
}
}
else
{
throw
new
Doctrine_Mapper_Exception
(
"Unsupported identifier type '
$identifierType
'."
);
}
$record
->
assignIdentifier
(
$identifier
);
}
else
{
foreach
((
array
)
$record
->
identifier
()
as
$id
=>
$value
)
{
$dataSet
[
$parent
][
$parentClass
->
getColumnName
(
$id
)]
=
$value
;
}
$this
->
_insertRow
(
$parentClass
->
getTableName
(),
$dataSet
[
$parent
]);
}
}
$conn
->
commit
();
}
catch
(
Exception
$e
)
{
$conn
->
rollback
();
throw
$e
;
}
return
true
;
}
/**
* Updates an entity that is part of a Class Table Inheritance hierarchy.
*
* @param Doctrine_Entity $record record to be updated
* @return boolean whether or not the update was successful
* @todo Move to Doctrine_Table (which will become Doctrine_Mapper).
*/
protected
function
_doUpdate
(
Doctrine_Entity
$record
)
{
$conn
=
$this
->
_conn
;
$classMetadata
=
$this
->
_classMetadata
;
$identifier
=
$this
->
_convertFieldToColumnNames
(
$record
->
identifier
(),
$classMetadata
);
$dataSet
=
$this
->
_groupFieldsByDefiningClass
(
$record
);
$component
=
$classMetadata
->
getClassName
();
$classes
=
$classMetadata
->
getParentClasses
();
array_unshift
(
$classes
,
$component
);
foreach
(
$record
as
$field
=>
$value
)
{
if
(
$value
instanceof
Doctrine_Entity
)
{
if
(
!
$value
->
exists
())
{
$value
->
save
();
}
$idValues
=
$value
->
identifier
();
$record
->
set
(
$field
,
$idValues
[
0
]);
}
}
foreach
(
array_reverse
(
$classes
)
as
$class
)
{
$parentTable
=
$conn
->
getClassMetadata
(
$class
);
$this
->
_updateRow
(
$parentTable
->
getTableName
(),
$dataSet
[
$class
],
$identifier
);
}
$record
->
assignIdentifier
(
true
);
return
true
;
}
/**
* Deletes an entity that is part of a Class Table Inheritance hierarchy.
*
*/
protected
function
_doDelete
(
Doctrine_Entity
$record
)
{
$conn
=
$this
->
_conn
;
try
{
$class
=
$this
->
_classMetadata
;
$conn
->
beginInternalTransaction
();
$this
->
_deleteComposites
(
$record
);
$record
->
state
(
Doctrine_Entity
::
STATE_TDIRTY
);
$identifier
=
$this
->
_convertFieldToColumnNames
(
$record
->
identifier
(),
$class
);
// run deletions, starting from the class, upwards the hierarchy
$conn
->
delete
(
$class
->
getTableName
(),
$identifier
);
foreach
(
$class
->
getParentClasses
()
as
$parent
)
{
$parentClass
=
$conn
->
getClassMetadata
(
$parent
);
$this
->
_deleteRow
(
$parentClass
->
getTableName
(),
$identifier
);
}
$record
->
state
(
Doctrine_Entity
::
STATE_TCLEAN
);
$this
->
removeRecord
(
$record
);
// @todo should be done in the unitofwork
$conn
->
commit
();
}
catch
(
Exception
$e
)
{
$conn
->
rollback
();
throw
$e
;
}
return
true
;
}
/**
* Adds all parent classes as INNER JOINs and subclasses as OUTER JOINs
* to the query.
*
* Callback that is invoked during the SQL construction process.
*
* @return array The custom joins in the format <className> => <joinType>
*/
public
function
getCustomJoins
()
{
$customJoins
=
array
();
$classMetadata
=
$this
->
_classMetadata
;
foreach
(
$classMetadata
->
getParentClasses
()
as
$parentClass
)
{
$customJoins
[
$parentClass
]
=
'INNER'
;
}
foreach
(
$classMetadata
->
getSubclasses
()
as
$subClass
)
{
if
(
$subClass
!=
$this
->
getComponentName
())
{
$customJoins
[
$subClass
]
=
'LEFT'
;
}
}
return
$customJoins
;
}
/**
* Adds the discriminator column to the selected fields in a query as well as
* all fields of subclasses. In Class Table Inheritance the default behavior is that
* all subclasses are joined in through OUTER JOINs when querying a base class.
*
* Callback that is invoked during the SQL construction process.
*
* @return array An array with the field names that will get added to the query.
*/
public
function
getCustomFields
()
{
$classMetadata
=
$this
->
_classMetadata
;
$conn
=
$this
->
_conn
;
$fields
=
array
(
$classMetadata
->
getInheritanceOption
(
'discriminatorColumn'
));
if
(
$classMetadata
->
getSubclasses
())
{
foreach
(
$classMetadata
->
getSubclasses
()
as
$subClass
)
{
$fields
=
array_merge
(
$conn
->
getClassMetadata
(
$subClass
)
->
getFieldNames
(),
$fields
);
}
}
return
array_unique
(
$fields
);
}
/**
*
*/
public
function
getFieldNames
()
{
if
(
$this
->
_fieldNames
)
{
return
$this
->
_fieldNames
;
}
$fieldNames
=
$this
->
_classMetadata
->
getFieldNames
();
$this
->
_fieldNames
=
array_unique
(
$fieldNames
);
return
$fieldNames
;
}
/**
*
*/
public
function
getFieldName
(
$columnName
)
{
if
(
isset
(
$this
->
_columnNameFieldNameMap
[
$columnName
]))
{
return
$this
->
_columnNameFieldNameMap
[
$columnName
];
}
$classMetadata
=
$this
->
_classMetadata
;
$conn
=
$this
->
_conn
;
if
(
$classMetadata
->
hasColumn
(
$columnName
))
{
$this
->
_columnNameFieldNameMap
[
$columnName
]
=
$classMetadata
->
getFieldName
(
$columnName
);
return
$this
->
_columnNameFieldNameMap
[
$columnName
];
}
foreach
(
$classMetadata
->
getSubclasses
()
as
$subClass
)
{
$subTable
=
$conn
->
getClassMetadata
(
$subClass
);
if
(
$subTable
->
hasColumn
(
$columnName
))
{
$this
->
_columnNameFieldNameMap
[
$columnName
]
=
$subTable
->
getFieldName
(
$columnName
);
return
$this
->
_columnNameFieldNameMap
[
$columnName
];
}
}
throw
new
Doctrine_Mapper_Exception
(
"No field name found for column name '
$columnName
'."
);
}
/**
*
* @todo Looks like this better belongs into the ClassMetadata class.
*/
public
function
getOwningClass
(
$fieldName
)
{
$conn
=
$this
->
_conn
;
$classMetadata
=
$this
->
_classMetadata
;
if
(
$classMetadata
->
hasField
(
$fieldName
)
&&
!
$classMetadata
->
isInheritedField
(
$fieldName
))
{
return
$classMetadata
;
}
foreach
(
$classMetadata
->
getParentClasses
()
as
$parentClass
)
{
$parentTable
=
$conn
->
getClassMetadata
(
$parentClass
);
if
(
$parentTable
->
hasField
(
$fieldName
)
&&
!
$parentTable
->
isInheritedField
(
$fieldName
))
{
return
$parentTable
;
}
}
foreach
((
array
)
$classMetadata
->
getSubclasses
()
as
$subClass
)
{
$subTable
=
$conn
->
getClassMetadata
(
$subClass
);
if
(
$subTable
->
hasField
(
$fieldName
)
&&
!
$subTable
->
isInheritedField
(
$fieldName
))
{
return
$subTable
;
}
}
throw
new
Doctrine_Mapper_Exception
(
"Unable to find defining class of field '
$fieldName
'."
);
}
/**
* Analyzes the fields of the entity and creates a map in which the field names
* are grouped by the class names they belong to.
*
* @return array
*/
protected
function
_groupFieldsByDefiningClass
(
Doctrine_Entity
$record
)
{
$conn
=
$this
->
_conn
;
$classMetadata
=
$this
->
_classMetadata
;
$dataSet
=
array
();
$component
=
$classMetadata
->
getClassName
();
$array
=
$record
->
getPrepared
();
$classes
=
array_merge
(
array
(
$component
),
$classMetadata
->
getParentClasses
());
foreach
(
$classes
as
$class
)
{
$dataSet
[
$class
]
=
array
();
$parentClassMetadata
=
$conn
->
getClassMetadata
(
$class
);
foreach
(
$parentClassMetadata
->
getColumns
()
as
$columnName
=>
$definition
)
{
if
((
isset
(
$definition
[
'primary'
])
&&
$definition
[
'primary'
]
===
true
)
||
(
isset
(
$definition
[
'inherited'
])
&&
$definition
[
'inherited'
]
===
true
))
{
continue
;
}
$fieldName
=
$classMetadata
->
getFieldName
(
$columnName
);
if
(
!
array_key_exists
(
$fieldName
,
$array
))
{
continue
;
}
$dataSet
[
$class
][
$columnName
]
=
$array
[
$fieldName
];
}
}
return
$dataSet
;
}
}
lib/Doctrine/EntityPersister/Standard.php
0 → 100644
View file @
d0ea5705
<?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.phpdoctrine.org>.
*/
/**
* The default mapping strategy maps a single entity instance to a single database table,
* as is the case in Single Table Inheritance & Concrete Table Inheritance.
*
* @author Roman Borschel <roman@code-factory.org>
* @package Doctrine
* @subpackage Abstract
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version $Revision$
* @link www.phpdoctrine.org
* @since 2.0
*/
class
Doctrine_EntityPersister_Standard
extends
Doctrine_EntityPersister_Abstract
{
/**
* Deletes an entity.
*/
protected
function
_doDelete
(
Doctrine_Entity
$record
)
{
$conn
=
$this
->
_conn
;
$metadata
=
$this
->
_classMetadata
;
try
{
$conn
->
beginInternalTransaction
();
$this
->
_deleteComposites
(
$record
);
$record
->
state
(
Doctrine_Entity
::
STATE_TDIRTY
);
$identifier
=
$this
->
_convertFieldToColumnNames
(
$record
->
identifier
(),
$metadata
);
$this
->
_deleteRow
(
$metadata
->
getTableName
(),
$identifier
);
$record
->
state
(
Doctrine_Entity
::
STATE_TCLEAN
);
$this
->
removeRecord
(
$record
);
$conn
->
commit
();
}
catch
(
Exception
$e
)
{
$conn
->
rollback
();
throw
$e
;
}
}
/**
* Inserts a single entity into the database, without any related entities.
*
* @param Doctrine_Entity $record The entity to insert.
*/
protected
function
_doInsert
(
Doctrine_Entity
$record
)
{
$conn
=
$this
->
_conn
;
$fields
=
$record
->
getPrepared
();
if
(
empty
(
$fields
))
{
return
false
;
}
//$class = $record->getClassMetadata();
$class
=
$this
->
_classMetadata
;
$identifier
=
$class
->
getIdentifier
();
$fields
=
$this
->
_convertFieldToColumnNames
(
$fields
,
$class
);
$seq
=
$class
->
getTableOption
(
'sequenceName'
);
if
(
!
empty
(
$seq
))
{
$id
=
$conn
->
sequence
->
nextId
(
$seq
);
$seqName
=
$identifier
[
0
];
$fields
[
$seqName
]
=
$id
;
$record
->
assignIdentifier
(
$id
);
}
$this
->
_insertRow
(
$class
->
getTableName
(),
$fields
);
if
(
empty
(
$seq
)
&&
count
(
$identifier
)
==
1
&&
$class
->
getIdentifierType
()
!=
Doctrine
::
IDENTIFIER_NATURAL
)
{
if
(
strtolower
(
$conn
->
getName
())
==
'pgsql'
)
{
$seq
=
$class
->
getTableName
()
.
'_'
.
$identifier
[
0
];
}
$id
=
$conn
->
sequence
->
lastInsertId
(
$seq
);
if
(
!
$id
)
{
throw
new
Doctrine_Mapper_Exception
(
"Couldn't get last insert identifier."
);
}
$record
->
assignIdentifier
(
$id
);
}
else
{
$record
->
assignIdentifier
(
true
);
}
}
/**
* Updates an entity.
*/
protected
function
_doUpdate
(
Doctrine_Entity
$record
)
{
$conn
=
$this
->
_conn
;
$classMetadata
=
$this
->
_classMetadata
;
$identifier
=
$this
->
_convertFieldToColumnNames
(
$record
->
identifier
(),
$classMetadata
);
$data
=
$this
->
_convertFieldToColumnNames
(
$record
->
getPrepared
(),
$classMetadata
);
$this
->
_updateRow
(
$classMetadata
->
getTableName
(),
$data
,
$identifier
);
$record
->
assignIdentifier
(
true
);
}
}
\ No newline at end of file
lib/Doctrine/HydratorNew.php
View file @
d0ea5705
...
...
@@ -24,7 +24,8 @@
* and turn them into useable structures.
*
* Runtime complexity: The following gives the overall number of iterations
* required to process a result set.
* required to process a result set when using identity hydration
* (HYDRATE_IDENTITY_OBJECT or HYDRATE_IDENTITY_ARRAY).
*
* <code>numRowsInResult * numColumnsInResult + numRowsInResult * numClassesInQuery</code>
*
...
...
@@ -32,15 +33,19 @@
*
* <code>(numRowsInResult * (numColumnsInResult + numClassesInQuery))</code>
*
* Note that this is only a crude definition of the complexity as it also heavily
* For scalar hydration (HYDRATE_SCALAR) it's:
*
* <code>numRowsInResult * numColumnsInResult</code>
*
* Note that this is only a crude definition as it also heavily
* depends on the complexity of all the single operations that are performed in
* each iteration.
*
* As can be seen, the number of columns in the result has the most impact on
* the overall performance (apart from the row coun
r
, of course), since numClassesInQuery
* the overall performance (apart from the row coun
t
, of course), since numClassesInQuery
* is usually pretty low.
* That's why the performance of the
gatherRowData() method which is
responsible
* for the "numRowsInResult * numColumnsInResult" part is crucial to fast hydraton.
* That's why the performance of the
_gatherRowData() methods which are
responsible
* for the "numRowsInResult * numColumnsInResult" part is crucial to fast hydrat
i
on.
*
* @package Doctrine
* @subpackage Hydrator
...
...
@@ -54,14 +59,10 @@
class
Doctrine_HydratorNew
extends
Doctrine_Hydrator_Abstract
{
/**
* hydrateResultSet
* parses the data returned by statement object
* Parses the data returned by statement object.
*
* This is method defines the core of Doctrine's object population algorithm.
*
* The key idea is the loop over the rowset only once doing all the needed operations
* within this massive loop.
*
* @todo: Detailed documentation. Refactor (too long & nesting level).
*
* @param mixed $stmt
...
...
@@ -125,7 +126,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
$idTemplate
=
array
();
// Holds the resulting hydrated data structure
if
(
$parserResult
->
isMixedQuery
())
{
if
(
$parserResult
->
isMixedQuery
()
||
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
$result
=
array
();
}
else
{
$result
=
$driver
->
getElementCollection
(
$rootComponentName
);
...
...
@@ -140,7 +141,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
// disable lazy-loading of related elements during hydration
$component
[
'table'
]
->
setAttribute
(
Doctrine
::
ATTR_LOAD_REFERENCES
,
false
);
$componentName
=
$component
[
'table'
]
->
getClassName
();
$listeners
[
$componentName
]
=
$component
[
'table'
]
->
getRecordListener
();
//
$listeners[$componentName] = $component['table']->getRecordListener();
$identifierMap
[
$dqlAlias
]
=
array
();
$resultPointers
[
$dqlAlias
]
=
array
();
$idTemplate
[
$dqlAlias
]
=
''
;
...
...
@@ -149,6 +150,11 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
// Process result set
$cache
=
array
();
while
(
$data
=
$stmt
->
fetch
(
Doctrine
::
FETCH_ASSOC
))
{
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
$result
[]
=
$this
->
_gatherScalarRowData
(
$data
,
$cache
);
continue
;
}
$id
=
$idTemplate
;
// initialize the id-memory
$nonemptyComponents
=
array
();
$rowData
=
$this
->
_gatherRowData
(
$data
,
$cache
,
$id
,
$nonemptyComponents
);
...
...
@@ -160,8 +166,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
$componentName
=
$class
->
getComponentName
();
// just event stuff
$event
->
set
(
'data'
,
$rowData
[
$rootAlias
]);
$listeners
[
$componentName
]
->
preHydrate
(
$event
);
//
$event->set('data', $rowData[$rootAlias]);
//
$listeners[$componentName]->preHydrate($event);
//--
// Check for an existing element
...
...
@@ -170,8 +176,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
$element
=
$driver
->
getElement
(
$rowData
[
$rootAlias
],
$componentName
);
// just event stuff
$event
->
set
(
'data'
,
$element
);
$listeners
[
$componentName
]
->
postHydrate
(
$event
);
//
$event->set('data', $element);
//
$listeners[$componentName]->postHydrate($event);
//--
// do we need to index by a custom field?
...
...
@@ -201,7 +207,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
}
else
{
$index
=
$identifierMap
[
$rootAlias
][
$id
[
$rootAlias
]];
}
$this
->
_setLastElement
(
$resultPointers
,
$result
,
$index
,
$rootAlias
,
false
);
unset
(
$rowData
[
$rootAlias
]);
// end hydrate data of the root component for the current row
...
...
@@ -212,7 +217,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
unset
(
$rowData
[
'scalars'
]);
}
// $resultPointers[$rootAlias] now points to the last element in $result.
// now hydrate the rest of the data found in the current row, that belongs to other
// (related) components.
foreach
(
$rowData
as
$dqlAlias
=>
$data
)
{
...
...
@@ -221,14 +225,13 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
$componentName
=
$map
[
'table'
]
->
getComponentName
();
// just event stuff
$event
->
set
(
'data'
,
$data
);
$listeners
[
$componentName
]
->
preHydrate
(
$event
);
//
$event->set('data', $data);
//
$listeners[$componentName]->preHydrate($event);
//--
$parent
=
$map
[
'parent'
];
$relation
=
$map
[
'relation'
];
$relationAlias
=
$relation
->
getAlias
();
$path
=
$parent
.
'.'
.
$dqlAlias
;
// pick the right element that will get the associated element attached
...
...
@@ -252,8 +255,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
$element
=
$driver
->
getElement
(
$data
,
$componentName
);
// just event stuff
$event
->
set
(
'data'
,
$element
);
$listeners
[
$componentName
]
->
postHydrate
(
$event
);
//
$event->set('data', $element);
//
$listeners[$componentName]->postHydrate($event);
//--
if
(
$field
=
$this
->
_getCustomIndexField
(
$dqlAlias
))
{
...
...
@@ -268,7 +271,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
}
else
{
$driver
->
addRelatedElement
(
$baseElement
,
$relationAlias
,
$element
);
}
$identifierMap
[
$path
][
$id
[
$parent
]][
$id
[
$dqlAlias
]]
=
$driver
->
getLastKey
(
$driver
->
getReferenceValue
(
$baseElement
,
$relationAlias
));
}
else
{
...
...
@@ -361,9 +363,12 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
}
/**
* Puts the fields of a data row into a new array, grouped by the component
* Processes a row of the result set.
* Used for identity hydration (HYDRATE_IDENTITY_OBJECT and HYDRATE_IDENTITY_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.
* 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).
...
...
@@ -377,7 +382,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
if
(
!
isset
(
$cache
[
$key
]))
{
// check ignored names. fastest solution for now. if we get more we'll start
// to introduce a list.
if
(
$
key
==
'doctrine_rownum'
)
continue
;
if
(
$
this
->
_isIgnoredName
(
$key
)
)
continue
;
// cache general information like the column name <-> field name mapping
$e
=
explode
(
'__'
,
$key
);
...
...
@@ -441,18 +446,94 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
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 prepares the
* values. The resulting row has the same number of elements as before.
*
* @param array $data
* @param array $cache
* @return array The processed row.
*/
private
function
_gatherScalarRowData
(
&
$data
,
&
$cache
)
{
$rowData
=
array
();
foreach
(
$data
as
$key
=>
$value
)
{
// Parse each column name only once. Cache the results.
if
(
!
isset
(
$cache
[
$key
]))
{
// check ignored names. fastest solution for now. if we get more we'll start
// to introduce a list.
if
(
$this
->
_isIgnoredName
(
$key
))
continue
;
// cache general information like the column name <-> field name mapping
$e
=
explode
(
'__'
,
$key
);
$columnName
=
strtolower
(
array_pop
(
$e
));
$cache
[
$key
][
'dqlAlias'
]
=
$this
->
_tableAliases
[
strtolower
(
implode
(
'__'
,
$e
))];
$mapper
=
$this
->
_queryComponents
[
$cache
[
$key
][
'dqlAlias'
]][
'mapper'
];
$classMetadata
=
$mapper
->
getClassMetadata
();
// check whether it's an aggregate value or a regular field
if
(
isset
(
$this
->
_queryComponents
[
$cache
[
$key
][
'dqlAlias'
]][
'agg'
][
$columnName
]))
{
$fieldName
=
$this
->
_queryComponents
[
$cache
[
$key
][
'dqlAlias'
]][
'agg'
][
$columnName
];
$cache
[
$key
][
'isScalar'
]
=
true
;
}
else
{
$fieldName
=
$mapper
->
getFieldName
(
$columnName
);
$cache
[
$key
][
'isScalar'
]
=
false
;
}
$cache
[
$key
][
'fieldName'
]
=
$fieldName
;
// cache type information
$type
=
$classMetadata
->
getTypeOfColumn
(
$columnName
);
if
(
$type
==
'integer'
||
$type
==
'string'
)
{
$cache
[
$key
][
'isSimpleType'
]
=
true
;
}
else
{
$cache
[
$key
][
'type'
]
=
$type
;
$cache
[
$key
][
'isSimpleType'
]
=
false
;
}
}
$mapper
=
$this
->
_queryComponents
[
$cache
[
$key
][
'dqlAlias'
]][
'mapper'
];
$dqlAlias
=
$cache
[
$key
][
'dqlAlias'
];
$fieldName
=
$cache
[
$key
][
'fieldName'
];
if
(
$cache
[
$key
][
'isSimpleType'
]
||
$cache
[
$key
][
'isScalar'
])
{
$rowData
[
$dqlAlias
.
'_'
.
$fieldName
]
=
$value
;
}
else
{
$rowData
[
$dqlAlias
.
'_'
.
$fieldName
]
=
$mapper
->
prepareValue
(
$fieldName
,
$value
,
$cache
[
$key
][
'type'
]);
}
}
return
$rowData
;
}
/**
* Gets the custom field used for indexing for the specified component alias.
*
* @return string The field name of the field used for indexing or NULL
* if the component does not use any custom field indices.
*/
pr
otected
function
_getCustomIndexField
(
$alias
)
pr
ivate
function
_getCustomIndexField
(
$alias
)
{
return
isset
(
$this
->
_queryComponents
[
$alias
][
'map'
])
?
$this
->
_queryComponents
[
$alias
][
'map'
]
:
null
;
}
/**
* Checks whether a name is ignored. Used during result set parsing to skip
* certain elements in the result set that do not have any meaning for the result.
* (I.e. ORACLE limit/offset emulation adds doctrine_rownum to the result set).
*
* @param string $name
* @return boolean
*/
private
function
_isIgnoredName
(
$name
)
{
return
$name
==
'doctrine_rownum'
;
}
/** Needed only temporarily until the new parser is ready */
private
$_isResultMixed
=
false
;
public
function
setResultMixed
(
$bool
)
{
...
...
tests/Orm/Hydration/BasicHydrationTest.php
View file @
d0ea5705
...
...
@@ -17,7 +17,8 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
{
return
array
(
array
(
'hydrationMode'
=>
Doctrine
::
HYDRATE_RECORD
),
array
(
'hydrationMode'
=>
Doctrine
::
HYDRATE_ARRAY
)
array
(
'hydrationMode'
=>
Doctrine
::
HYDRATE_ARRAY
),
array
(
'hydrationMode'
=>
Doctrine
::
HYDRATE_SCALAR
)
);
}
...
...
@@ -76,18 +77,27 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$result
=
$hydrator
->
hydrateResultSet
(
$this
->
_createParserResult
(
$stmt
,
$queryComponents
,
$tableAliasMap
,
$hydrationMode
));
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
||
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertEquals
(
1
,
$result
[
0
][
'id'
]);
$this
->
assertEquals
(
'romanb'
,
$result
[
0
][
'name'
]);
$this
->
assertEquals
(
2
,
$result
[
1
][
'id'
]);
$this
->
assertEquals
(
'jwage'
,
$result
[
1
][
'name'
]);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertTrue
(
$result
instanceof
Doctrine_Collection
);
$this
->
assertTrue
(
$result
[
0
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
]
instanceof
Doctrine_Entity
);
}
else
{
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
)
{
$this
->
assertTrue
(
is_array
(
$result
));
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertEquals
(
'romanb'
,
$result
[
0
][
'u_name'
]);
$this
->
assertEquals
(
1
,
$result
[
0
][
'u_id'
]);
$this
->
assertEquals
(
'jwage'
,
$result
[
1
][
'u_name'
]);
$this
->
assertEquals
(
2
,
$result
[
1
][
'u_id'
]);
}
}
...
...
@@ -160,6 +170,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
//var_dump($result);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
||
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
[
0
]));
...
...
@@ -175,6 +186,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$this
->
assertEquals
(
42
,
$result
[
0
][
0
][
'phonenumbers'
][
0
][
'phonenumber'
]);
$this
->
assertEquals
(
43
,
$result
[
0
][
0
][
'phonenumbers'
][
1
][
'phonenumber'
]);
$this
->
assertEquals
(
91
,
$result
[
1
][
0
][
'phonenumbers'
][
0
][
'phonenumber'
]);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertTrue
(
$result
[
0
][
0
]
instanceof
Doctrine_Entity
);
...
...
@@ -183,6 +195,16 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$this
->
assertTrue
(
$result
[
0
][
0
][
'phonenumbers'
][
1
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
][
0
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
][
0
][
'phonenumbers'
]
instanceof
Doctrine_Collection
);
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertEquals
(
3
,
count
(
$result
));
$this
->
assertEquals
(
1
,
$result
[
0
][
'u_id'
]);
$this
->
assertEquals
(
'developer'
,
$result
[
0
][
'u_status'
]);
$this
->
assertEquals
(
'ROMANB'
,
$result
[
0
][
'u_nameUpper'
]);
$this
->
assertEquals
(
42
,
$result
[
0
][
'p_phonenumber'
]);
// ... more checks to come
}
}
...
...
@@ -237,7 +259,6 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
)
);
$stmt
=
new
Doctrine_HydratorMockStatement
(
$resultSet
);
$hydrator
=
new
Doctrine_HydratorNew
(
$this
->
_em
);
...
...
@@ -245,6 +266,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$stmt
,
$queryComponents
,
$tableAliasMap
,
$hydrationMode
,
true
));
//var_dump($result);
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
||
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
[
0
]));
...
...
@@ -254,10 +276,21 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$this
->
assertEquals
(
2
,
$result
[
0
][
'numPhones'
]);
// second user => 1 phonenumber
$this
->
assertEquals
(
1
,
$result
[
1
][
'numPhones'
]);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertTrue
(
$result
[
0
][
0
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
][
0
]
instanceof
Doctrine_Entity
);
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertEquals
(
1
,
$result
[
0
][
'u_id'
]);
$this
->
assertEquals
(
'developer'
,
$result
[
0
][
'u_status'
]);
$this
->
assertEquals
(
2
,
$result
[
0
][
'p_numPhones'
]);
$this
->
assertEquals
(
2
,
$result
[
1
][
'u_id'
]);
$this
->
assertEquals
(
'developer'
,
$result
[
1
][
'u_status'
]);
$this
->
assertEquals
(
1
,
$result
[
1
][
'p_numPhones'
]);
}
}
...
...
@@ -330,12 +363,12 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
//var_dump($result);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
||
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
[
0
]));
$this
->
assertTrue
(
is_array
(
$result
[
1
]));
// first user => 2 phonenumbers. notice the custom indexing by user id
$this
->
assertEquals
(
2
,
count
(
$result
[
0
][
'1'
][
'phonenumbers'
]));
// second user => 1 phonenumber. notice the custom indexing by user id
...
...
@@ -349,12 +382,16 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
// test the scalar values
$this
->
assertEquals
(
'ROMANB'
,
$result
[
0
][
'nameUpper'
]);
$this
->
assertEquals
(
'JWAGE'
,
$result
[
1
][
'nameUpper'
]);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertTrue
(
$result
[
0
][
'1'
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
][
'2'
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
0
][
'1'
][
'phonenumbers'
]
instanceof
Doctrine_Collection
);
$this
->
assertEquals
(
2
,
count
(
$result
[
0
][
'1'
][
'phonenumbers'
]));
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
// NOTE: Indexing has no effect with HYDRATE_SCALAR
//... asserts to come
}
}
...
...
@@ -469,6 +506,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
//var_dump($result);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
||
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
[
0
]));
...
...
@@ -491,6 +529,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$this
->
assertEquals
(
'ZendCon'
,
$result
[
0
][
0
][
'articles'
][
1
][
'topic'
]);
$this
->
assertEquals
(
'LINQ'
,
$result
[
1
][
0
][
'articles'
][
0
][
'topic'
]);
$this
->
assertEquals
(
'PHP6'
,
$result
[
1
][
0
][
'articles'
][
1
][
'topic'
]);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertTrue
(
$result
[
0
][
0
]
instanceof
Doctrine_Entity
);
...
...
@@ -505,6 +544,10 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$this
->
assertTrue
(
$result
[
1
][
0
][
'phonenumbers'
][
0
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
][
0
][
'articles'
][
0
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
][
0
][
'articles'
][
1
]
instanceof
Doctrine_Entity
);
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
//...
$this
->
assertEquals
(
6
,
count
(
$result
));
//var_dump($result);
}
}
...
...
@@ -642,6 +685,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
//var_dump($result);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
||
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
[
0
]));
...
...
@@ -672,6 +716,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
$this
->
assertFalse
(
isset
(
$result
[
0
][
0
][
'articles'
][
1
][
'comments'
]));
$this
->
assertFalse
(
isset
(
$result
[
1
][
0
][
'articles'
][
0
][
'comments'
]));
$this
->
assertFalse
(
isset
(
$result
[
1
][
0
][
'articles'
][
1
][
'comments'
]));
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertTrue
(
$result
[
0
][
0
]
instanceof
Doctrine_Entity
);
...
...
@@ -691,6 +736,8 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
// article comments
$this
->
assertTrue
(
$result
[
0
][
0
][
'articles'
][
0
][
'comments'
]
instanceof
Doctrine_Collection
);
$this
->
assertTrue
(
$result
[
0
][
0
][
'articles'
][
0
][
'comments'
][
0
]
instanceof
Doctrine_Entity
);
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
//...
}
}
...
...
@@ -787,21 +834,28 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
//var_dump($result);
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
||
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertEquals
(
2
,
count
(
$result
));
$this
->
assertTrue
(
isset
(
$result
[
0
][
'boards'
]));
$this
->
assertEquals
(
3
,
count
(
$result
[
0
][
'boards'
]));
$this
->
assertTrue
(
isset
(
$result
[
1
][
'boards'
]));
$this
->
assertEquals
(
1
,
count
(
$result
[
1
][
'boards'
]));
}
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_ARRAY
)
{
$this
->
assertTrue
(
is_array
(
$result
));
$this
->
assertTrue
(
is_array
(
$result
[
0
]));
$this
->
assertTrue
(
is_array
(
$result
[
1
]));
}
else
{
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_RECORD
)
{
$this
->
assertTrue
(
$result
instanceof
Doctrine_Collection
);
$this
->
assertTrue
(
$result
[
0
]
instanceof
Doctrine_Entity
);
$this
->
assertTrue
(
$result
[
1
]
instanceof
Doctrine_Entity
);
}
else
if
(
$hydrationMode
==
Doctrine
::
HYDRATE_SCALAR
)
{
//...
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment