Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Sql/Model/Related.php
<?php
/**
 * 
 * Abstract class to represent the characteristics of a related model.
 * 
 * @category Solar
 * 
 * @package Solar_Sql_Model
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @author Jeff Moore <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: Related.php 4514 2010-03-15 15:06:16Z pmjones $
 * 
 */
abstract class Solar_Sql_Model_Related extends Solar_Base {
    
    /**
     * 
     * User-defined configuration values.
     * 
     * @config int wherein_max The default value for the 'wherein_max'
     * setting.
     * 
     * @var array
     * 
     */
    protected $_Solar_Sql_Model_Related = array(
        'wherein_max' => 100,
    );
    
    /**
     * 
     * Indicates the strategy to use for merging joined rows; 'server' means
     * the database will do it via a single SELECT combined into the native 
     * fetch, whereas 'client' means PHP will do it, using one additional 
     * SELECT for the relationship.
     * 
     * @var string
     * 
     */
    public $merge;
    
    /**
     * 
     * The name of the relationship as defined by the original (native) model.
     * 
     * @var string
     * 
     */
    public $name;
    
    /**
     * 
     * The type of the relationship as defined by the original (native) model;
     * e.g., 'has_one', 'belongs_to', 'has_many'.
     * 
     * @var string
     * 
     */
    public $type;
    
    /**
     * 
     * The class of the native model.
     * 
     * @var string
     * 
     */
    public $native_class;
    
    /**
     * 
     * The alias for the native table.
     * 
     * @var string
     * 
     */
    public $native_alias;
    
    /**
     * 
     * The native column to match against the foreign primary column.
     * 
     * @var string
     * 
     */
    public $native_col;
    
    /**
     * 
     * The class name of the foreign model. Default is the first
     * matching class for the relationship name, as loaded from the parent
     * class stack.
     * 
     * 
     * @var string
     * 
     */
    public $foreign_class;
    
    /**
     * 
     * The name of the table for the foreign model. Default is the
     * table specified by the foreign model.
     * 
     * @var string
     * 
     */
    public $foreign_table;
    
    /**
     * 
     * Aliases the foreign table to this name. Default is the
     * relationship name.
     * 
     * @var string
     * 
     */
    public $foreign_alias;
    
    /**
     * 
     * The name of the column to join with in the *foreign* table.
     * This forms one-half of the relationship.  Default is per association
     * type.
     * 
     * @var string
     * 
     */
    public $foreign_col;
    
    /**
     * 
     * The name of the foreign primary column.
     * 
     * @var string
     * 
     */
    public $foreign_primary_col;
    
    /**
     * 
     * Fetch these columns for the related records.
     * 
     * @var string|array
     * 
     */
    public $cols;
    
    /**
     * 
     * Additional conditions when fetching related records.
     * 
     * @var string|array
     * 
     */
    public $conditions;
    
    /**
     * 
     * Additional ORDER clauses when fetching related records.
     * 
     * @var string|array
     * 
     */
    public $order;
    
    /**
     * 
     * The virtual element called `foreign_key` automatically
     * populates the `native_col` or `foreign_col` value for you, based on the
     * association type.  This will be used **only** when `native_col` **and**
     * `foreign_col` are not set.
     * 
     * @var string
     * 
     */
    public $foreign_key;
    
    /**
     * 
     * The virtual element called `foreign_name` automatically sets the
     * `foreign_class` by looking up the foreign_name in the model catalog.
     * The virtual element is used  only when foreign_class is not set.
     * 
     * @var string
     * 
     */
    public $foreign_name;
    
    /**
     * 
     * What strategy should be used for connecting to native records
     * when eager-fetching: 'wherein', meaning a "WHERE IN (...)" a list of
     * native IDs, or 'select', meaning a join against a sub-SELECT.
     * 
     * @var string
     * 
     */
    public $native_by;
    
    /**
     * 
     * When picking a native-by strategy, use 'wherein' for up to this many
     * record in the native result; after this point, use a 'select' strategy.
     * 
     * @var string
     * 
     */
    public $wherein_max;
    
    /**
     * 
     * An instance of the native (origin) model that defined this relationship.
     * 
     * @var Solar_Sql_Model
     * 
     */
    protected $_native_model;
    
    /**
     * 
     * An instance of the foreign (related) model.
     * 
     * @var Solar_Sql_Model
     * 
     */
    protected $_foreign_model;
    
    /**
     * 
     * The registered Solar_Inflect object.
     * 
     * @var Solar_Inflect
     * 
     */
    protected $_inflect;
    
    /**
     * 
     * Post-construction tasks to complete object construction.
     * 
     * @return void
     * 
     */
    protected function _postConstruct()
    {
        parent::_postConstruct();
        $this->_inflect = Solar_Registry::get('inflect');
    }
    
    /**
     * 
     * Sets the native (origin) model instance.
     * 
     * @param Solar_Sql_Model $model The native model instance.
     * 
     * @return void
     * 
     */
    public function setNativeModel($model)
    {
        $this->_native_model = $model;
        $this->native_class = $this->_native_model->class;
        $this->native_alias = $this->_native_model->model_name;
    }
    
    /**
     * 
     * Returns the related (foreign) model instance.
     * 
     * @return Solar_Sql_Model
     * 
     */
    public function getModel()
    {
        return $this->_foreign_model;
    }
    
    /**
     * 
     * Returns the relation characteristics as an array.
     * 
     * @return array
     * 
     */
    public function toArray()
    {
        $vars = get_object_vars($this);
        foreach ($vars as $key => $val) {
            if ($key[0] == '_') {
                unset($vars[$key]);
            }
        }
        return $vars;
    }
    
    /**
     * 
     * Loads this relationship object with user-defined characteristics
     * (options), and corrects them as needed.
     * 
     * @param array $opts The user-defined options for the relationship.
     * 
     * @return void
     * 
     */
    public function load($opts)
    {
        $this->name = $opts['name'];
        $this->_setType();
        $this->_setForeignClass($opts);
        $this->_setForeignModel($opts);
        $this->_setCols($opts);
        $this->_setConditions($opts);
        $this->_setOrder($opts);
        $this->_setMerge($opts);
        $this->_setNativeBy($opts);
        $this->_setWhereinMax($opts);
        
        // if the user has specified *neither* a foreign_col *nor* a native_col,
        // but *has* specified a foreign_key, use the foreign_key to define 
        // the foreign_col or native col (depending on relation type). 
        if (empty($opts['native_col']) && empty($opts['foreign_col'])) {
            
            // if a "virtual" foreign_key value is not set, define one
            if (empty($opts['foreign_key'])) {
                $this->_fixForeignKey($opts);
            }
            
            // retain the foreign key
            $this->foreign_key = $opts['foreign_key'];
            
            // now set the related column based on the foreign_key value
            $this->_fixRelatedCol($opts);
        }
        
        $this->_setRelated($opts);
    }
    
    /**
     * 
     * Convenience method for getting a dump the whole object, or one of its
     * properties, or an external variable.
     * 
     * @param mixed $var If null, dump $this; if a string, dump $this->$var;
     * otherwise, dump $var.
     * 
     * @param string $label Label the dump output with this string.
     * 
     * @return void
     * 
     */
    public function dump($var = null, $label = null)
    {
        if ($var) {
            return parent::dump($var, $label);
        }
        
        $clone = clone($this);
        unset($clone->_config);
        unset($clone->_native_model);
        unset($clone->_foreign_model);
        unset($clone->_inflect);
        return parent::dump($clone, $label);
    }
    
    /**
     * 
     * Is this related to one record?
     * 
     * @return bool
     * 
     */
    abstract public function isOne();
    
    /**
     * 
     * Is this related to many records?
     * 
     * @return bool
     * 
     */
    abstract public function isMany();
    
    /**
     * 
     * Packages foreign data as a record or collection object.
     * 
     * @param array $data The foreign Data
     * 
     * @return Solar_Sql_Model_Record|Solar_Sql_Model_Collection A record or 
     * collection object.
     * 
     */
    abstract public function newObject($data);
    
    /**
     * 
     * Returns an empty related value for an internal array result.
     * 
     * @return null
     * 
     */
    abstract protected function _getEmpty();
    
    /**
     * 
     * Fetches a new record or collection object.
     * 
     * @param array $data The data for the record or collection.
     * 
     * @return Solar_Sql_Model_Record|Solar_Sql_Model_Collection A record or 
     * collection object.
     * 
     */
    abstract public function fetchNew($data = null);
    
    /**
     * 
     * Returns a new empty value appropriate for a lazy- or eager-fetch;
     * this is different for each kind of related.
     * 
     * @return mixed
     * 
     */
    abstract public function fetchEmpty();
    
    /**
     * 
     * Sets the base name for the foreign class.
     * 
     * @param array $opts The user-defined relationship options.
     * 
     * @return void
     * 
     */
    abstract protected function _setForeignClass($opts);
    
    /**
     * 
     * Corrects the foreign_key value in the options.
     * 
     * @param array &$opts The user-defined relationship options.
     * 
     * @return void
     * 
     */
    abstract protected function _fixForeignKey(&$opts);
    
    /**
     * 
     * Sets the foreign model instance based on user-defined relationship
     * options.
     * 
     * @param array $opts The user-defined relationship options.
     * 
     * @return void
     * 
     */
    protected function _setForeignModel($opts)
    {
        // get the foreign model from the catalog by its class name
        $catalog = $this->_native_model->catalog;
        $this->_foreign_model = $catalog->getModelByClass($this->foreign_class);
        
        // get its table name
        $this->foreign_table = $this->_foreign_model->table_name;
        
        // and its primary column
        $this->foreign_primary_col = $this->_foreign_model->primary_col;
        
        // set the foreign alias based on the relationship name
        $this->foreign_alias = $opts['name'];
    }
    
    /**
     * 
     * Sets the foreign columns to be selected based on user-defined 
     * relationship options.
     * 
     * @param array $opts The user-defined relationship options.
     * 
     * @return void
     * 
     */
    protected function _setCols($opts)
    {
        // the list of foreign table cols to retrieve
        if (empty($opts['cols'])) {
            $this->cols = $this->_foreign_model->fetch_cols;
        } elseif (is_string($opts['cols'])) {
            $this->cols = explode(',', $opts['cols']);
        } else {
            $this->cols = (array) $opts['cols'];
        }
        
        // make sure we always retrieve the foreign primary key value,
        // if there is one.
        $primary = $this->_foreign_model->primary_col;
        if ($primary && ! in_array($primary, $this->cols)) {
            $this->cols[] = $primary;
        }
        
        // if inheritance is turned on for the foreign model,
        // make sure we always retrieve the foreign inheritance value.
        $inherit = $this->_foreign_model->inherit_col;
        if ($inherit && ! in_array($inherit, $this->cols)) {
            $this->cols[] = $inherit;
        }
        
    }
    
    /**
     * 
     * Sets additional conditions from the relationship definition; these are
     * used in the WHERE and/or JOIN ON conditions.
     * 
     * @param array $opts The user-defined relationship options.
     * 
     * @return void
     * 
     */
    protected function _setConditions($opts)
    {
        if (empty($opts['conditions'])) {
            $this->conditions = array();
        } else {
            $this->conditions = (array) $opts['conditions'];
        }
    }
    
    /**
     * 
     * Sets default ORDER clause from the relationship definition.
     * 
     * @param array $opts The user-defined relationship options.
     * 
     * @return void
     * 
     */
    protected function _setOrder($opts)
    {
        if (empty($opts['order'])) {
            $this->order = array();
        } else {
            $this->order = (array) $opts['order'];
        }
    }
    
    /**
     * 
     * Sets the relationship type.
     * 
     * @return void
     * 
     */
    abstract protected function _setType();
    
    /**
     * 
     * Fixes the related column names in the user-defined options **in place**.
     * 
     * @param array $opts The user-defined relationship options.
     * 
     * @return void
     * 
     */
    abstract protected function _fixRelatedCol(&$opts);
    
    /**
     * 
     * Sets the characteristics for the related model, table, etc. based on
     * the user-defined relationship options.
     * 
     * @param array $opts The user-defined options for the relationship.
     * 
     * @return void
     * 
     */
    abstract protected function _setRelated($opts);
    
    /**
     * 
     * Sets the merge type.
     * 
     * @param array $opts The user-defined options for the relationship.
     * 
     * @return void
     * 
     */
    abstract protected function _setMerge($opts);
    
    /**
     * 
     * Fixes the native fetch params and eager params; then, if the join_flag
     * is set on the eager, calles _modEagerFetch() to modify the native fetch
     * params based on the eager params.
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @param Solar_Sql_Model_Params_Fetch $fetch The native fetch settings.
     * 
     * @return void
     * 
     * @see _modEagerFetch()
     * 
     */
    public function modEagerFetch($eager, $fetch)
    {
        $this->_fixFetchParams($fetch);
        $this->_fixEagerParams($eager);
        if ($eager['join_flag']) {
            $this->_modEagerFetch($eager, $fetch);
        }
    }
    
    /**
     * 
     * Fixes the native fetch params based on the settings for this related.
     * 
     * @param Solar_Sql_Model_Params_Fetch $fetch The native fetch settings.
     * 
     * @return void
     * 
     */
    protected function _fixFetchParams($fetch)
    {
        if (! $fetch['alias']) {
            $fetch->alias($this->native_alias);
        }
    }
    
    /**
     * 
     * Fixes the eager params based on the settings for this related.
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @return void
     * 
     */
    protected function _fixEagerParams($eager)
    {
        // always need an alias
        if (! $eager['alias']) {
            $eager->alias($this->foreign_alias);
        }
        
        // always need a merge type
        if (! $eager['merge']) {
            $eager->merge($this->merge);
        }
        
        // if a condition is present, and no join type is specified, make it
        // an inner join. this is to mimic WHERE behavior.
        if ($eager['conditions'] && ! $eager['join_type']) {
            $eager->joinType('inner');
        }
        
        // always need a join type
        if (! $eager['join_type']) {
            $eager->joinType('left');
        }
        
        // for inner joins, always join to the main fetch
        if ($eager['join_type'] == 'inner') {
            $eager->joinFlag(true);
        }
        
        // which columns?
        if ($eager['join_only']) {
            // don't fetch cols when only joining ...
            $eager['cols'] = false;
            // ... and force the join
            $eager->joinFlag(true);
        } elseif ($eager['cols'] === array() || $eager['cols'] === null) {
            // empty array or null means "use default cols"
            $eager->cols($this->cols);
        }
        
        // native-by strategy (wherein or select)
        if (! $eager['native_by']) {
            $eager['native_by'] = $this->native_by;
        }
        
        // when to switch from array to select
        if ($eager['wherein_max'] === null) {
            $eager['wherein_max'] = $this->wherein_max;
        }
    }
    
    /**
     * 
     * Modifies the native fetch with an eager join so that the foreign table
     * is joined properly.
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @param Solar_Sql_Model_Params_Fetch $fetch The native fetch settings.
     * 
     * @return void
     * 
     * @see modEagerFetch()
     * 
     */
    abstract protected function _modEagerFetch($eager, $fetch);
    
    /**
     * 
     * Gets the foreign-model WHERE conditions and merges with the
     * WHERE conditions on this relationship.
     * 
     * @param string $alias The alias to use for the foreign table.
     * 
     * @return array An array of WHERE conditions.
     * 
     */
    public function getForeignConditions($alias)
    {
        $where = array_merge(
            $this->conditions,
            $this->_foreign_model->getConditions($alias)
        );
        return $where;
    }
    
    /**
     * 
     * Modifies the parent result array to add eager records.
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @param array &$result The parent result rows.
     * 
     * @param string $type The type of fetch performed (e.g., 'one', 'all', etc).
     * 
     * @param Solar_Sql_Model_Params_Fetch $fetch The native fetch settings.
     * 
     * @return void
     * 
     */
    abstract public function modEagerResult($eager, &$result, $type, $fetch);
    
    /**
     * 
     * Fetches the related record or collection for a native ID or record.
     * 
     * @param mixed $spec If a scalar, treated as the native primary key
     * value; if an array or record, retrieves the native primary key value
     * from it.
     * 
     * @return object The related record or collection object.
     * 
     */
    public function fetch($spec)
    {
        if ($spec instanceof Solar_Sql_Model_Record || is_array($spec)) {
            $native_id = $spec[$this->native_col];
        } else {
            $native_id = $spec;
        }
        
        $where = array();
        $cond  = "{$this->foreign_alias}.{$this->foreign_col} = ?";
        $where[$cond] = $native_id;
        
        $where = array_merge(
            $where,
            $this->getForeignConditions($this->foreign_alias)
        );
        
        $fetch = array(
            'alias' => $this->foreign_alias,
            'where' => $where,
            'order' => $this->order,
        );
        
        if ($this->isOne()) {
            $obj = $this->_foreign_model->fetchOne($fetch);
        } elseif ($this->isMany()) {
            $obj = $this->_foreign_model->fetchAll($fetch);
        } else {
            throw $this->_exception('ERR_NOT_ONE_OR_ALL', array(
                'class' => get_class($this),
            ));
        }
        
        if (! $obj) {
            $obj = $this->fetchEmpty();
        }
        
        return $obj;
    }
    
    /**
     * 
     * Given a results array, collates the results based on a key within each
     * result.
     * 
     * @param array $array The results array.
     * 
     * @param string $key The key to collate by.
     * 
     * @return array The collated array.
     * 
     */
    abstract protected function _collate($array, $key);
    
    /**
     * 
     * Sets the native-by strategy ('wherein' or 'select').
     * 
     * @param array $opts The user-defined options for the relationship.
     * 
     * @return void
     * 
     */
    protected function _setNativeBy($opts)
    {
        // default to array
        if (empty($opts['native_by'])) {
            $this->native_by = 'wherein';
            return;
        }
        
        // check for 'wherein' or 'select'
        $opts['native_by'] = strtolower(trim($opts['native_by']));
        if ($opts['native_by'] == 'wherein' || $opts['native_by'] == 'select') {
            $this->wherein = $opts['native_by'];
        } else {
            throw $this->_exception('ERR_UNKNOWN_NATIVE_BY', array(
                'name' => $this->name,
                'native' => get_class($this->_native_model),
                'native_by' => $opts['native_by'],
                'known' => '"wherein" or "select"',
            ));
        }
    }
    
    /**
     * 
     * Sets the 'wherein_max' value (i.e., the number of records in the native
     * collection after which we should use a 'native-by select' strategy).
     * 
     * @param array $opts The user-defined options for the relationship.
     * 
     * @return void
     * 
     */
    protected function _setWhereinMax($opts)
    {
        if (empty($opts['wherein_max'])) {
            $this->wherein_max = $this->_config['wherein_max'];
        } else {
            $this->wherein_max = (int) $opts['wherein_max'];
        }
    }
    
    /**
     * 
     * Fetches eager results into an existing single native array row.
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @param array &$array The existing native result row.
     * 
     * @return void
     * 
     */
    protected function _fetchIntoArrayOne($eager, &$array)
    {
        $where = array();
        
        $col = "{$eager['alias']}.{$this->foreign_col}";
        $where["$col = ?"] = $array[$this->native_col];
        
        $where = array_merge(
            $where,
            $this->getForeignConditions($eager['alias'])
        );
        
        $params = array(
            'alias' => $eager['alias'],
            'cols'  => $eager['cols'],
            'where' => $where,
            'order' => $this->order,
            'eager' => $eager['eager'],
        );
        
        if ($this->isOne()) {
            $data = $this->_foreign_model->fetchOneAsArray($params);
        } elseif ($this->isMany()) {
            $data = $this->_foreign_model->fetchAllAsArray($params);
        }
        
        $array[$this->name] = $data;
    }
    
    /**
     * 
     * Fetches eager results into an existing native array rowset.
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @param array &$array The existing native result row.
     * 
     * @param Solar_Sql_Model_Params_Fetch $fetch The native fetch settings.
     * 
     * @return void
     * 
     */
    protected function _fetchIntoArrayAll($eager, &$array, $fetch)
    {
        $col = "{$eager['alias']}.{$this->foreign_col}";
        
        $use_select = $eager['native_by'] == 'select'
                   || count($array) > $eager['wherein_max'];
        
        $join = null;
        $where = null;
        if ($use_select) {
            $join = $this->_getNativeBySelect($eager, $fetch, $col);
            $join['cond'] = array_merge(
                (array) $join['cond'],
                $this->getForeignConditions($eager['alias'])
            );
        } else {
            $where = array_merge(
                $this->_getNativeByWherein($eager, $array, $col),
                $this->getForeignConditions($eager['alias'])
            );
        }
        
        $params = array(
            'alias' => $eager['alias'],
            'cols'  => $eager['cols'],
            'join'  => $join,
            'where' => $where,
            'order' => $this->order,
            'eager' => $eager['eager'],
        );
        
        $data = $this->_foreign_model->fetchAllAsArray($params);
        $data = $this->_collate($data, $this->foreign_col);
        
        // now we have all the foreign rows for all-of-all of the native rows.
        // next is to tie each of those foreign sets to the appropriate
        // native result rows.
        foreach ($array as &$row) {
            $key = $row[$this->native_col];
            if (! empty($data[$key])) {
                $row[$this->name] = $data[$key];
            } else {
                $row[$this->name] = $this->_getEmpty();
            }
        }
    }
    
    /**
     * 
     * Returns an INNER JOIN specification for joining to the native table as
     * as sub-SELECT.
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @param Solar_Sql_Model_Params_Fetch $fetch The native fetch settings.
     * 
     * @param string $col The foreign column to join against.
     * 
     * @return array A join specification array.
     * 
     */
    protected function _getNativeBySelect($eager, $fetch, $col)
    {
        // get a *copy* of the fetch params; don't want to mess them up
        // for other eagers. only use the joins marked "keep".
        $clone = $fetch->cloneForKeeps();
        
        // reset the column list and get only the native column
        $clone['cols'] = array();
        $clone->cols($this->native_col);
        
        // for all sub-eagers, if they are joining to the top-level fetch,
        // make sure they are join_only ... otherwise, they'll add columns,
        // which will mess up our cols() from earlier.
        foreach ($clone['eager'] as $sub_eager) {
            if ($sub_eager['join_flag']) {
                $sub_eager['join_only'] = true;
            }
        }
        
        // don't waste time ordering the results
        $clone['order'] = false;
        
        // build a select and get it as a string
        $select = $this->_native_model->newSelect($clone);
        $string = $select->__toString();
        
        $join = array(
            'type' => "inner",
            'name' => "($string) AS {$fetch['alias']}",
            'cond' => "{$fetch['alias']}.{$this->native_col} = $col",
            'cols' => null,
        );
        
        // done!
        return $join;
    }
    
    /**
     * 
     * Returns an array of WHERE conditions for selecting only certain
     * native values from the foreign table using "WHERE IN (...)".
     * 
     * @param Solar_Sql_Model_Params_Eager $eager The eager params.
     * 
     * @param array $array The array of native results.
     * 
     * @param string $col The foreign column to use for the "WHERE IN (...)".
     * 
     * @return array An array of WHERE conditions.
     * 
     */
    protected function _getNativeByWherein($eager, $array, $col)
    {
        // get the list of IDs in the array
        $list = array();
        foreach ($array as $row) {
            $key = $row[$this->native_col];
            $list[$key] = true;
        }
        $list = array_keys($list);
        
        // tack it on to the end of the baseline relationship where clauses
        $where = array("$col IN (?)" => $list);
        
        // done!
        return $where;
    }
    
    /**
     * 
     * Pre-save hook for saving related records or collections from a native
     * record.
     * 
     * @param Solar_Sql_Model_Record $native The native record to save from.
     * 
     * @return void
     * 
     */
    public function preSave($native)
    {
        // at least for now, only belongs-to needs this
    }
    
    /**
     * 
     * Saves a related record or collection from a native record.
     * 
     * @param Solar_Sql_Model_Record $native The native record to save from.
     * 
     * @return void
     * 
     */
    abstract public function save($native);
    
    /**
     * 
     * Is the related record or collection valid?
     * 
     * @param Solar_Sql_Model_Record $native The native record to check from.
     * 
     * @return bool
     * 
     */
    public function isInvalid($native)
    {
        $foreign = $native->{$this->name};
        if ($foreign) {
            return $foreign->isInvalid();
        } else {
            // if it's not there, it can't be invalid
            return false;
        }
    }
}
Return current item: SolarPHP