[2.0] More progress on the UnitOfWork and collections. First basic functional many-many test.
This commit is contained in:
parent
deb095f2c8
commit
2eb4a16dd4
@ -1,260 +0,0 @@
|
||||
<?php
|
||||
|
||||
#namespace Doctrine\Common;
|
||||
|
||||
#use \ArrayAccess;
|
||||
|
||||
/**
|
||||
* Base class for classes that use the virtual property system.
|
||||
*
|
||||
* @author robo
|
||||
*/
|
||||
class Doctrine_Common_VirtualPropertyObject implements ArrayAccess
|
||||
{
|
||||
protected $_data = array();
|
||||
protected $_entityName;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of a class derived from VirtualPropertyObject.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_entityName = get_class($this);
|
||||
if ( ! Doctrine_Common_VirtualPropertySystem::isInitialized($this->_entityName)) {
|
||||
Doctrine_Common_VirtualPropertySystem::initialize($this->_entityName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic getter for virtual properties.
|
||||
*
|
||||
* @param string $fieldName Name of the field.
|
||||
* @return mixed
|
||||
*/
|
||||
final public function get($fieldName)
|
||||
{
|
||||
if ( ! Doctrine_Common_VirtualPropertySystem::hasProperty($this->_entityName, $fieldName)) {
|
||||
throw new Doctrine_Exception("Access of undefined property '$fieldName'.");
|
||||
}
|
||||
$getter = $this->_getCustomAccessor($fieldName);
|
||||
if ($getter) {
|
||||
return $this->$getter();
|
||||
}
|
||||
return $this->_get($fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for virtual properties.
|
||||
*
|
||||
* @param string $name The name of the field to set.
|
||||
* @param mixed $value The value of the field.
|
||||
*/
|
||||
final public function set($fieldName, $value)
|
||||
{
|
||||
if ( ! Doctrine_Common_VirtualPropertySystem::hasProperty($this->_entityName, $fieldName)) {
|
||||
throw new Doctrine_Exception("Access of undefined property '$fieldName'.");
|
||||
}
|
||||
if (Doctrine_Common_VirtualPropertySystem::isTypeCheckEnabled()) {
|
||||
$this->_checkType($fieldName, $value);
|
||||
}
|
||||
$setter = $this->_getCustomMutator($fieldName);
|
||||
if ($setter) {
|
||||
return $this->$setter($value);
|
||||
}
|
||||
$this->_set($fieldName, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the type of a virtual property.
|
||||
*
|
||||
* @param <type> $fieldName
|
||||
* @param <type> $value
|
||||
*/
|
||||
protected function _checkType($fieldName, $value)
|
||||
{
|
||||
$type = Doctrine_Common_VirtualPropertySystem::getType($this->_entityName, $fieldName);
|
||||
if (Doctrine_Common_VirtualPropertySystem::isSimplePHPType($type)) {
|
||||
$is_type = "is_$type";
|
||||
if ( ! $is_type($value)) {
|
||||
throw new Doctrine_Exception("'$value' is of an invalid type. Expected: $type.");
|
||||
}
|
||||
} else if ($type == 'array') {
|
||||
if ( ! is_array($value)) {
|
||||
throw new Doctrine_Exception("'$value' is of an invalid type. Expected: array.");
|
||||
}
|
||||
} else {
|
||||
if ( ! $value instanceof $type) {
|
||||
throw new Doctrine_Exception("'$value' is of an invalid type. Expected: $type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _get($fieldName)
|
||||
{
|
||||
return isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null;
|
||||
}
|
||||
|
||||
protected function _set($fieldName, $value)
|
||||
{
|
||||
$this->_data[$fieldName] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom mutator method for a virtual property, if it exists.
|
||||
*
|
||||
* @param string $fieldName The field name.
|
||||
* @return mixed The name of the custom mutator or FALSE, if the field does
|
||||
* not have a custom mutator.
|
||||
*/
|
||||
private function _getCustomMutator($fieldName)
|
||||
{
|
||||
if (Doctrine_Common_VirtualPropertySystem::getMutator($this->_entityName, $fieldName) === null) {
|
||||
if (Doctrine_Common_VirtualPropertySystem::isAutoAccessorOverride()) {
|
||||
$setterMethod = 'set' . Doctrine::classify($fieldName);
|
||||
if ( ! method_exists($this, $setterMethod)) {
|
||||
$setterMethod = false;
|
||||
}
|
||||
Doctrine_Common_VirtualPropertySystem::setMutator(
|
||||
$this->_entityName, $fieldName, $setterMethod);
|
||||
} else {
|
||||
Doctrine_Common_VirtualPropertySystem::setMutator(
|
||||
$this->_entityName, $fieldName, false);
|
||||
}
|
||||
}
|
||||
return Doctrine_Common_VirtualPropertySystem::getMutator($this->_entityName, $fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom accessor method of a virtual property, if it exists.
|
||||
*
|
||||
* @param string $fieldName The field name.
|
||||
* @return mixed The name of the custom accessor method, or FALSE if the
|
||||
* field does not have a custom accessor.
|
||||
*/
|
||||
private function _getCustomAccessor($fieldName)
|
||||
{
|
||||
if (Doctrine_Common_VirtualPropertySystem::getAccessor($this->_entityName, $fieldName) === null) {
|
||||
if (Doctrine_Common_VirtualPropertySystem::isAutoAccessorOverride()) {
|
||||
$getterMethod = 'get' . Doctrine::classify($fieldName);
|
||||
if ( ! method_exists($this, $getterMethod)) {
|
||||
$getterMethod = false;
|
||||
}
|
||||
Doctrine_Common_VirtualPropertySystem::setAccessor(
|
||||
$this->_entityName, $fieldName, $getterMethod);
|
||||
} else {
|
||||
Doctrine_Common_VirtualPropertySystem::setAccessor(
|
||||
$this->_entityName, $fieldName, false);
|
||||
}
|
||||
}
|
||||
|
||||
return Doctrine_Common_VirtualPropertySystem::getAccessor($this->_entityName, $fieldName);
|
||||
}
|
||||
|
||||
protected function _contains($fieldName)
|
||||
{
|
||||
return isset($this->_data[$fieldName]);
|
||||
}
|
||||
|
||||
protected function _unset($fieldName)
|
||||
{
|
||||
unset($this->_data[$fieldName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts mutating calls for virtual properties.
|
||||
*
|
||||
* @see set, offsetSet
|
||||
* @param $name
|
||||
* @param $value
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts accessing calls for virtual properties.
|
||||
*
|
||||
* @see get, offsetGet
|
||||
* @param mixed $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts isset() calls for virtual properties.
|
||||
*
|
||||
* @param string $name
|
||||
* @return boolean whether or not this object contains $name
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return $this->_contains($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts unset() calls for virtual properties.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
return $this->_unset($name);
|
||||
}
|
||||
|
||||
/* ArrayAccess implementation */
|
||||
|
||||
/**
|
||||
* Check if an offsetExists.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return boolean whether or not this object contains $offset
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return $this->_contains($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* offsetGet an alias of get()
|
||||
*
|
||||
* @see get, __get
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->get($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets $offset to $value
|
||||
* @see set, __set
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
return $this->set($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* unset a given offset
|
||||
* @see set, offsetSet, __set
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
return $this->_unset($offset);
|
||||
}
|
||||
|
||||
/* END of ArrayAccess implementation */
|
||||
}
|
||||
?>
|
@ -1,241 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The VirtualPropertySystem class is a class consisting solely of static methods and
|
||||
* serves as a generic virtual property registry system.
|
||||
* Classes register their (virtual) properties with the property system, optionally specifying
|
||||
* property features/options. These can then be evaluated by other code.
|
||||
*
|
||||
* @author robo
|
||||
* @since 2.0
|
||||
*/
|
||||
class Doctrine_Common_VirtualPropertySystem {
|
||||
private static $_properties = array();
|
||||
private static $_callback = 'construct';
|
||||
private static $_checkTypes = false;
|
||||
private static $_useAutoAccessorOverride = true;
|
||||
private static $_simplePHPTypes = array(
|
||||
'int' => true,
|
||||
'string' => true,
|
||||
'bool' => true,
|
||||
'double' => true
|
||||
);
|
||||
|
||||
/** Private constructor. This class cannot be instantiated. */
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* Gets all properties of a class that are registered with the VirtualPropertySystem.
|
||||
*
|
||||
* @param string $class
|
||||
* @return array
|
||||
*/
|
||||
public static function getProperties($class)
|
||||
{
|
||||
if ( ! self::isInitialized($class)) {
|
||||
self::initialize($class);
|
||||
}
|
||||
return self::$_properties[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether automatic accessor overrides are enabled.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAutoAccessorOverride()
|
||||
{
|
||||
return self::$_useAutoAccessorOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether automatic accessor overrides are enabled.
|
||||
*
|
||||
* @param boolean $bool
|
||||
*/
|
||||
public static function setAutoAccessorOverride($bool)
|
||||
{
|
||||
self::$_useAutoAccessorOverride = (bool)$bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepopulates the property system.
|
||||
*
|
||||
* @param array $properties
|
||||
*/
|
||||
public static function populate(array $properties)
|
||||
{
|
||||
self::$_properties = $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given type is a simple PHP type.
|
||||
* Simple php types are: int, string, bool, double.
|
||||
*
|
||||
* @param string $type The type to check.
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isSimplePHPType($type)
|
||||
{
|
||||
return isset(self::$_simplePHPTypes[$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether type checks are enabled.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isTypeCheckEnabled()
|
||||
{
|
||||
return self::$_checkTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether type checks are enabled.
|
||||
*
|
||||
* @param boolean $bool
|
||||
*/
|
||||
public static function setTypeCheckEnabled($bool)
|
||||
{
|
||||
self::$_checkTypes = (bool)$bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the callback method to use for initializing the virtual
|
||||
* properties of a class. The callback must be static and public.
|
||||
*
|
||||
* @param string $callback
|
||||
*/
|
||||
public static function setCallback($callback)
|
||||
{
|
||||
self::$_callback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a virtual property for a class.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $propName
|
||||
* @param string $type
|
||||
* @param string $accessor
|
||||
* @param string $mutator
|
||||
*/
|
||||
public static function register($class, $propName, $type, $accessor = null, $mutator = null)
|
||||
{
|
||||
self::$_properties[$class][$propName] = array(
|
||||
'type' => $type, 'accessor' => $accessor, 'mutator' => $mutator
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether a class has already been initialized by the virtual property system.
|
||||
*
|
||||
* @param string $class
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isInitialized($class)
|
||||
{
|
||||
return isset(self::$_properties[$class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a class with the virtual property system.
|
||||
*
|
||||
* @param <type> $class
|
||||
*/
|
||||
public static function initialize($class)
|
||||
{
|
||||
if (method_exists($class, self::$_callback)) {
|
||||
call_user_func(array($class, self::$_callback));
|
||||
} else {
|
||||
self::$_properties[$class] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether a class has a virtual property with a certain name.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $propName
|
||||
* @return boolean
|
||||
*/
|
||||
public static function hasProperty($class, $propName)
|
||||
{
|
||||
return isset(self::$_properties[$class][$propName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the accessor for a virtual property.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $propName
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getAccessor($class, $propName)
|
||||
{
|
||||
return isset(self::$_properties[$class][$propName]['accessor']) ?
|
||||
self::$_properties[$class][$propName]['accessor'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accessor method for a virtual property.
|
||||
*
|
||||
* @param <type> $class
|
||||
* @param <type> $propName
|
||||
* @param <type> $accessor
|
||||
*/
|
||||
public static function setAccessor($class, $propName, $accessor)
|
||||
{
|
||||
self::$_properties[$class][$propName]['accessor'] = $accessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mutator method for a virtual property.
|
||||
*
|
||||
* @param <type> $class
|
||||
* @param <type> $propName
|
||||
* @return <type>
|
||||
*/
|
||||
public static function getMutator($class, $propName)
|
||||
{
|
||||
return isset(self::$_properties[$class][$propName]['mutator']) ?
|
||||
self::$_properties[$class][$propName]['mutator'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mutator method for a virtual property.
|
||||
*
|
||||
* @param <type> $class
|
||||
* @param <type> $propName
|
||||
* @param <type> $mutator
|
||||
*/
|
||||
public static function setMutator($class, $propName, $mutator)
|
||||
{
|
||||
self::$_properties[$class][$propName]['mutator'] = $mutator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of a virtual property.
|
||||
*
|
||||
* @param <type> $class
|
||||
* @param <type> $propName
|
||||
* @return <type>
|
||||
*/
|
||||
public static function getType($class, $propName)
|
||||
{
|
||||
return isset(self::$_properties[$class][$propName]['type']) ?
|
||||
self::$_properties[$class][$propName]['type'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of a virtual property.
|
||||
*
|
||||
* @param <type> $class
|
||||
* @param <type> $propName
|
||||
* @param <type> $type
|
||||
*/
|
||||
public static function setType($class, $propName, $type)
|
||||
{
|
||||
self::$_properties[$class][$propName]['type'] = $type;
|
||||
}
|
||||
}
|
||||
?>
|
@ -542,8 +542,9 @@ class Connection
|
||||
public function exec($query, array $params = array()) {
|
||||
$this->connect();
|
||||
try {
|
||||
echo $query . PHP_EOL;
|
||||
if ( ! empty($params)) {
|
||||
//var_dump($params);
|
||||
var_dump($params);
|
||||
$stmt = $this->prepare($query);
|
||||
$stmt->execute($params);
|
||||
return $stmt->rowCount();
|
||||
|
@ -1,316 +0,0 @@
|
||||
<?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>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Doctrine_Export_Frontbase
|
||||
*
|
||||
* @package Doctrine
|
||||
* @subpackage Export
|
||||
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
||||
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.phpdoctrine.org
|
||||
* @since 1.0
|
||||
* @version $Revision$
|
||||
*/
|
||||
class Doctrine_Export_Frontbase extends Doctrine_Export
|
||||
{
|
||||
/**
|
||||
* create a new database
|
||||
*
|
||||
* @param string $name name of the database that should be created
|
||||
* @return string
|
||||
*/
|
||||
public function createDatabaseSql($name)
|
||||
{
|
||||
$name = $this->conn->quoteIdentifier($name, true);
|
||||
return 'CREATE DATABASE ' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* drop an existing database
|
||||
*
|
||||
* @param string $name name of the database that should be dropped
|
||||
* @return string
|
||||
*/
|
||||
public function dropDatabaseSql($name)
|
||||
{
|
||||
$name = $this->conn->quoteIdentifier($name, true);
|
||||
return 'DELETE DATABASE ' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* drop an existing table
|
||||
*
|
||||
* @param object $this->conns database object that is extended by this class
|
||||
* @param string $name name of the table that should be dropped
|
||||
* @return string
|
||||
*/
|
||||
public function dropTableSql($name)
|
||||
{
|
||||
$name = $this->conn->quoteIdentifier($name, true);
|
||||
return 'DROP TABLE ' . $name . ' CASCADE';
|
||||
}
|
||||
|
||||
/**
|
||||
* alter an existing table
|
||||
*
|
||||
* @param string $name name of the table that is intended to be changed.
|
||||
* @param array $changes associative array that contains the details of each type
|
||||
* of change that is intended to be performed. The types of
|
||||
* changes that are currently supported are defined as follows:
|
||||
*
|
||||
* name
|
||||
*
|
||||
* New name for the table.
|
||||
*
|
||||
* add
|
||||
*
|
||||
* Associative array with the names of fields to be added as
|
||||
* indexes of the array. The value of each entry of the array
|
||||
* should be set to another associative array with the properties
|
||||
* of the fields to be added. The properties of the fields should
|
||||
* be the same as defined by the MDB2 parser.
|
||||
*
|
||||
*
|
||||
* remove
|
||||
*
|
||||
* Associative array with the names of fields to be removed as indexes
|
||||
* of the array. Currently the values assigned to each entry are ignored.
|
||||
* An empty array should be used for future compatibility.
|
||||
*
|
||||
* rename
|
||||
*
|
||||
* Associative array with the names of fields to be renamed as indexes
|
||||
* of the array. The value of each entry of the array should be set to
|
||||
* another associative array with the entry named name with the new
|
||||
* field name and the entry named Declaration that is expected to contain
|
||||
* the portion of the field declaration already in DBMS specific SQL code
|
||||
* as it is used in the CREATE TABLE statement.
|
||||
*
|
||||
* change
|
||||
*
|
||||
* Associative array with the names of the fields to be changed as indexes
|
||||
* of the array. Keep in mind that if it is intended to change either the
|
||||
* name of a field and any other properties, the change array entries
|
||||
* should have the new names of the fields as array indexes.
|
||||
*
|
||||
* The value of each entry of the array should be set to another associative
|
||||
* array with the properties of the fields to that are meant to be changed as
|
||||
* array entries. These entries should be assigned to the new values of the
|
||||
* respective properties. The properties of the fields should be the same
|
||||
* as defined by the MDB2 parser.
|
||||
*
|
||||
* Example
|
||||
* array(
|
||||
* 'name' => 'userlist',
|
||||
* 'add' => array(
|
||||
* 'quota' => array(
|
||||
* 'type' => 'integer',
|
||||
* 'unsigned' => 1
|
||||
* )
|
||||
* ),
|
||||
* 'remove' => array(
|
||||
* 'file_limit' => array(),
|
||||
* 'time_limit' => array()
|
||||
* ),
|
||||
* 'change' => array(
|
||||
* 'name' => array(
|
||||
* 'length' => '20',
|
||||
* 'definition' => array(
|
||||
* 'type' => 'text',
|
||||
* 'length' => 20,
|
||||
* ),
|
||||
* )
|
||||
* ),
|
||||
* 'rename' => array(
|
||||
* 'sex' => array(
|
||||
* 'name' => 'gender',
|
||||
* 'definition' => array(
|
||||
* 'type' => 'text',
|
||||
* 'length' => 1,
|
||||
* 'default' => 'M',
|
||||
* ),
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param boolean $check indicates whether the function should just check if the DBMS driver
|
||||
* can perform the requested table alterations if the value is true or
|
||||
* actually perform them otherwise.
|
||||
* @access public
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function alterTable($name, array $changes, $check = false)
|
||||
{
|
||||
foreach ($changes as $changeName => $change) {
|
||||
switch ($changeName) {
|
||||
case 'add':
|
||||
case 'remove':
|
||||
case 'change':
|
||||
case 'rename':
|
||||
case 'name':
|
||||
break;
|
||||
default:
|
||||
throw new Doctrine_Export_Exception('change type "'.$changeName.'" not yet supported');
|
||||
}
|
||||
}
|
||||
|
||||
if ($check) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$query = '';
|
||||
if ( ! empty($changes['name'])) {
|
||||
$changeName = $this->conn->quoteIdentifier($changes['name'], true);
|
||||
$query .= 'RENAME TO ' . $changeName;
|
||||
}
|
||||
|
||||
if ( ! empty($changes['add']) && is_array($changes['add'])) {
|
||||
foreach ($changes['add'] as $fieldName => $field) {
|
||||
if ($query) {
|
||||
$query.= ', ';
|
||||
}
|
||||
$query.= 'ADD ' . $this->conn->getDeclaration($fieldName, $field);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($changes['remove']) && is_array($changes['remove'])) {
|
||||
foreach ($changes['remove'] as $fieldName => $field) {
|
||||
if ($query) {
|
||||
$query.= ', ';
|
||||
}
|
||||
$fieldName = $this->conn->quoteIdentifier($fieldName, true);
|
||||
$query.= 'DROP ' . $fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
$rename = array();
|
||||
if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
|
||||
foreach ($changes['rename'] as $fieldName => $field) {
|
||||
$rename[$field['name']] = $fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($changes['change']) && is_array($changes['change'])) {
|
||||
foreach ($changes['change'] as $fieldName => $field) {
|
||||
if ($query) {
|
||||
$query.= ', ';
|
||||
}
|
||||
if (isset($rename[$fieldName])) {
|
||||
$oldFieldName = $rename[$fieldName];
|
||||
unset($rename[$fieldName]);
|
||||
} else {
|
||||
$oldFieldName = $fieldName;
|
||||
}
|
||||
$oldFieldName = $this->conn->quoteIdentifier($oldFieldName, true);
|
||||
$query.= 'CHANGE ' . $oldFieldName . ' ' . $this->conn->getDeclaration($oldFieldName, $field['definition']);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($rename) && is_array($rename)) {
|
||||
foreach ($rename as $renamedFieldName => $renamed_field) {
|
||||
if ($query) {
|
||||
$query.= ', ';
|
||||
}
|
||||
$oldFieldName = $rename[$renamedFieldName];
|
||||
$field = $changes['rename'][$oldFieldName];
|
||||
$query.= 'CHANGE ' . $this->conn->getDeclaration($oldFieldName, $field['definition']);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $query) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$name = $this->conn->quoteIdentifier($name, true);
|
||||
return $this->conn->exec('ALTER TABLE ' . $name . ' ' . $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* create sequence
|
||||
*
|
||||
* @param string $seqName name of the sequence to be created
|
||||
* @param string $start start value of the sequence; default is 1
|
||||
* @param array $options An associative array of table options:
|
||||
* array(
|
||||
* 'comment' => 'Foo',
|
||||
* 'charset' => 'utf8',
|
||||
* 'collate' => 'utf8_unicode_ci',
|
||||
* );
|
||||
* @return void
|
||||
*/
|
||||
public function createSequence($sequenceName, $start = 1, array $options = array())
|
||||
{
|
||||
$sequenceName = $this->conn->quoteIdentifier($this->conn->getSequenceName($sequenceName), true);
|
||||
$seqcolName = $this->conn->quoteIdentifier($this->conn->getAttribute(Doctrine::ATTR_SEQCOL_NAME), true);
|
||||
|
||||
$query = 'CREATE TABLE ' . $sequenceName . ' (' . $seqcolName . ' INTEGER DEFAULT UNIQUE, PRIMARY KEY(' . $seqcolName . '))';
|
||||
$res = $this->conn->exec($query);
|
||||
$res = $this->conn->exec('SET UNIQUE = 1 FOR ' . $sequenceName);
|
||||
|
||||
if ($start == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->conn->exec('INSERT INTO ' . $sequenceName . ' (' . $seqcolName . ') VALUES (' . ($start-1) . ')');
|
||||
} catch(Doctrine_Connection_Exception $e) {
|
||||
// Handle error
|
||||
try {
|
||||
$this->conn->exec('DROP TABLE ' . $sequenceName);
|
||||
} catch(Doctrine_Connection_Exception $e) {
|
||||
throw new Doctrine_Export_Exception('could not drop inconsistent sequence table');
|
||||
}
|
||||
|
||||
throw new Doctrine_Export_Exception('could not create sequence table');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drop existing sequence
|
||||
*
|
||||
* @param string $seqName name of the sequence to be dropped
|
||||
* @return string
|
||||
*/
|
||||
public function dropSequenceSql($seqName)
|
||||
{
|
||||
$sequenceName = $this->conn->quoteIdentifier($this->conn->getSequenceName($seqName), true);
|
||||
|
||||
return 'DROP TABLE ' . $sequenceName . ' CASCADE';
|
||||
}
|
||||
|
||||
/**
|
||||
* drop existing index
|
||||
*
|
||||
* @param string $table name of table that should be used in method
|
||||
* @param string $name name of the index to be dropped
|
||||
* @return boolean
|
||||
*/
|
||||
public function dropIndexSql($table, $name)
|
||||
{
|
||||
$table = $this->conn->quoteIdentifier($table, true);
|
||||
$name = $this->conn->quoteIdentifier($this->conn->getIndexName($name), true);
|
||||
|
||||
return 'ALTER TABLE ' . $table . ' DROP INDEX ' . $name;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id: Reporter.php 3882 2008-02-22 18:11:35Z jwage $
|
||||
*
|
||||
* 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>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Doctrine_Export_Reporter
|
||||
*
|
||||
* @package Doctrine
|
||||
* @subpackage Export
|
||||
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.phpdoctrine.org
|
||||
* @since 1.0
|
||||
* @version $Revision: 3882 $
|
||||
*/
|
||||
class Doctrine_Export_Reporter implements IteratorAggregate {
|
||||
protected $messages = array();
|
||||
|
||||
public function add($code, $message) {
|
||||
$this->messages[] = array($code, $message);
|
||||
}
|
||||
public function pop() {
|
||||
return array_pop($this->messages);
|
||||
}
|
||||
public function getIterator() {
|
||||
return new ArrayIterator($this->messages);
|
||||
}
|
||||
}
|
@ -31,9 +31,11 @@ if ( ! class_exists('\Addendum', false)) {
|
||||
require __DIR__ . '/DoctrineAnnotations.php';
|
||||
|
||||
/**
|
||||
* The AnnotationDriver reads the mapping metadata from docblock annotations.
|
||||
* The AnnotationDriver reads the mapping metadata from docblock annotations
|
||||
* with the help of the Addendum reflection extensions.
|
||||
*
|
||||
* @author robo
|
||||
* @since 2.0
|
||||
*/
|
||||
class AnnotationDriver
|
||||
{
|
||||
|
@ -51,6 +51,11 @@ class ManyToManyMapping extends AssociationMapping
|
||||
* Maps the columns in the target table to the columns in the relation table.
|
||||
*/
|
||||
private $_targetToRelationKeyColumns = array();
|
||||
|
||||
/**
|
||||
* The columns on the join table.
|
||||
*/
|
||||
private $_joinTableColumns = array();
|
||||
|
||||
/**
|
||||
* Initializes a new ManyToManyMapping.
|
||||
@ -76,25 +81,34 @@ class ManyToManyMapping extends AssociationMapping
|
||||
if ( ! isset($mapping['joinTable'])) {
|
||||
throw MappingException::joinTableRequired($mapping['fieldName']);
|
||||
}
|
||||
|
||||
// owning side MUST specify joinColumns
|
||||
if ( ! isset($mapping['joinTable']['joinColumns'])) {
|
||||
throw MappingException::invalidMapping($this->_sourceFieldName);
|
||||
}
|
||||
foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
|
||||
$this->_sourceToRelationKeyColumns[$joinColumn['referencedColumnName']] = $joinColumn['name'];
|
||||
$this->_joinTableColumns[] = $joinColumn['name'];
|
||||
}
|
||||
$this->_sourceKeyColumns = array_keys($this->_sourceToRelationKeyColumns);
|
||||
|
||||
// owning side MUST specify inverseJoinColumns
|
||||
if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
|
||||
throw MappingException::invalidMapping($this->_sourceFieldName);
|
||||
}
|
||||
foreach ($mapping['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) {
|
||||
$this->_targetToRelationKeyColumns[$inverseJoinColumn['referencedColumnName']] = $inverseJoinColumn['name'];
|
||||
$this->_joinTableColumns[] = $inverseJoinColumn['name'];
|
||||
}
|
||||
$this->_targetKeyColumns = array_keys($this->_targetToRelationKeyColumns);
|
||||
}
|
||||
}
|
||||
|
||||
public function getJoinTableColumns()
|
||||
{
|
||||
return $this->_joinTableColumns;
|
||||
}
|
||||
|
||||
public function getSourceToRelationKeyColumns()
|
||||
{
|
||||
return $this->_sourceToRelationKeyColumns;
|
||||
|
@ -27,11 +27,11 @@ use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
* 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 xref table are removed (on flush).
|
||||
* 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
|
||||
* (or removal of the links in the xref table if the one-many is mapped through an
|
||||
* xref table). If you want entities in a one-many collection to be removed when
|
||||
* (or removal of the links in the relation table if the one-many is mapped through a
|
||||
* relation table). If you want entities in a one-many collection to be removed when
|
||||
* they're removed from the collection, use deleteOrphans => true on the one-many
|
||||
* mapping.
|
||||
*
|
||||
|
@ -55,54 +55,54 @@ abstract class AbstractCollectionPersister
|
||||
}
|
||||
//...
|
||||
}
|
||||
|
||||
public function delete(PersistentCollection $coll)
|
||||
{
|
||||
if ($coll->getRelation()->isInverseSide()) {
|
||||
return;
|
||||
}
|
||||
//...
|
||||
}
|
||||
|
||||
public function update(PersistentCollection $coll)
|
||||
{
|
||||
$this->deleteRows($coll);
|
||||
$this->updateRows($coll);
|
||||
$this->insertRows($coll);
|
||||
}
|
||||
|
||||
/* collection update actions */
|
||||
|
||||
public function deleteRows(PersistentCollection $coll)
|
||||
/**
|
||||
* Deletes the persistent state represented by the given collection.
|
||||
*
|
||||
* @param PersistentCollection $coll
|
||||
*/
|
||||
public function delete(PersistentCollection $coll)
|
||||
{
|
||||
if ($coll->getMapping()->isInverseSide()) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
|
||||
|
||||
$sql = $this->_getDeleteSql($coll);
|
||||
$this->_conn->exec($sql, $this->_getDeleteSqlParameters($coll));
|
||||
}
|
||||
|
||||
abstract protected function _getDeleteSql(PersistentCollection $coll);
|
||||
abstract protected function _getDeleteSqlParameters(PersistentCollection $coll);
|
||||
|
||||
public function update(PersistentCollection $coll)
|
||||
{
|
||||
if ($coll->getMapping()->isInverseSide()) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
|
||||
$this->deleteRows($coll);
|
||||
//$this->updateRows($coll);
|
||||
$this->insertRows($coll);
|
||||
}
|
||||
|
||||
public function deleteRows(PersistentCollection $coll)
|
||||
{
|
||||
$deleteDiff = $coll->getDeleteDiff();
|
||||
$sql = $this->_getDeleteRowSql($coll);
|
||||
$uow = $this->_em->getUnitOfWork();
|
||||
foreach ($deleteDiff as $element) {
|
||||
$this->_conn->exec($sql, $this->_getDeleteRowSqlParameters($element));
|
||||
$this->_conn->exec($sql, $this->_getDeleteRowSqlParameters($coll, $element));
|
||||
}
|
||||
}
|
||||
|
||||
public function updateRows(PersistentCollection $coll)
|
||||
{
|
||||
|
||||
}
|
||||
{}
|
||||
|
||||
public function insertRows(PersistentCollection $coll)
|
||||
{
|
||||
if ($coll->getMapping()->isInverseSide()) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
|
||||
$insertDiff = $coll->getInsertDiff();
|
||||
$sql = $this->_getInsertRowSql($coll);
|
||||
$uow = $this->_em->getUnitOfWork();
|
||||
foreach ($insertDiff as $element) {
|
||||
$this->_conn->exec($sql/*, $uow->getEntityIdentifier($element)*/);
|
||||
$this->_conn->exec($sql, $this->_getInsertRowSqlParameters($coll, $element));
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,13 +120,15 @@ abstract class AbstractCollectionPersister
|
||||
*
|
||||
* @param PersistentCollection $coll
|
||||
*/
|
||||
abstract protected function _getUpdateRowSql();
|
||||
abstract protected function _getUpdateRowSql(PersistentCollection $coll);
|
||||
|
||||
/**
|
||||
* Gets the SQL statement used for inserting a row from to the collection.
|
||||
*
|
||||
* @param PersistentCollection $coll
|
||||
*/
|
||||
abstract protected function _getInsertRowSql();
|
||||
abstract protected function _getInsertRowSql(PersistentCollection $coll);
|
||||
|
||||
abstract protected function _getInsertRowSqlParameters(PersistentCollection $coll, $element);
|
||||
}
|
||||
|
||||
|
@ -171,13 +171,8 @@ abstract class AbstractEntityPersister
|
||||
protected function _prepareData($entity, array &$result, $isInsert = false)
|
||||
{
|
||||
foreach ($this->_em->getUnitOfWork()->getEntityChangeSet($entity) as $field => $change) {
|
||||
if (is_array($change)) {
|
||||
$oldVal = $change[0];
|
||||
$newVal = $change[1];
|
||||
} else {
|
||||
$oldVal = null;
|
||||
$newVal = $change;
|
||||
}
|
||||
$oldVal = $change[0];
|
||||
$newVal = $change[1];
|
||||
|
||||
$type = $this->_classMetadata->getTypeOfField($field);
|
||||
$columnName = $this->_classMetadata->getColumnName($field);
|
||||
|
@ -21,40 +21,112 @@
|
||||
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
/**
|
||||
* Persister for many-to-many collections.
|
||||
*
|
||||
* @author robo
|
||||
* @since 2.0
|
||||
*/
|
||||
class ManyToManyPersister extends AbstractCollectionPersister
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param <type> $coll
|
||||
* @override
|
||||
* @todo Identifier quoting.
|
||||
* @see _getDeleteRowSqlParameters()
|
||||
*/
|
||||
protected function _getDeleteRowSql(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping->getJoinTable();
|
||||
$columns = array_merge($mapping->getSourceKeyColumns(), $mapping->getTargetKeyColumns());
|
||||
return "DELETE FROM $joinTable WHERE " . implode(' = ?, ', $columns) . ' = ?';
|
||||
$columns = $mapping->getJoinTableColumns();
|
||||
return "DELETE FROM {$joinTable['name']} WHERE " . implode(' = ? AND ', $columns) . ' = ?';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param <type> $element
|
||||
* @override
|
||||
* @see _getDeleteRowSql()
|
||||
*/
|
||||
protected function _getDeleteRowSqlParameters(PersistentCollection $coll, $element)
|
||||
{
|
||||
$owner = $coll->getOwner();
|
||||
$params = array_merge(
|
||||
$this->_uow->getEntityIdentifier($coll->getOwner()),
|
||||
$this->_uow->getEntityIdentifier($element)
|
||||
);
|
||||
//var_dump($params);
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected function _getUpdateRowSql(PersistentCollection $coll)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected function _getInsertRowSql(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping->getJoinTable();
|
||||
$columns = $mapping->getJoinTableColumns();
|
||||
return "INSERT INTO {$joinTable['name']} (" . implode(', ', $columns) . ")"
|
||||
. " VALUES (" . implode(', ', array_fill(0, count($columns), '?')) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected function _getInsertRowSqlParameters(PersistentCollection $coll, $element)
|
||||
{
|
||||
//FIXME: This is still problematic for composite keys because we silently
|
||||
// rely on a specific ordering of the columns.
|
||||
$params = array_merge(
|
||||
$this->_uow->getEntityIdentifier($coll->getOwner()),
|
||||
$this->_uow->getEntityIdentifier($element)
|
||||
);
|
||||
var_dump($params);
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected function _getDeleteSql(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping->getJoinTable();
|
||||
$whereClause = '';
|
||||
foreach ($mapping->getSourceToRelationKeyColumns() as $relationColumn) {
|
||||
if ($whereClause !== '') $whereClause .= ' AND ';
|
||||
$whereClause .= "$relationColumn = ?";
|
||||
}
|
||||
return "DELETE FROM {$joinTable['name']} WHERE $whereClause";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected function _getDeleteSqlParameters(PersistentCollection $coll)
|
||||
{
|
||||
//FIXME: This is still problematic for composite keys because we silently
|
||||
// rely on a specific ordering of the columns.
|
||||
return $this->_uow->getEntityIdentifier($coll->getOwner());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,11 @@ use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
/**
|
||||
* Persister for one-to-many collections.
|
||||
*
|
||||
* This persister is only used for uni-directional one-to-many mappings.
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class OneToManyPersister extends AbstractCollectionPersister
|
||||
{
|
||||
@ -57,19 +60,7 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
$whereClause .= "$idColumn = ?";
|
||||
}
|
||||
|
||||
return "UPDATE $table SET $setClause WHERE $whereClause";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param <type> $element
|
||||
* @return <type>
|
||||
* @override
|
||||
*/
|
||||
protected function _getDeleteRowSqlParameters(PersistentCollection $coll, $element)
|
||||
{
|
||||
return $this->_uow->getEntityIdentifier($element);
|
||||
return array("UPDATE $table SET $setClause WHERE $whereClause", $this->_uow->getEntityIdentifier($element));
|
||||
}
|
||||
|
||||
protected function _getInsertRowSql()
|
||||
|
@ -24,6 +24,8 @@ namespace Doctrine\ORM;
|
||||
use Doctrine\ORM\Internal\CommitOrderCalculator;
|
||||
use Doctrine\ORM\Internal\CommitOrderNode;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Mapping;
|
||||
use Doctrine\ORM\Persisters;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Exceptions\UnitOfWorkException;
|
||||
|
||||
@ -227,7 +229,8 @@ class UnitOfWork
|
||||
if (empty($this->_newEntities) &&
|
||||
empty($this->_deletedEntities) &&
|
||||
empty($this->_dirtyEntities) &&
|
||||
empty($this->_collectionUpdates)) {
|
||||
empty($this->_collectionUpdates) &&
|
||||
empty($this->_collectionDeletions)) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
@ -243,8 +246,12 @@ class UnitOfWork
|
||||
$this->_executeUpdates($class);
|
||||
}
|
||||
|
||||
//TODO: collection deletions (deletions of complete collections)
|
||||
//TODO: collection updates (deleteRows, updateRows, insertRows)
|
||||
// Collection deletions (deletions of complete collections)
|
||||
foreach ($this->_collectionDeletions as $collectionToDelete) {
|
||||
$this->getCollectionPersister($collectionToDelete->getMapping())
|
||||
->delete($collectionToDelete);
|
||||
}
|
||||
// Collection updates (deleteRows, updateRows, insertRows)
|
||||
foreach ($this->_collectionUpdates as $collectionToUpdate) {
|
||||
$this->getCollectionPersister($collectionToUpdate->getMapping())
|
||||
->update($collectionToUpdate);
|
||||
@ -269,6 +276,7 @@ class UnitOfWork
|
||||
$this->_deletedEntities = array();
|
||||
$this->_entityChangeSets = array();
|
||||
$this->_collectionUpdates = array();
|
||||
$this->_collectionDeletions = array();
|
||||
$this->_visitedCollections = array();
|
||||
}
|
||||
|
||||
@ -328,24 +336,31 @@ class UnitOfWork
|
||||
$actualData[$name] = $refProp->getValue($entity);
|
||||
}
|
||||
|
||||
if ($class->isCollectionValuedAssociation($name) && ! ($actualData[$name] instanceof PersistentCollection)) {
|
||||
// Inject PersistentCollection
|
||||
if ($class->isCollectionValuedAssociation($name)
|
||||
&& ! is_null($actualData[$name])
|
||||
&& ! ($actualData[$name] instanceof PersistentCollection)) {
|
||||
//TODO: If $actualData[$name] is Collection then unwrap the array
|
||||
$assoc = $class->getAssociationMapping($name);
|
||||
$coll = new PersistentCollection($this->_em, $assoc->getTargetEntityName(),
|
||||
if ($assoc->isOwningSide()) {
|
||||
// Inject PersistentCollection
|
||||
$coll = new PersistentCollection($this->_em, $assoc->getTargetEntityName(),
|
||||
$actualData[$name] ? $actualData[$name] : array());
|
||||
$coll->_setOwner($entity, $assoc);
|
||||
if ( ! $coll->isEmpty()) $coll->setDirty(true);
|
||||
$class->getReflectionProperty($name)->setValue($entity, $coll);
|
||||
$actualData[$name] = $coll;
|
||||
$coll->_setOwner($entity, $assoc);
|
||||
if ( ! $coll->isEmpty()) $coll->setDirty(true);
|
||||
$class->getReflectionProperty($name)->setValue($entity, $coll);
|
||||
$actualData[$name] = $coll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset($this->_originalEntityData[$oid])) {
|
||||
// Entity is either NEW or MANAGED but not yet fully persisted
|
||||
// (only has an id). These result in an INSERT.
|
||||
$this->_entityChangeSets[$oid] = $actualData;
|
||||
$this->_originalEntityData[$oid] = $actualData;
|
||||
$this->_entityChangeSets[$oid] = array_map(
|
||||
function($e) { return array(null, $e); },
|
||||
$actualData
|
||||
);
|
||||
} else {
|
||||
// Entity is "fully" MANAGED: it was already fully persisted before
|
||||
// and we have a copy of the original data
|
||||
@ -366,11 +381,16 @@ class UnitOfWork
|
||||
$assoc = $class->getAssociationMapping($propName);
|
||||
if ($assoc->isOneToOne() && $assoc->isOwningSide()) {
|
||||
$entityIsDirty = true;
|
||||
} else if (/*is_null($actualValue) && */$orgValue instanceof PersistentCollection) {
|
||||
// A PersistentCollection was de-referenced, so delete it.
|
||||
if ( ! in_array($orgValue, $this->_collectionDeletions, true)) {
|
||||
$this->_collectionDeletions[] = $orgValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$entityIsDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($changeSet) {
|
||||
if ($entityIsDirty) {
|
||||
@ -403,6 +423,10 @@ class UnitOfWork
|
||||
*/
|
||||
private function _computeAssociationChanges($assoc, $value)
|
||||
{
|
||||
/*if ( ! $assoc->isCascadeSave()) {
|
||||
return; // "Persistence by reachability" only if save cascade enabled
|
||||
}*/
|
||||
|
||||
if ($assoc->isOneToOne()) {
|
||||
$value = array($value);
|
||||
}
|
||||
@ -428,13 +452,27 @@ class UnitOfWork
|
||||
|
||||
// NEW entities are INSERTed within the current unit of work.
|
||||
$data = array();
|
||||
$changeSet = array();
|
||||
foreach ($targetClass->getReflectionProperties() as $name => $refProp) {
|
||||
$data[$name] = $refProp->getValue($entry);
|
||||
$changeSet[$name] = array(null, $data[$name]);
|
||||
// --
|
||||
/*if ($targetClass->isCollectionValuedAssociation($name) && ! ($data[$name] instanceof PersistentCollection)) {
|
||||
// Inject PersistentCollection
|
||||
//TODO: If $actualData[$name] is Collection then unwrap the array
|
||||
$assoc = $targetClass->getAssociationMapping($name);
|
||||
$coll = new PersistentCollection($this->_em, $assoc->getTargetEntityName(),
|
||||
$data[$name] ? $data[$name] : array());
|
||||
$coll->_setOwner($entry, $assoc);
|
||||
if ( ! $coll->isEmpty()) $coll->setDirty(true);
|
||||
$targetClass->getReflectionProperty($name)->setValue($entry, $coll);
|
||||
$data[$name] = $coll;
|
||||
}*/
|
||||
//--
|
||||
}
|
||||
|
||||
$oid = spl_object_hash($entry);
|
||||
$this->_newEntities[$oid] = $entry;
|
||||
$this->_entityChangeSets[$oid] = $data;
|
||||
$this->_entityChangeSets[$oid] = $changeSet;
|
||||
$this->_originalEntityData[$oid] = $data;
|
||||
} else if ($state == self::STATE_DELETED) {
|
||||
throw new DoctrineException("Deleted entity in collection detected during flush.");
|
||||
@ -481,11 +519,6 @@ class UnitOfWork
|
||||
}
|
||||
}
|
||||
|
||||
private function _executeCollectionUpdate($collectionToUpdate)
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all entity updates for entities of the specified type.
|
||||
*
|
||||
@ -784,7 +817,7 @@ class UnitOfWork
|
||||
{
|
||||
$oid = spl_object_hash($entity);
|
||||
if ( ! isset($this->_entityStates[$oid])) {
|
||||
if (isset($this->_entityIdentifiers[$oid])) {
|
||||
if (isset($this->_entityIdentifiers[$oid]) && ! isset($this->_newEntities[$oid])) {
|
||||
$this->_entityStates[$oid] = self::STATE_DETACHED;
|
||||
} else {
|
||||
$this->_entityStates[$oid] = self::STATE_NEW;
|
||||
@ -824,7 +857,7 @@ class UnitOfWork
|
||||
*
|
||||
* @param string $idHash
|
||||
* @param string $rootClassName
|
||||
* @return Doctrine\ORM\Entity
|
||||
* @return object
|
||||
*/
|
||||
public function getByIdHash($idHash, $rootClassName)
|
||||
{
|
||||
@ -1071,8 +1104,8 @@ class UnitOfWork
|
||||
}
|
||||
$relatedEntities = $class->getReflectionProperty($assocMapping->getSourceFieldName())
|
||||
->getValue($entity);
|
||||
if ($relatedEntities instanceof \Doctrine\Common\Collections\Collection &&
|
||||
count($relatedEntities) > 0) {
|
||||
if ($relatedEntities instanceof \Doctrine\Common\Collections\Collection || is_array($relatedEntities)
|
||||
&& count($relatedEntities) > 0) {
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->_doDelete($relatedEntity, $visited);
|
||||
}
|
||||
@ -1323,23 +1356,29 @@ class UnitOfWork
|
||||
if ( ! isset($this->_persisters[$entityName])) {
|
||||
$class = $this->_em->getClassMetadata($entityName);
|
||||
if ($class->isInheritanceTypeJoined()) {
|
||||
$persister = new \Doctrine\ORM\Persisters\JoinedSubclassPersister($this->_em, $class);
|
||||
$persister = new Persisters\JoinedSubclassPersister($this->_em, $class);
|
||||
} else {
|
||||
$persister = new \Doctrine\ORM\Persisters\StandardEntityPersister($this->_em, $class);
|
||||
$persister = new Persisters\StandardEntityPersister($this->_em, $class);
|
||||
}
|
||||
$this->_persisters[$entityName] = $persister;
|
||||
}
|
||||
return $this->_persisters[$entityName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a collection persister for a collection-valued association.
|
||||
*
|
||||
* @param AssociationMapping $association
|
||||
* @return AbstractCollectionPersister
|
||||
*/
|
||||
public function getCollectionPersister($association)
|
||||
{
|
||||
$type = get_class($association);
|
||||
if ( ! isset($this->_collectionPersisters[$type])) {
|
||||
if ($association instanceof \Doctrine\ORM\Mapping\OneToManyMapping) {
|
||||
$persister = new \Doctrine\ORM\Persisters\OneToManyPersister($this->_em);
|
||||
} else if ($association instanceof \Doctrine\ORM\Mapping\ManyToManyMapping) {
|
||||
$persister = new \Doctrine\ORM\Persisters\ManyToManyPersister($this->_em);
|
||||
if ($association instanceof Mapping\OneToManyMapping) {
|
||||
$persister = new Persisters\OneToManyPersister($this->_em);
|
||||
} else if ($association instanceof Mapping\ManyToManyMapping) {
|
||||
$persister = new Persisters\ManyToManyPersister($this->_em);
|
||||
}
|
||||
$this->_collectionPersisters[$type] = $persister;
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ use Doctrine\ORM\Export\ClassExporter;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\Forum\ForumUser;
|
||||
use Doctrine\Tests\Models\Forum\ForumAvatar;
|
||||
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
@ -25,7 +24,8 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase {
|
||||
$exporter->exportClasses(array(
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress')
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup')
|
||||
));
|
||||
|
||||
// Create
|
||||
@ -48,7 +48,7 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase {
|
||||
$em->flush();
|
||||
$this->assertTrue($em->contains($ph));
|
||||
$this->assertTrue($em->contains($user));
|
||||
$this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\PersistentCollection);
|
||||
//$this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\PersistentCollection);
|
||||
|
||||
// Update name
|
||||
$user->name = 'guilherme';
|
||||
@ -90,7 +90,7 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase {
|
||||
$this->_em->save($user);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\PersistentCollection);
|
||||
//$this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\PersistentCollection);
|
||||
|
||||
// Remove the first element from the collection
|
||||
unset($user->phonenumbers[0]);
|
||||
@ -125,5 +125,63 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase {
|
||||
array($address->id))->fetchColumn();
|
||||
$this->assertTrue(is_numeric($userId));
|
||||
}
|
||||
|
||||
public function testBasicManyToMany()
|
||||
{
|
||||
$user = new CmsUser;
|
||||
$user->name = 'Guilherme';
|
||||
$user->username = 'gblanco';
|
||||
$user->status = 'developer';
|
||||
|
||||
$group = new CmsGroup;
|
||||
$group->name = 'Developers';
|
||||
|
||||
$user->groups[] = $group;
|
||||
$group->users[] = $user;
|
||||
|
||||
$this->_em->save($user);
|
||||
$this->_em->save($group);
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
unset($group->users[0]); // inverse side
|
||||
unset($user->groups[0]); // owning side!
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
// Check that the link in the association table has been deleted
|
||||
$count = $this->_em->getConnection()->execute("SELECT COUNT(*) FROM cms_users_groups",
|
||||
array())->fetchColumn();
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
|
||||
public function testManyToManyCollectionClearing()
|
||||
{
|
||||
$user = new CmsUser;
|
||||
$user->name = 'Guilherme';
|
||||
$user->username = 'gblanco';
|
||||
$user->status = 'developer';
|
||||
|
||||
for ($i=0; $i<10; ++$i) {
|
||||
$group = new CmsGroup;
|
||||
$group->name = 'Developers_' . $i;
|
||||
$user->groups[] = $group;
|
||||
$group->users[] = $user;
|
||||
}
|
||||
|
||||
$this->_em->save($user); // Saves the user, cause of post-insert ID
|
||||
|
||||
$this->_em->flush(); // Saves the groups, cause they're attached to a persistent entity ($user)
|
||||
|
||||
//$user->groups->clear();
|
||||
unset($user->groups);
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
// Check that the links in the association table have been deleted
|
||||
$count = $this->_em->getConnection()->execute("SELECT COUNT(*) FROM cms_users_groups",
|
||||
array())->fetchColumn();
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user