Location: PHPKode > projects > Maintainable PHP Framework > vendor/Mad/Model/Association/Base.php
<?php
/**
 * @category   Mad
 * @package    Mad_Model
 * @subpackage Association
 * @copyright  (c) 2007-2009 Maintainable Software, LLC
 * @license    http://opensource.org/licenses/bsd-license.php BSD
 */

/**
 * An association between model objects
 * 
 * belongsTo: (Article belongsTo User) 
 *   $id = articles.id 
 *   SELECT * FROM users WHERE id = $id
 * 
 *   foreignKey => articles.user_id    $assocModel->tableName() . '_id'
 *   primaryKey => users.id            $assocModel->primaryKeyName()
 * 
 * 
 * hasOne: (User hasOne Avatar) 
 *   $id = users.users_id
 *   SELECT * FROM avatars WHERE users_id = $id
 * 
 *   foreignKey => avatars.user_id     $model->tableName() + _id
 *   primaryKey => users.id            $model->primaryKeyName()
 * 
 * 
 * hasMany: (User hasMany Articles) 
 *   $id = users.id
 *   SELECT * FROM articles WHERE user_id = $id
 * 
 *   foreignKey => articles.user_id    $model->tableName() . '_id'
 *   primaryKey => users.id            $model->primaryKeyName()
 * 
 * 
 * hasMany (through): (Article hasMany Tags 'through' => Taggings)
 *   $id = articles.id
 *   SELECT * 
 *     FROM taggings ts, tags t
 *    WHERE ts.article_id = $id
 *      AND ts.tag_id = t.id
 * 
 *   foreignKey  => article_id   $model->tableName() . '_id'
 *   primaryKey  => articles.id  $model->primaryKeyName()
 *   joinTable   => new $options['through']->tableName()
 * 
 *
 * hasAndBelongsToMany: (Category hasAndBelongsToMany Articles) 
 *   $id = categories.id
 *   SELECT * 
 *     FROM articles_categories ac, articles a
 *    WHERE ac.category_id = $id
 *      AND ac.article_id = a.id
 * 
 *   foreignKey      => category_id    $model->tableName() . '_id'
 *   primaryKey      => categories.id  $model->primaryKeyName()
 *   assocForeignKey => article_id     $assocModel->tableName() . '_id'
 *   assocPrimaryKey => articles.id    $assocModel->primaryKeyName()
 * 
 * 
 * @category   Mad
 * @package    Mad_Model
 * @subpackage Association
 * @copyright  (c) 2007-2009 Maintainable Software, LLC
 * @license    http://opensource.org/licenses/bsd-license.php BSD
 */
abstract class Mad_Model_Association_Base
{
    /**
     * The name given to the association when declared
     * @var string
     */
    protected $_assocName = null;

    /**
     * The method used to create the association.
     * eg. (hasOne|belongsTo|hasMany|hasAndBelongsToMany)
     * Lazy loaded
     *
     * @var string
     */
    protected $_macro;

    /**
     * The method used to create the association.
     * Lazy loaded
     *
     * @var string
     */
    protected $_assocModel;

    /**
     * The name of the table for the associated object
     * Lazy loaded
     *
     * @var string
     */
    protected $_assocTable;

    /**
     * The name of the class for the associated object
     * Lazy loaded
     *
     * @var string
     */
    protected $_assocClass;

    /**
     * The model's primary key needed to link the associated object
     * Lazy loaded
     *
     * @var string
     */
    protected $_pkName;

    /**
     * The model's foreign key needed to link the associated object
     * Lazy loaded
     *
     * @var string
     */
    protected $_fkName;

    /**
     * The primary key needed to link to the associated object through a join table
     * Lazy loaded
     *
     * @var string
     */
    protected $_assocPkName;

    /**
     * The foreign key needed to link to the associated object through a join table
     * Lazy loaded
     *
     * @var string
     */
    protected $_assocFkName;


    /**
     * The Mad_Model_Base object this association is for
     * @param   object
     */
    protected $_model = null;

    /**
     * The list of association options
     */
    protected $_options = array();

    /**
     * The list of dynamic methods added to the model through this association
     * @var array
     */
    protected $_methods = array();

    /**
     * The list of data that has already been loaded
     * @var array
     */
    protected $_loaded = array();

    /**
     * Has the association changed in any way
     * @var boolean
     */
    protected $_changed = false;

    /**
     * Instance of the db connection
     * @var object
     */
    protected $_conn = null;


    /*##########################################################################
    # Construct/Destruct
    ##########################################################################*/

    /**
     * Mad_Model_Association_Base classes are done through a simple factory pattern.
     * @see Mad_Model_Association_Base::factory
     * 
     * @param   string  $assocName
     * @param   array   $options
     */
    abstract protected function __construct($assocName, $options, $model);

    /**
     * Stringified version
     * @return  string
     */
    public function __toString()
    {
        return $this->getAssocName().' ['.
               get_class($this->getModel()).' '.
               $this->getMacro().' '.
               $this->getAssocClass().']';
    }

    /**
     * Construct association object - simple factory
     * @param   string  $macro (belongsTo|hasOne|hasMany|hasAndBelongsToMany)
     * @param   string  $assocName
     * @param   array   $options
     * @param   object  $model
     */
    public static function factory($macro, $assocName, $options, Mad_Model_Base $model)
    {
        // through association
        $through = isset($options['through']) ? 'Through' : '';
        $className = 'Mad_Model_Association_'.Mad_Support_Inflector::classify($macro).$through;

        return new $className($assocName, $options, $model);
    }


    /*##########################################################################
    # Accessors
    ##########################################################################*/

    /**
     * Get the method used to declare the of this association
     *
     * Example:
     * <code>
     *  $this->hasMany('Documents');
     *  // returns 'hasMany'
     * </code>
     *
     * @return  string
     */
    public function getMacro()
    {
        if (!isset($this->_macro)) {
            $this->_macro = Mad_Support_Inflector::camelize(
                str_replace('Mad_Model_Association_', '', get_class($this)), 'lower'
            );
        }
        return $this->_macro;
    }

    /**
     * Get all options for this association
     *
     * Example:
     * <code>
     *  $this->hasMany('Documents', array('foreignKey' => 'documentid'));
     *  // returns array('foreignKey' => 'documentid')
     * </code>
     *
     * @return  array
     */
    public function getOptions()
    {
        return $this->_options;
    }

    /**
     * Get the name given when the association is created.
     *
     * Example:
     * <code>
     *  $this->hasMany('Documents');
     *  // returns 'Documents'
     *
     *  $this->hasMany('ChildDocuments', array('className' => 'Document'));
     *  // returns 'ChildDocuments'
     * </code>
     *
     * @return  string
     */
    public function getAssocName()
    {
        return $this->_assocName;
    }

    /**
     * The class name is determined based on the class in which the
     *
     * Example:
     * in models/Folder.php
     * <code>
     *  $this->hasMany('Documents');
     *  // or
     *  $this->hasMany('ChildDocuments', array('className' => 'Document'));
     *  // returns 'Folder'
     * </code>
     *
     * @return  string
     */
    public function getClass()
    {
        return get_class($this->_model);
    }

    /**
     * Get the model in which the association was defined
     *
     * Example:
     * in models/Folder.php
     * <code>
     *  $this->hasMany('Documents');
     *  // returns a Folder object
     * </code>
     *
     * @return  Mad_Model_Base
     */
    public function getModel()
    {
        return $this->_model;
    }

    /**
     * Get the table for the object
     *
     * Example:
     * in models/Folder.php
     * <code>
     *  $this->hasMany('Documents');
     *  // or
     *  $this->hasMany('ChildDocuments', array('className' => 'Document'));
     *  // returns 'folders'
     * </code>
     *
     * @return  string
     */
    public function tableName()
    {
        return $this->_model->tableName();
    }

    /**
     * Get the value for the primaryKey
     *
     * @return  string
     */
    public function getPkValue()
    {
        // belongsTo association is reversed so we use the foreignKey
        if ($this->getMacro() == 'belongsTo') {
            $attribute = $this->getFkName();
        } else {
            $attribute = $this->getPkName();
        }
        return $this->getModel()->readAttribute($attribute);
    }

    /**
     * The class name is determined based on the string passed into the association
     *  declaration unless specified in the options
     *
     * Example:
     * <code>
     *  $this->hasMany('Documents');
     *  // or
     *  $this->hasMany('ChildDocuments', array('className' => 'Document'));
     *  // returns 'Document'
     * </code>
     *
     * @return  string
     */
    public function getAssocClass()
    {
        if (!isset($this->_assocClass)) {
            $className = $this->_options['className'];

            // name of the association and model
            if (empty($className)) {
                $plural = ($this->getMacro() == 'hasMany' || 
                           $this->getMacro() == 'hasManyThrough' || 
                           $this->getMacro() == 'hasAndBelongsToMany');
                $className = $plural ? Mad_Support_Inflector::singularize($this->_assocName) : $this->_assocName;
            }
            $this->_assocClass = $className;

            if (!class_exists($this->_assocClass)) {
                $msg = "Invalid association declaration. The class $this->_assocClass doesn't exist";
                throw new Mad_Model_Association_Exception($msg);
            }
        }
        return $this->_assocClass;
    }

    /**
     * Get the model used for the association
     *
     * Example:
     * <code>
     *  $this->hasMany('Documents');
     *  // returns a Document object
     * </code>
     *
     * @return  Mad_Model_Base
     */
    public function getAssocModel()
    {
        if (!isset($this->_assocModel)) {
            $class = $this->getAssocClass();
            $this->_assocModel = new $class;
        }
        return $this->_assocModel;
    }


    /**
     * Get the table for the associated object
     *
     * Example:
     * <code>
     * class Folder
     * {
     *   ...
     *   $this->hasMany('Documents');
     *   // or
     *   $this->hasMany('ChildDocuments', array('className' => 'Document'));
     *   // returns 'documents'
     *   ...
     * }
     * </code>
     *
     * @return  string
     */
    public function getAssocTable()
    {
        if (!isset($this->_assocTable)) {
            $this->_assocTable = $this->getAssocModel()->tableName();
        }
        return $this->_assocTable;
    }

    /**
     * The primary key for the model table. This is determined
     * automatically by the data object if not given in options.
     * 
     * Example:
     * in Folder.php
     * <code>
     *  $this->hasMany('Documents');
     *  // returns 'folderid'
     *
     *  $this->hasMany('ChildDocuments', array('primaryKey' => 'parent_folderid'));
     *  // returns 'parent_folderid'
     * </code>
     *
     * @return  string
     */
    public function getPkName()
    {
        if (!isset($this->_pkName)) {
            $macro = $this->getMacro();

            // key was given in options
            if (!empty($this->_options['primaryKey'])) {
                $this->_pkName = $this->_options['primaryKey'];

            } elseif ($macro == 'belongsTo') {
                $this->_pkName = $this->getAssocModel()->primaryKey();

            } else {
                $this->_pkName = $this->getModel()->primaryKey();
            }
        }
        return $this->_pkName;
    }

    /**
     * The foreign key for the model table. This is determined
     * automatically by the data object if not given in options.
     *
     * Example:
     * in Folder.php
     * <code>
     *  $this->hasMany('Documents');
     *  // returns 'folderid'
     *
     *  $this->hasMany('ChildDocuments', array('foreignKey' => 'parent_folderid'));
     *  // returns 'parent_folderid'
     * </code>
     *
     * @return  string
     */
    public function getFkName()
    {
        if (!isset($this->_fkName)) {
            $macro = $this->getMacro();

            // key was given in options
            if (!empty($this->_options['foreignKey'])) {
                $this->_fkName = $this->_options['foreignKey'];

            } elseif ($macro == 'belongsTo') {
                $associationName = Mad_Support_Inflector::underscore($this->getAssocName());
                $this->_fkName = "{$associationName}_id";
            } else {
                $table = $this->getModel()->tableName();
                $singularTable = Mad_Support_Inflector::singularize($table);
                $this->_fkName = "{$singularTable}_id";
            }
        }
        return $this->_fkName;
    }

    /**
     * The primary key for the associated model table. This is determined
     * automatically by the data object if not given in options.
     *
     * Example:
     * <code>
     *  $this->hasMany('Documents');
     *  // returns 'documentid'
     *
     *  $this->hasMany('ChildDocuments', array('foreignKey' => 'parent_documentid'));
     *  // returns 'parent_documentid'
     * </code>
     *
     * @return  string
     */
    public function getAssocPkName()
    {
        if (!isset($this->_assocPkName)) {
            // key was given in options
            if (!empty($this->_options['associationPrimaryKey'])) {
                $this->_assocPkName = $this->_options['associationPrimaryKey'];
            } else {
                $this->_assocPkName = $this->getAssocModel()->primaryKey();
            }
        }
        return $this->_assocPkName;
    }

    /**
     * The foreign key name for the join table in an association. This is determined
     * automatically by the data object if not given in options.
     *
     * Example:
     * <code>
     *  $this->hasAndBelongsToMany('Documents', array('associationForeignKey' => 'documentid'));
     *  // returns 'documentid'
     * </code>
     *
     * @return  string
     */
    public function getAssocFkName()
    {
        if (!isset($this->_assocFkName)) {
            // key was given in options
            if (!empty($this->_options['associationForeignKey'])) {
                $this->_assocFkName = $this->_options['associationForeignKey'];
            } else {
                $table = $this->getAssocModel()->tableName();
                $singularTable = Mad_Support_Inflector::singularize($table);
                $this->_assocFkName = "{$singularTable}_id";
            }
        }
        return $this->_assocFkName;
    }

    /**
     * Get the join table for hasAndBelongsToMany associations. Default to
     * tablename1_tablename2 by alpha (briefcases_documents)
     *
     * Example:
     * <code>
     * class Binder
     * ...
     *  $this->hasAndBelongsToMany('Documents');
     * ...
     *  // returns 'briefcases_documents' (by default)
     * </code>
     *
     * @return  string
     */
    public function getJoinTable()
    {
        if (!isset($this->_joinTable)) {
            $macro = $this->getMacro();

            // join table was given in options
            if (!empty($this->_options['joinTable'])) {
                $this->_joinTable = $this->_options['joinTable'];

            // join table from through association
            } elseif (!empty($this->_options['through'])) {
                $class = Mad_Support_Inflector::classify($this->_options['through']);
                $model = new $class;
                $this->_joinTable = $model->tableName();

            // determine table name by convention from DO data
            } elseif ($macro == 'hasAndBelongsToMany') {
                $tbls = array($this->_model->tableName(), $this->getAssocTable());
                sort($tbls);
                $this->_joinTable = implode('_', $tbls);

            // no join table
            } else {
                $this->_joinTable = null;
            }
        }
        return $this->_joinTable;
    }

    /**
     * List of dynamic methods that are added when the association is created. It
     * shows the dynamic method's mapping to the association method
     *
     * Example:
     * <code>
     *  $this->belongsTo('Folder');
     *
     *  // returns array('folder'       => 'getObject',
     *  //               'folder='      => 'setObject',
     *  //               'buildFolder'  => 'buildObject',
     *  //               'createFolder' => 'createObject');
     * </code>
     *
     * @return  array
     */
    public function getMethods()
    {
        return $this->_methods;
    }

    /**
     * An object is considered loaded once the assocation model has been
     * populated from hitting the database, or explicitly flagged as loaded
     *
     * @return  boolean
     */
    abstract public function isLoaded();

    /**
     * Set that this association's object data as loaded
     *
     * @param   boolean $loaded
     */
    abstract public function setLoaded($loaded=true);

    /**
     * Check if the associated object has changed
     *
     * @return  boolean
     */
    abstract public function isChanged();

    /**
     * Flag the association as being changed
     *
     * @param   boolean $changed
     */
    public function setChanged($changed=true)
    {
        $this->_changed = $changed;
    }

    /**
     * Destroy all objects that are dependent on the base object based on their
     * dependency options. This only applies to hasOne/hasMany/HABTM associations.
     */
    public function destroyDependent()
    {
        // this is re-implemented in subclasses that need to use it
        // no code should be here
    }

    /**
     * Save changes to association objects. This will only save the object changes if it
     * has been loaded up from the database and was changed.
     */
    abstract public function save();


    /*##########################################################################
    # Magic methods call this to method to get results
    ##########################################################################*/

    /**
     * Dynamically call an association method by name/args
     * @param   string  $name
     * @param   array   $args
     * @return  mixed
     */
    public function callMethod($name, $args)
    {
        // get the local name of the method we need to call & call it
        $methodName = $this->_methods[$name];
        return $this->$methodName($args);
    }
}
Return current item: Maintainable PHP Framework