Commit c1c3f489 authored by romanb's avatar romanb

validator refactorings and others

parent 514e17ea
......@@ -120,7 +120,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
*
* @var array $columns
*/
protected $_columns = array();
protected $_mappedColumns = array();
/**
* An array of field names. used to look up field names from column names.
......@@ -417,8 +417,8 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
* getColumnName
*
* returns a column name for a field name.
* if the actual name for the alias cannot be found
* this method returns the given alias
* if the column name for the field cannot be found
* this method returns the given field name.
*
* @param string $alias column alias
* @return string column name
......@@ -428,21 +428,22 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
if (isset($this->_columnNames[$fieldName])) {
return $this->_columnNames[$fieldName];
}
return $fieldName;
}
/**
*
*
* @deprecated
*/
public function getColumnDefinition($columnName)
{
if ( ! isset($this->_columns[$columnName])) {
return false;
}
return $this->_columns[$columnName];
return $this->getColumnMapping($columnName);
}
public function getColumnMapping($columnName)
{
return isset($this->_mappedColumns[$columnName]) ?
$this->_mappedColumns[$columnName] : false;
}
/**
......@@ -471,7 +472,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
}
/**
* Maps a column of the class' database table to a property of the entity.
* Maps a column of the class' database table to a field of the entity.
*
* @param string $name The name of the column to map. Syntax: columnName [as propertyName].
* The property name is optional. If not used the column will be
......@@ -548,9 +549,9 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
$options['length'] = $length;
if ($prepend) {
$this->_columns = array_merge(array($name => $options), $this->_columns);
$this->_mappedColumns = array_merge(array($name => $options), $this->_mappedColumns);
} else {
$this->_columns[$name] = $options;
$this->_mappedColumns[$name] = $options;
}
if ( ! empty($options['primary'])) {
......@@ -586,6 +587,17 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
return $this->mapColumn($name, $type, $length, $options, $prepend);
}
/**
* Gets the names of all validators that are applied on a field.
*
*/
public function getFieldValidators($fieldName)
{
$columnName = $this->getColumnName($fieldName);
return isset($this->_mappedColumns[$columnName]['validators']) ?
$this->_mappedColumns[$columnName]['validators'] : array();
}
/**
* hasDefaultValues
* returns true if this class has default values, otherwise false
......@@ -607,11 +619,11 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
public function getDefaultValueOf($fieldName)
{
$columnName = $this->getColumnName($fieldName);
if ( ! isset($this->_columns[$columnName])) {
if ( ! isset($this->_mappedColumns[$columnName])) {
throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$columnName." doesn't exist.");
}
if (isset($this->_columns[$columnName]['default'])) {
return $this->_columns[$columnName]['default'];
if (isset($this->_mappedColumns[$columnName]['default'])) {
return $this->_mappedColumns[$columnName]['default'];
} else {
return null;
}
......@@ -649,7 +661,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
*/
public function hasColumn($columnName)
{
return isset($this->_columns[$columnName]);
return isset($this->_mappedColumns[$columnName]);
}
/**
......@@ -668,8 +680,8 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
public function getEnumValues($fieldName)
{
$columnName = $this->getColumnName($fieldName);
if (isset($this->_columns[$columnName]['values'])) {
return $this->_columns[$columnName]['values'];
if (isset($this->_mappedColumns[$columnName]['values'])) {
return $this->_mappedColumns[$columnName]['values'];
} else {
return array();
}
......@@ -694,8 +706,8 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
$columnName = $this->getColumnName($fieldName);
if ( ! $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM) &&
isset($this->_columns[$columnName]['values'][$index])) {
$enumValue = $this->_columns[$columnName]['values'][$index];
isset($this->_mappedColumns[$columnName]['values'][$index])) {
$enumValue = $this->_mappedColumns[$columnName]['values'][$index];
} else {
$enumValue = $index;
}
......@@ -726,20 +738,66 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
* getColumnCount
*
* @return integer the number of columns in this table
* @deprecated
*/
public function getColumnCount()
{
return $this->_columnCount;
}
/**
* getMappedColumnCount
*
* @return integer the number of mapped columns in the class.
*/
public function getMappedColumnCount()
{
return $this->_columnCount;
}
/**
*
* @return string The name of the accessor (getter) method or NULL if the field does
* not have a custom accessor.
*/
public function getCustomAccessor($fieldName)
{
$columnName = $this->getColumnName($fieldName);
return isset($this->_mappedColumns[$columnName]['accessor']) ?
$this->_mappedColumns[$columnName]['accessor'] : null;
}
/**
*
* @return string The name of the mutator (setter) method or NULL if the field does
* not have a custom mutator.
*/
public function getCustomMutator($fieldName)
{
$columnName = $this->getColumnName($fieldName);
return isset($this->_mappedColumns[$columnName]['mutator']) ?
$this->_mappedColumns[$columnName]['mutator'] : null;
}
/**
* returns all columns and their definitions
*
* @return array
* @deprecated
*/
public function getColumns()
{
return $this->_columns;
return $this->_mappedColumns;
}
/**
* Gets all mapped columns and their mapping definitions.
*
* @return array
*/
public function getMappedColumns()
{
return $this->_mappedColumns;
}
/**
......@@ -754,8 +812,8 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
unset($this->_fieldNames[$columnName]);
if (isset($this->_columns[$columnName])) {
unset($this->_columns[$columnName]);
if (isset($this->_mappedColumns[$columnName])) {
unset($this->_mappedColumns[$columnName]);
return true;
}
$this->_columnCount--;
......@@ -771,7 +829,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
public function getColumnNames(array $fieldNames = null)
{
if ($fieldNames === null) {
return array_keys($this->_columns);
return array_keys($this->_mappedColumns);
} else {
$columnNames = array();
foreach ($fieldNames as $fieldName) {
......@@ -831,33 +889,25 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
*/
public function getTypeOfColumn($columnName)
{
return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false;
return isset($this->_mappedColumns[$columnName]) ? $this->_mappedColumns[$columnName]['type'] : false;
}
/**
* getTableName
*
* @return void
* Gets the (maximum) length of a field.
*/
public function getTableName()
public function getFieldLength($fieldName)
{
return $this->getTableOption('tableName');
return $this->_mappedColumns[$this->getColumnName($fieldName)]['length'];
}
/**
* getTableName
*
* @return void
*/
public function getFieldMapping($fieldName)
{
return $this->getDefinitionOf($fieldName);
}
/**
*
*/
public function getFields()
public function getTableName()
{
return $this->_columns;
return $this->getTableOption('tableName');
}
public function getInheritedFields()
......@@ -865,12 +915,8 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
}
public function getAllFields()
{
}
/**
* Adds a named query.
*
* @param string $name The name under which the query gets registered.
* @param string $query The DQL query.
......@@ -952,6 +998,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
* returns all templates attached to this table
*
* @return array an array containing all templates
* @todo Unify under 'Behaviors'
*/
public function getTemplates()
{
......@@ -971,7 +1018,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
/**
* Sets the subclasses of the class.
* All entity classes that participate in a hierarchy and have subclasses
* need to declare them in this way.
* need to declare them this way.
*
* @param array $subclasses The names of all subclasses.
*/
......@@ -1002,6 +1049,8 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
/**
* Gets the names of all parent classes.
*
* @return array The names of all parent classes.
*/
public function getParentClasses()
{
......@@ -1048,6 +1097,14 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
}
}
/**
* Checks if the 2 options 'discriminatorColumn' and 'discriminatorMap' are present.
* If either of them is missing an exception is thrown.
*
* @param array $options The options.
* @throws Doctrine_ClassMetadata_Exception If at least one of the required discriminator
* options is missing.
*/
private function _checkRequiredDiscriminatorOptions(array $options)
{
if ( ! isset($options['discriminatorColumn'])) {
......@@ -1059,21 +1116,30 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
}
}
/**
* Gets an inheritance option.
*
*/
public function getInheritanceOption($name)
{
if ( ! array_key_exists($name, $this->_inheritanceOptions)) {
echo $name;
throw new Doctrine_ClassMetadata_Exception("Unknown inheritance option: '$name'.");
}
return $this->_inheritanceOptions[$name];
}
/**
* Gets all inheritance options.
*/
public function getInheritanceOptions()
{
return $this->_inheritanceOptions;
}
/**
* Sets an inheritance option.
*/
public function setInheritanceOption($name, $value)
{
if ( ! array_key_exists($name, $this->_inheritanceOptions)) {
......@@ -1082,7 +1148,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
switch ($name) {
case 'discriminatorColumn':
if ( $value !== null && ! is_string($value)) {
if ($value !== null && ! is_string($value)) {
throw new Doctrine_ClassMetadata_Exception("Invalid value '$value' for option"
. " 'discriminatorColumn'.");
}
......@@ -1395,7 +1461,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
*/
public function isInheritedField($fieldName)
{
return isset($this->_columns[$this->getColumnName($fieldName)]['inherited']);
return isset($this->_mappedColumns[$this->getColumnName($fieldName)]['inherited']);
}
/**
......@@ -1450,15 +1516,15 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab
}
/**
* Serializes the metadata class.
* Serializes the metadata.
*
* Part of the implementation of the Serializable interface.
*
* @return string The serialized metadata class.
* @return string The serialized metadata.
*/
public function serialize()
{
return serialize($this->_columns);
return serialize($this->_mappedColumns);
}
/**
......
......@@ -1047,9 +1047,9 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
$event = new Doctrine_Event($this, Doctrine_Event::CONN_ERROR);
$this->getListener()->preError($event);
/*if (strstr($e->getMessage(), 'no such column')) {
if (strstr($e->getMessage(), 'may not be NULL')) {
echo $e->getMessage() . "<br />" . $e->getTraceAsString() . "<br />";
}*/
}
$name = 'Doctrine_Connection_' . $this->driverName . '_Exception';
......
......@@ -29,15 +29,61 @@ Doctrine::autoload('Doctrine_Connection_Module');
* @since 1.0
* @version $Revision$
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @todo package:orm. Figure out a useful implementation.
*/
class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module
{
/**
* A map of all currently managed entities.
*
* @var array
*/
protected $_managedEntities = array();
/**
* The identity map that holds references to all managed entities that have
* an identity.
*/
protected $_identityMap = array();
/**
* Boolean flag that indicates whether the unit of work immediately executes any
* database operations or whether these operations are postponed until the
* unit of work is flushed/committed.
*
* @var boolean
*/
protected $_autoflush = true;
/**
* A list of all postponed inserts.
*/
protected $_inserts = array();
/**
* A list of all postponed updates.
*/
protected $_updates = array();
/**
* A list of all postponed deletes.
*/
protected $_deletes = array();
/**
* The dbal connection used by the unit of work.
*
* @var Doctrine_Connection
* @todo Allow multiple connections for transparent master-slave replication.
*/
protected $_conn;
/**
* Flushes the unit of work, executing all operations that have been postponed
* up to this point.
*
*/
public function flush()
{
// get the flush tree
......@@ -47,7 +93,6 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module
foreach ($tree as $name) {
$mapper = $this->conn->getMapper($name);
foreach ($mapper->getRepository() as $record) {
//echo $record->getOid() . "<br />";
$mapper->saveSingleRecord($record);
}
}
......@@ -95,7 +140,7 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module
if ( ! ($mapper instanceof Doctrine_Mapper)) {
$mapper = $this->conn->getMapper($mapper);
}
$nm = $mapper->getComponentName();
$nm = $mapper->getComponentName();
$index = array_search($nm, $tree);
......@@ -172,6 +217,7 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module
}
}
}
return array_values($tree);
}
......
......@@ -497,6 +497,21 @@ class Doctrine_Manager extends Doctrine_Configurable implements Countable, Itera
return $query;
}
/**
* Creates a new native query (instance of Doctrine_RawSql).
*
* @return Doctrine_RawSql
*/
public function createNativeQuery($sql = "")
{
$nativeQuery = new Doctrine_RawSql($this->getCurrentConnection());
if ( ! empty($sql)) {
$nativeQuery->parseQuery($sql);
}
return $nativeQuery;
}
/**
* Creates a query object out of a registered, named query.
*
......
......@@ -317,6 +317,17 @@ class Doctrine_Mapper extends Doctrine_Configurable implements Countable
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).
*/
public function manage(Doctrine_Record $record)
{
return $this->getRepository()->add($record);
}
/**
* removeRecord
......
......@@ -80,11 +80,37 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
* and saves will not cause infinite loops
*/
const STATE_LOCKED = 6;
/**
* Index used for creating object identifiers (oid's).
*
* @var integer $index
*/
private static $_index = 1;
/**
* Boolean flag that indicated whether automatic accessor overriding is enabled.
*/
private static $_useAutoAccessorOverride;
/**
* The accessor cache is used as a memory for the existance of custom accessors
* for fields.
* Only used when ATTR_ACCESSOR_OVERRIDE is set to ACCESSOR_OVERRIDE_AUTO.
*/
private static $_accessorCache = array();
/**
* The mutator cache is used as a memory for the existance of custom mutators
* for fields.
* Only used when ATTR_ACCESSOR_OVERRIDE is set to ACCESSOR_OVERRIDE_MANUAL.
*/
private static $_mutatorCache = array();
/**
*
*/
protected $_domainClassName;
protected $_entityName;
/**
* @var Doctrine_Node_<TreeImpl> node object
......@@ -132,7 +158,8 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
/**
* The error stack used to collect errors during validation.
*
* @var Doctrine_Validator_ErrorStack
* @var Doctrine_Validator_ErrorStack
* @internal Uses lazy initialization to reduce memory usage.
*/
protected $_errorStack;
......@@ -143,13 +170,6 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
*/
protected $_references = array();
/**
* Index used for creating object identifiers (oid's).
*
* @var integer $index
*/
private static $_index = 1;
/**
* The object identifier of the object. Each object has a unique identifier during runtime.
*
......@@ -182,7 +202,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
$exists = false;
}
$this->_domainClassName = get_class($this);
$this->_entityName = $this->_mapper->getMappedClassName();
$this->_oid = self::$_index;
self::$_index++;
......@@ -208,15 +228,13 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
$this->assignDefaultValues();
} else {
$this->_state = Doctrine_Record::STATE_CLEAN;
// @TODO table->getColumnCount is not correct in CTI
if ($count < $this->_table->getColumnCount()) {
$this->_state = Doctrine_Record::STATE_PROXY;
}
}
$repository = $this->_mapper->getRepository();
$repository->add($this);
self::$_useAutoAccessorOverride = false; // @todo read from attribute the first time
$this->_mapper->manage($this);
$this->construct();
}
......@@ -842,18 +860,38 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
*/
public function get($fieldName, $load = true)
{
/*// check for custom accessor, if not done yet.
if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) {
if (self::$_useAutoAccessorOverride) {
$getterMethod = 'get' . Doctrine::classify($fieldName);
if (method_exists($this, $getterMethod)) {
self::$_accessorCache[$this->_entityName][$fieldName] = $getterMethod;
} else {
self::$_accessorCache[$this->_entityName][$fieldName] = false;
}
}
if ($getter = $this->_table->getCustomAccessor($fieldName)) {
self::$_accessorCache[$this->_entityName][$fieldName] = $getter;
} else if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) {
self::$_accessorCache[$this->_entityName][$fieldName] = false;
}
}
// invoke custom accessor, if it exists.
if ($getter = self::$_accessorCache[$this->_entityName][$fieldName]) {
return $this->$getter();
}*/
// Use built-in accessor functionality
$value = self::$_null;
if (isset($this->_data[$fieldName])) {
// check if the value is the Doctrine_Null object located in self::$_null)
if ($this->_data[$fieldName] !== self::$_null) {
return $this->_data[$fieldName];
}
if ($this->_data[$fieldName] === self::$_null && $load) {
$this->load();
}
if ($this->_data[$fieldName] === self::$_null) {
$value = null;
} else {
$value = $this->_data[$fieldName];
}
return $value;
}
......@@ -869,10 +907,6 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
}
return $this->_references[$fieldName];
} catch (Doctrine_Relation_Exception $e) {
//echo $this->_domainClassName . "<br />";
//var_dump($this->_values);
//echo $e->getTraceAsString();
//echo "<br /><br />";
foreach ($this->_table->getFilters() as $filter) {
if (($value = $filter->filterGet($this, $fieldName, $value)) !== null) {
return $value;
......@@ -916,9 +950,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
if (isset($this->_data[$fieldName])) {
if ($value instanceof Doctrine_Record) {
$type = $this->_table->getTypeOf($fieldName);
$id = $value->getIncremented();
if ($id !== null && $type !== 'object') {
$value = $id;
}
......@@ -948,15 +980,6 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
}
} else {
try {
/*echo $this->_domainClassName;
var_dump($this->_data);
echo "<br /><br />";
try {
throw new Exception();
} catch (Exception $e) {
echo $e->getTraceAsString() . "<br />";
}
echo "<br /><br />";*/
$this->_coreSetRelated($fieldName, $value);
} catch (Doctrine_Relation_Exception $e) {
foreach ($this->_table->getFilters() as $filter) {
......@@ -1063,11 +1086,8 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
}
/**
* applies the changes made to this object into database
* this method is smart enough to know if any changes are made
* and whether to use INSERT or UPDATE statement
*
* this method also saves the related components
* Applies the changes made to this object into database.
* This method also saves the related components.
*
* @param Doctrine_Connection $conn optional connection parameter
* @return void
......@@ -1078,7 +1098,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
}
/**
* Tries to save the object and all its related components.
* Tries to save the object and all its related objects.
* In contrast to Doctrine_Record::save(), this method does not
* throw an exception when validation fails but returns TRUE on
* success or FALSE on failure.
......@@ -1133,7 +1153,8 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
}
/**
* returns an array of modified fields and associated values.
* Gets the names and values of all fields that have been modified since
* the entity was last synch'd with the database.
*
* @return array
*/
......@@ -1159,7 +1180,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
*/
public function getPrepared(array $array = array())
{
$a = array();
$dataSet = array();
if (empty($array)) {
$modifiedFields = $this->_modified;
......@@ -1169,23 +1190,23 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
$type = $this->_table->getTypeOf($field);
if ($this->_data[$field] === self::$_null) {
$a[$field] = null;
$dataSet[$field] = null;
continue;
}
switch ($type) {
case 'array':
case 'object':
$a[$field] = serialize($this->_data[$field]);
$dataSet[$field] = serialize($this->_data[$field]);
break;
case 'gzip':
$a[$field] = gzcompress($this->_data[$field],5);
$dataSet[$field] = gzcompress($this->_data[$field],5);
break;
case 'boolean':
$a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]);
$dataSet[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]);
break;
case 'enum':
$a[$field] = $this->_table->enumIndex($field, $this->_data[$field]);
$dataSet[$field] = $this->_table->enumIndex($field, $this->_data[$field]);
break;
default:
if ($this->_data[$field] instanceof Doctrine_Record) {
......@@ -1197,7 +1218,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
}
*/
$a[$field] = $this->_data[$field];
$dataSet[$field] = $this->_data[$field];
}
}
......@@ -1208,14 +1229,14 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
$discCol = $this->_table->getInheritanceOption('discriminatorColumn');
$discMap = $this->_table->getInheritanceOption('discriminatorMap');
$old = $this->get($discCol, false);
$v = array_search($this->_domainClassName, $discMap);
if ((string) $old !== (string) $v || $old === null) {
$a[$discCol] = $v;
$this->_data[$discCol] = $v;
$discValue = array_search($this->_entityName, $discMap);
if ((string) $old !== (string) $discValue || $old === null) {
$dataSet[$discCol] = $discValue;
$this->_data[$discCol] = $discValue;
}
}
return $a;
return $dataSet;
}
/**
......@@ -1249,7 +1270,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
$a[$column] = $value;
}
if ($this->_table->getIdentifierType() == Doctrine::IDENTIFIER_AUTOINC) {
if ($this->_table->getIdentifierType() == Doctrine::IDENTIFIER_AUTOINC) {
$i = $this->_table->getIdentifier();
$a[$i] = $this->getIncremented();
}
......@@ -1275,9 +1296,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
}
/**
* merge
*
* merges this record with an array of values
* Merges this record with an array of values
* or with another existing instance of this object
*
* @param mixed $data Data to merge. Either another instance of this model or an array
......@@ -1427,7 +1446,7 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
/**
* Deletes the entity.
*
* This event can be listened by the onPreDelete and onDelete listeners
* Triggered events: onPreDelete, onDelete.
*
* @return boolean true on success, false on failure
*/
......@@ -1755,7 +1774,6 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
$rel = $this->getTable()->getRelation($alias);
if ($rel instanceof Doctrine_Relation_Association) {
$modelClassName = $rel->getAssociationTable()->getComponentName();
$localFieldName = $rel->getLocalFieldName();
$localFieldDef = $rel->getAssociationTable()->getColumnDefinition($localFieldName);
......@@ -1863,10 +1881,20 @@ abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Count
return (string) $this->_oid;
}
/**
* Helps freeing the memory occupied by the entity.
* Cuts all references the entity has to other entities and removes the entity
* from the instance pool.
* Note: The entity is no longer useable after free() has been called. Any operations
* done with the entity afterwards can lead to unpredictable results.
*/
public function free()
{
$this->_mapper->getRepository()->evict($this->_oid);
$this->_mapper->removeRecord($this);
$this->_data = array();
$this->_id = array();
$this->_references = array();
}
}
......@@ -67,6 +67,7 @@ class Doctrine_Validator extends Doctrine_Locator_Injectable
*/
public function validateRecord(Doctrine_Record $record)
{
$classMetadata = $record->getTable();
$columns = $record->getTable()->getColumns();
$component = $record->getTable()->getComponentName();
......@@ -74,90 +75,49 @@ class Doctrine_Validator extends Doctrine_Locator_Injectable
// if record is transient all fields will be validated
// if record is persistent only the modified fields will be validated
$data = ($record->exists()) ? $record->getModified() : $record->getData();
$err = array();
foreach ($data as $key => $value) {
$fields = ($record->exists()) ? $record->getModified() : $record->getData();
$err = array();
foreach ($fields as $fieldName => $value) {
if ($value === self::$_null) {
$value = null;
} else if ($value instanceof Doctrine_Record) {
$value = $value->getIncremented();
}
$dataType = $classMetadata->getTypeOf($fieldName);
$column = $columns[$record->getTable()->getColumnName($key)];
if ($column['type'] == 'enum') {
$value = $record->getTable()->enumIndex($key, $value);
if ($value === false) {
$errorStack->add($key, 'enum');
continue;
// Validate field type, if type validation is enabled
if ($classMetadata->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_TYPES) {
if ( ! self::isValidType($value, $dataType)) {
$errorStack->add($fieldName, 'type');
}
if ($dataType == 'enum') {
$enumIndex = $classMetadata->enumIndex($fieldName, $value);
if ($enumIndex === false) {
$errorStack->add($fieldName, 'enum');
}
}
}
// Validate field length, if length validation is enabled
if ($record->getTable()->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_LENGTHS) {
if ( ! $this->validateLength($column, $key, $value)) {
$errorStack->add($key, 'length');
continue;
if ( ! $this->validateLength($value, $dataType, $classMetadata->getFieldLength($fieldName))) {
$errorStack->add($fieldName, 'length');
}
}
foreach ($column as $name => $args) {
if (empty($name)
|| $name == 'primary'
|| $name == 'protected'
|| $name == 'autoincrement'
|| $name == 'default'
|| $name == 'values'
|| $name == 'sequence'
|| $name == 'zerofill'
|| $name == 'scale'
|| $name == 'inherited') {
continue;
// Run all custom validators
foreach ($classMetadata->getFieldValidators($fieldName) as $validatorName => $args) {
if ( ! is_string($validatorName)) {
$validatorName = $args;
$args = array();
}
if (strtolower($name) === 'notnull' && isset($column['autoincrement'])) {
continue;
}
if (strtolower($name) == 'length') {
if ( ! ($record->getTable()->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_LENGTHS)) {
if ( ! $this->validateLength($column, $key, $value)) {
$errorStack->add($key, 'length');
}
}
continue;
}
if (strtolower($name) == 'type') {
if ( ! ($record->getTable()->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_TYPES)) {
if ( ! self::isValidType($value, $column['type'])) {
$errorStack->add($key, 'type');
}
}
continue;
}
$validator = self::getValidator($name);
$validator = self::getValidator($validatorName);
$validator->invoker = $record;
$validator->field = $key;
$validator->args = $args;
$validator->field = $fieldName;
$validator->args = $args;
if ( ! $validator->validate($value)) {
$errorStack->add($key, $name);
//$err[$key] = 'not valid';
// errors found quit validation looping for this column
//break;
}
}
if ($record->getTable()->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_TYPES) {
if ( ! self::isValidType($value, $column['type'])) {
$errorStack->add($key, 'type');
continue;
$errorStack->add($fieldName, $validatorName);
}
}
}
......@@ -166,18 +126,16 @@ class Doctrine_Validator extends Doctrine_Locator_Injectable
/**
* Validates the length of a field.
*/
private function validateLength($column, $key, $value)
private function validateLength($value, $type, $maximumLength)
{
if ($column['type'] == 'timestamp' || $column['type'] == 'integer' ||
$column['type'] == 'enum') {
if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') {
return true;
} else if ($column['type'] == 'array' || $column['type'] == 'object') {
} else if ($type == 'array' || $type == 'object') {
$length = strlen(serialize($value));
} else {
$length = strlen($value);
}
if ($length > $column['length']) {
if ($length > $maximumLength) {
return false;
}
return true;
......@@ -191,68 +149,7 @@ class Doctrine_Validator extends Doctrine_Locator_Injectable
public function hasErrors()
{
return (count($this->stack) > 0);
}
/**
* phpType
* converts a doctrine type to native php type
*
* @param $portableType portable doctrine type
* @return string
*//*
public static function phpType($portableType)
{
switch ($portableType) {
case 'enum':
return 'integer';
case 'blob':
case 'clob':
case 'mbstring':
case 'timestamp':
case 'date':
case 'gzip':
return 'string';
break;
default:
return $portableType;
}
}*/
/**
* returns whether or not the given variable is
* valid type
*
* @param mixed $var
* @param string $type
* @return boolean
*/
/*
public static function isValidType($var, $type)
{
if ($type == 'boolean') {
return true;
}
$looseType = self::gettype($var);
$type = self::phpType($type);
switch ($looseType) {
case 'float':
case 'double':
case 'integer':
if ($type == 'string' || $type == 'float') {
return true;
}
case 'string':
case 'array':
case 'object':
return ($type === $looseType);
break;
case 'NULL':
return true;
break;
}
}*/
}
/**
* returns whether or not the given variable is
......@@ -300,29 +197,4 @@ class Doctrine_Validator extends Doctrine_Locator_Injectable
return false;
}
}
/**
* returns the type of loosely typed variable
*
* @param mixed $var
* @return string
*//*
public static function gettype($var)
{
$type = gettype($var);
switch ($type) {
case 'string':
if (preg_match("/^[0-9]+$/",$var)) {
return 'integer';
} elseif (is_numeric($var)) {
return 'float';
} else {
return $type;
}
break;
default:
return $type;
}
}*/
}
......@@ -44,6 +44,16 @@ class Orm_Component_AccessTest extends Doctrine_OrmTestCase
$this->user = new ForumUser();
}
public function testAccessorOverridePerformance() {
$this->user->username;
$start = microtime(true);
for ($i = 0; $i < 1; $i++) {
$this->user->username;
}
$end = microtime(true);
echo ($end - $start) . " seconds" . PHP_EOL;
}
/**
* @test
*/
......
......@@ -119,24 +119,4 @@ class Doctrine_OrmTestCase extends Doctrine_TestCase
$conn->exec("DELETE FROM " . $conn->getClassMetadata($model)->getTableName());
}
}
/*
public function loadFixturesPackage($package, $models = array())
{
$packagePath = 'fixtures' . DIRECTORY_SEPARATOR . $package;
if ( ! file_exists($packagePath)) {
throw new Exception("Could not find fixtures package: $package.");
}
$modelsPath = $packagePath . DIRECTORY_SEPARATOR . 'models';
$dataPath = $packagePath . DIRECTORY_SEPARATOR . 'data';
Doctrine::loadModels($modelsPath);
Doctrine::createTablesFromModels($modelsPath);
$data = new Doctrine_Data();
$data->importData($dataPath, 'yml', $models);
}
*/
}
\ No newline at end of file
......@@ -16,11 +16,17 @@ class ForumUser extends Doctrine_Record
// the discriminator column
$class->mapColumn('dtype', 'string', 50);
// property mapping
// column mapping
$class->mapColumn('id', 'integer', 4, array(
'primary' => true,
'autoincrement' => true));
$class->mapColumn('username', 'string', 50);
}
/*
public function getUsername()
{
return $this->rawGet('username') . "!";
}
*/
}
\ No newline at end of file
......@@ -300,4 +300,13 @@ class Doctrine_UnitTestCase extends UnitTestCase
$this->init = true;
}
public function tearDown()
{
/*foreach ($this->tables as $table) {
foreach ($this->conn->getMapper($table)->getRepository() as $obj) {
$obj->free();
}
}*/
}
}
......@@ -116,11 +116,15 @@ class UnitTestCase
foreach (get_class_methods($this) as $method) {
if (substr($method, 0, 4) === 'test') {
$this->setUp();
$this->$method();
$this->tearDown();
}
}
}
public function setUp() {}
public function tearDown() {}
public function getMessages()
{
return $this->_messages;
......
......@@ -17,20 +17,20 @@ class Doctrine_Inheritance_SingleTable_TestCase extends Doctrine_UnitTestCase
public function testMetadataSetup()
{
$userTable = $this->conn->getMetadata('STI_User');
$superManagerTable = $this->conn->getMetadata('STI_SuperManager');
$managerTable = $this->conn->getMetadata('STI_Manager');
$customerTable = $this->conn->getMetadata('STI_Customer');
$userClass = $this->conn->getClassMetadata('STI_User');
$superManagerClass = $this->conn->getClassMetadata('STI_SuperManager');
$managerClass = $this->conn->getClassMetadata('STI_Manager');
$customerClass = $this->conn->getClassMetadata('STI_Customer');
$this->assertEqual(4, count($userTable->getFields()));
$this->assertEqual('sti_entity', $userTable->getTableName());
$this->assertEqual('sti_entity', $managerTable->getTableName());
$this->assertEqual(4, count($userClass->getMappedColumns()));
$this->assertEqual('sti_entity', $userClass->getTableName());
$this->assertEqual('sti_entity', $managerClass->getTableName());
// check inheritance map
$this->assertEqual(array(1 => 'STI_User',
2 => 'STI_Manager',
3 => 'STI_Customer',
4 => 'STI_SuperManager'), $userTable->getInheritanceOption('discriminatorMap'));
4 => 'STI_SuperManager'), $userClass->getInheritanceOption('discriminatorMap'));
//var_dump($superManagerTable->getComponentName());
}
......
......@@ -25,8 +25,8 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
$userClass = $this->conn->getClassMetadata('Metadata_User');
$this->assertTrue($userClass instanceof Doctrine_ClassMetadata);
$this->assertEqual('cti_user', $userClass->getTableName());
$this->assertEqual(4, count($userClass->getFields()));
$this->assertIdentical(array(), $userClass->getOption('parents'));
$this->assertEqual(4, count($userClass->getMappedColumns()));
$this->assertIdentical(array(), $userClass->getParentClasses());
$this->assertEqual('type', $userClass->getInheritanceOption('discriminatorColumn'));
$this->assertIdentical(array(
1 => 'CTI_User',
......@@ -37,9 +37,9 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
$managerClass = $this->conn->getMetadata('Metadata_Manager');
$this->assertTrue($managerClass instanceof Doctrine_ClassMetadata);
$this->assertIdentical(array('Metadata_User'), $managerClass->getOption('parents'));
$this->assertIdentical(array('Metadata_User'), $managerClass->getParentClasses());
$this->assertEqual('cti_manager', $managerClass->getTableName());
$this->assertEqual(5, count($managerClass->getFields()));
$this->assertEqual(5, count($managerClass->getMappedColumns()));
$this->assertEqual('type', $managerClass->getInheritanceOption('discriminatorColumn'));
$this->assertIdentical(array(
1 => 'CTI_User',
......@@ -50,9 +50,9 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
$suManagerClass = $this->conn->getMetadata('Metadata_SuperManager');
$this->assertTrue($suManagerClass instanceof Doctrine_ClassMetadata);
$this->assertIdentical(array('Metadata_Manager', 'Metadata_User'), $suManagerClass->getOption('parents'));
$this->assertIdentical(array('Metadata_Manager', 'Metadata_User'), $suManagerClass->getParentClasses());
$this->assertEqual('cti_supermanager', $suManagerClass->getTableName());
$this->assertEqual(6, count($suManagerClass->getFields()));
$this->assertEqual(6, count($suManagerClass->getMappedColumns()));
$this->assertEqual('type', $suManagerClass->getInheritanceOption('discriminatorColumn'));
$this->assertIdentical(array(
1 => 'CTI_User',
......@@ -65,7 +65,7 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
public function testExportableFormatOfClassInClassTableInheritanceHierarchy()
{
$userClass = $this->conn->getMetadata('Metadata_User');
$userClass = $this->conn->getClassMetadata('Metadata_User');
$userClassExportableFormat = $userClass->getExportableFormat();
$this->assertEqual(4, count($userClassExportableFormat['columns']));
$this->assertTrue(isset($userClassExportableFormat['columns']['cti_id']));
......@@ -75,21 +75,21 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
$this->assertTrue(isset($userClassExportableFormat['columns']['cti_name']));
$this->assertTrue(isset($userClassExportableFormat['columns']['type']));
$managerClass = $this->conn->getMetadata('Metadata_Manager');
$managerClass = $this->conn->getClassMetadata('Metadata_Manager');
$managerClassExportableFormat = $managerClass->getExportableFormat();
$this->assertEqual(2, count($managerClassExportableFormat['columns']));
$this->assertTrue(isset($managerClassExportableFormat['columns']['cti_id']));
$this->assertTrue(isset($managerClassExportableFormat['columns']['cti_id']['primary']));
$this->assertFalse(isset($managerClassExportableFormat['columns']['cti_id']['autoincrement']));
$customerClass = $this->conn->getMetadata('Metadata_Customer');
$customerClass = $this->conn->getClassMetadata('Metadata_Customer');
$customerClassExportableFormat = $customerClass->getExportableFormat();
$this->assertEqual(2, count($customerClassExportableFormat['columns']));
$this->assertTrue(isset($customerClassExportableFormat['columns']['cti_id']));
$this->assertTrue(isset($customerClassExportableFormat['columns']['cti_id']['primary']));
$this->assertFalse(isset($customerClassExportableFormat['columns']['cti_id']['autoincrement']));
$superManagerClass = $this->conn->getMetadata('Metadata_SuperManager');
$superManagerClass = $this->conn->getClassMetadata('Metadata_SuperManager');
$superManagerClassExportableFormat = $superManagerClass->getExportableFormat();
$this->assertEqual(2, count($superManagerClassExportableFormat['columns']));
$this->assertTrue(isset($superManagerClassExportableFormat['columns']['cti_id']));
......@@ -99,11 +99,11 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
public function testMetadataSetupOnSingleTableInheritanceHierarchy()
{
$userClass = $this->conn->getMetadata('Metadata_STI_User');
$userClass = $this->conn->getClassMetadata('Metadata_STI_User');
$this->assertTrue($userClass instanceof Doctrine_ClassMetadata);
$this->assertEqual('cti_user', $userClass->getTableName());
$this->assertEqual(4, count($userClass->getFields()));
$this->assertIdentical(array(), $userClass->getOption('parents'));
$this->assertEqual(4, count($userClass->getMappedColumns()));
$this->assertIdentical(array(), $userClass->getParentClasses());
$this->assertEqual('type', $userClass->getInheritanceOption('discriminatorColumn'));
$this->assertIdentical(array(
1 => 'CTI_User',
......@@ -111,11 +111,11 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
3 => 'CTI_Customer',
4 => 'CTI_SuperManager'), $userClass->getInheritanceOption('discriminatorMap'));
$managerClass = $this->conn->getMetadata('Metadata_STI_Manager');
$managerClass = $this->conn->getClassMetadata('Metadata_STI_Manager');
$this->assertTrue($managerClass instanceof Doctrine_ClassMetadata);
$this->assertIdentical(array('Metadata_STI_User'), $managerClass->getOption('parents'));
$this->assertIdentical(array('Metadata_STI_User'), $managerClass->getParentClasses());
$this->assertEqual('cti_user', $managerClass->getTableName());
$this->assertEqual(5, count($managerClass->getFields()));
$this->assertEqual(5, count($managerClass->getMappedColumns()));
$this->assertEqual('type', $managerClass->getInheritanceOption('discriminatorColumn'));
$this->assertIdentical(array(
1 => 'CTI_User',
......@@ -124,11 +124,11 @@ class Doctrine_Metadata_Factory_TestCase extends Doctrine_UnitTestCase
4 => 'CTI_SuperManager'), $managerClass->getInheritanceOption('discriminatorMap'));
$suManagerClass = $this->conn->getMetadata('Metadata_STI_SuperManager');
$suManagerClass = $this->conn->getClassMetadata('Metadata_STI_SuperManager');
$this->assertTrue($suManagerClass instanceof Doctrine_ClassMetadata);
$this->assertIdentical(array('Metadata_STI_Manager', 'Metadata_STI_User'), $suManagerClass->getOption('parents'));
$this->assertIdentical(array('Metadata_STI_Manager', 'Metadata_STI_User'), $suManagerClass->getParentClasses());
$this->assertEqual('cti_user', $suManagerClass->getTableName());
$this->assertEqual(6, count($suManagerClass->getFields()));
$this->assertEqual(6, count($suManagerClass->getMappedColumns()));
$this->assertEqual('type', $suManagerClass->getInheritanceOption('discriminatorColumn'));
$this->assertIdentical(array(
1 => 'CTI_User',
......
......@@ -56,4 +56,5 @@ class Doctrine_Ticket_381_TestCase extends Doctrine_UnitTestCase {
$this->assertEqual($obj->get('name'), 'yes2');
$obj->save();
}
}
......@@ -139,6 +139,7 @@ class Doctrine_Validator_TestCase extends Doctrine_UnitTestCase
public function testValidate()
{
$this->manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
$user = $this->connection->getMapper('User')->find(4);
$set = array('password' => 'this is an example of too long password',
......@@ -152,11 +153,11 @@ class Doctrine_Validator_TestCase extends Doctrine_UnitTestCase
$this->assertTrue($user->getModified() == $set);
$validator = new Doctrine_Validator();
$validator->validateRecord($user);
$validator->validateRecord($user);
$stack = $user->errorStack();
$this->assertTrue($stack instanceof Doctrine_Validator_ErrorStack);
$this->assertTrue(in_array('length', $stack['loginname']));
$this->assertTrue(in_array('length', $stack['password']));
......@@ -171,6 +172,7 @@ class Doctrine_Validator_TestCase extends Doctrine_UnitTestCase
$stack = $email->errorStack();
$this->assertTrue(in_array('unique', $stack['address']));
$this->manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_NONE);
}
/**
......
......@@ -3,7 +3,8 @@ class Email extends Doctrine_Record
{
public static function initMetadata($class)
{
$class->setColumn('address', 'string', 150, array('email', 'unique' => true));
$class->setColumn('address', 'string', 150,
array('email', 'unique' => true, 'validators' => array('email', 'unique')));
}
......
......@@ -5,7 +5,7 @@ class Entity extends Doctrine_Record
{
$class->setColumn('id', 'integer',20, array('autoincrement' => true, 'primary' => true));
$class->setColumn('name', 'string',50);
$class->setColumn('loginname', 'string',20, array('unique' => true));
$class->setColumn('loginname', 'string',20, array('unique' => true, 'validators' => array('unique')));
$class->setColumn('password', 'string',16);
$class->setColumn('type', 'integer');
$class->setColumn('created', 'integer',11);
......
......@@ -2,14 +2,23 @@
class ValidatorTest extends Doctrine_Record {
public static function initMetadata($class) {
$class->setColumn('mymixed', 'string', 100);
$class->setColumn('mystring', 'string', 100, array('notnull', 'unique'));
$class->setColumn('mystring', 'string', 100,
array('validators' => array('notnull', 'unique'))
);
$class->setColumn('myarray', 'array', 1000);
$class->setColumn('myobject', 'object', 1000);
$class->setColumn('myinteger', 'integer', 11);
$class->setColumn('myrange', 'integer', 11, array('range' => array(4,123)));
$class->setColumn('myregexp', 'string', 5, array('regexp' => '/^[0-9]+$/'));
$class->setColumn('myemail', 'string', 100, array('email'));
$class->setColumn('myemail2', 'string', 100, array('email', 'notblank'));
$class->setColumn('myrange', 'integer', 11,
array('validators' => array('range' => array(4,123)))
);
$class->setColumn('myregexp', 'string', 5,
array('validators' => array('regexp' => '/^[0-9]+$/'))
);
$class->setColumn('myemail', 'string', 100,
array('validators' => array('email'))
);
$class->setColumn('myemail2', 'string', 100,
array('validators' => array('email', 'notblank'))
);
}
}
......@@ -4,11 +4,16 @@ class ValidatorTest_AddressModel extends Doctrine_Record {
$class->setColumn("id", "integer", 11, array('autoincrement' => true,
'primary' => true
));
$class->setColumn('address1', 'string', 255, array('notnull' => true, 'notblank'));
$class->setColumn('address2', 'string', 255, array('notnull' => true));
$class->setColumn('city', 'string', 255, array('notnull' => true, 'notblank'));
$class->setColumn('state', 'string', 10, array('notnull' => true, 'notblank', 'usstate'));
$class->setColumn('zip', 'string', 15, array('notnull' => true, 'notblank', 'regexp' => '/^[0-9-]*$/'));
$class->setColumn('address1', 'string', 255, array('notnull' => true,
'validators' => array('notnull', 'notblank')));
$class->setColumn('address2', 'string', 255, array('notnull' => true,
'validators' => array('notnull')));
$class->setColumn('city', 'string', 255, array('notnull' => true,
'validators' => array('notnull', 'notblank')));
$class->setColumn('state', 'string', 10, array('notnull' => true,
'validators' => array('notnull', 'notblank', 'usstate')));
$class->setColumn('zip', 'string', 15, array('notnull' => true,
'validators' => array('notnull', 'notblank', 'regexp' => '/^[0-9-]*$/')));
$class->hasMany('ValidatorTest_ClientModel', array('local' => 'address_id', 'foreign' => 'client_id', 'refClass' => 'ValidatorTest_ClientToAddressModel'));
}
}
<?php
class ValidatorTest_DateModel extends Doctrine_Record {
public static function initMetadata($class) {
$class->setColumn('birthday', 'date', null, array('past'));
$class->setColumn('death', 'date', null, array('future'));
$class->setColumn('birthday', 'date', null,
array('validators' => array('past')));
$class->setColumn('death', 'date', null,
array('validators' => array('future')));
}
}
<?php
class ValidatorTest_Person extends Doctrine_Record {
public static function initMetadata($class) {
$class->setColumn('identifier', 'integer', 4, array('notblank', 'unique'));
$class->setColumn('identifier', 'integer', 4,
array('validators' => array('notblank', 'unique')));
$class->setColumn('is_football_player', 'boolean');
$class->hasOne('ValidatorTest_FootballPlayer', array('local' => 'id', 'foreign' => 'person_id'));
$class->hasOne('ValidatorTest_FootballPlayer',
array('local' => 'id', 'foreign' => 'person_id'));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment