Pluf Framework

Pluf Framework Git Source Tree


Root/src/Pluf/Model.php

<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of Plume Framework, a simple PHP Application Framework.
# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
#
# Plume Framework is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Plume Framework is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# ***** END LICENSE BLOCK ***** */


/**
 * Sort of Active Record Class
 *
 */
class Pluf_Model
{
    public $_model = __CLASS__; //set it to your model name

    /** Database connection. */
    public $_con = null;

    /** 
     * Store the attributes of the model. To minimize pollution of the
     * property space, all the attributes are stored in this array.
     *
     * Description of the keys:
     * 'table': The table in which the model is stored.
     * 'model': The name of the model.
     * 'cols': The definition of the columns.
     * 'idx': The definition of the indexes.
     * 'views': The definition of the views.
     * 'verbose': The verbose name of the model.
     */
    public $_a = array('table' => 'model',
                       'model' => 'Pluf_Model',
                       'cols' => array(),
                       'idx' => array(),
                       'views' => array(),
                       );

    /** Storage of the data.
     *
     * The object data are stored in an associative array. Each key
     * corresponds to a column and stores a Pluf_DB_Field_* variable.
     */
    protected $_data = array(); 

    /**
     * Storage cached data for methods_get
     */
    protected $_cache = array(); // We should use a global cache.
    
    /** List of the foreign keys.
     *
     * Set by the init() method from the definition of the columns.
     */
    protected $_fk = array();

    /** 
     * Methods available, this array is dynamically populated by init
     * method.
     */ 
    protected $_m = array('list' => array(), // get_*_list methods
                          'many' => array(), // many to many
                          'get' => array(), // foreign keys
                          'extra' => array(), // added by some fields
                          );

    function __construct($pk=null, $values=array())
    {
        $this->_init();
        if ((int) $pk > 0) {
            $this->get($pk); //Should not have a side effect
        }
    }


    function init()
    {
        // Define it yourself.
    }

    /**
     * Define the list of methods for the model from the available
     * model relationship.
     */
    function _init()
    {
        $this->_getConnection();
        if (isset($GLOBALS['_PX_models_init_cache'][$this->_model])) {
            $this->_cache = $GLOBALS['_PX_models_init_cache'][$this->_model]['cache'];
            $this->_m = $GLOBALS['_PX_models_init_cache'][$this->_model]['m'];
            $this->_a = $GLOBALS['_PX_models_init_cache'][$this->_model]['a'];
            $this->_fk = $GLOBALS['_PX_models_init_cache'][$this->_model]['fk'];
            $this->_data = $GLOBALS['_PX_models_init_cache'][$this->_model]['data'];
            return;
        }
        $this->init();
        foreach ($this->_a['cols'] as $col=>$val) {
            $field = new $val['type']('', $col);
            if ($field->type == 'foreignkey') {
                $this->_m['get']['get_'.strtolower($col)] = array($val['model'], $col);
                $this->_cache['fk'][$col] = 'foreignkey';
            }
            if ($field->type == 'manytomany') {
                $this->_m['list']['get_'.strtolower($col).'_list'] = $val['model'];
                $this->_m['many'][$val['model']] = 'manytomany';
            }
            foreach ($field->methods as $method) {
                $this->_m['extra'][$method[0]] = array(strtolower($col), $method[1]);
            }
            if (array_key_exists('default', $val)) {
                $this->_data[$col] = $val['default'];
            } else {
                $this->_data[$col] = '';
            }
        }
        foreach ($GLOBALS['_PX_models'] as $model=>$val) {
            if (isset($val['relate_to'])) {
                foreach ($val['relate_to'] as $related) {
                    if ($this->_a['model'] == $related) {
                        // The current model is related to $model
                        // through one or more foreign key. We load
                        // the $model to check on which fields the
                        // foreign keys are set, as it is possible in
                        // one model to have several foreign keys to
                        // the same other model.
                        if ($model != $this->_a['model']) {
                            $_m = new $model();
                            $_fkeys = $_m->getForeignKeysToModel($this->_a['model']);
                        } else {
                            $_fkeys = $this->getForeignKeysToModel($this->_a['model']);
                        }
                        foreach ($_fkeys as $_fkey=>$_fkeyval) {
                            //For each foreign key, we add the
                            //get_xx_list method that can have a
                            //custom name through the relate_name
                            //value.
                            if (isset($_fkeyval['relate_name'])) {
                                $mname = $_fkeyval['relate_name'];
                            } else {
                                $mname = strtolower($model);
                            }
                            $this->_m['list']['get_'.$mname.'_list'] = array($model, $_fkey);
                        }
                        break;
                    }
                }
            }
            if (isset($val['relate_to_many']) && 
                in_array($this->_a['model'], $val['relate_to_many'])) {
                $this->_m['list']['get_'.strtolower($model).'_list'] = $model;
                $this->_m['many'][$model] = 'manytomany';
            }
        }
        $GLOBALS['_PX_models_init_cache'][$this->_model] = array();
        $GLOBALS['_PX_models_init_cache'][$this->_model]['cache'] = $this->_cache;
        $GLOBALS['_PX_models_init_cache'][$this->_model]['m'] = $this->_m;
        $GLOBALS['_PX_models_init_cache'][$this->_model]['a'] = $this->_a;
        $GLOBALS['_PX_models_init_cache'][$this->_model]['fk'] = $this->_fk;
        $GLOBALS['_PX_models_init_cache'][$this->_model]['data'] = $this->_data;
    }

    /**
     * Get the foreign keys relating to a given model.
     *
     * @param string Model
     * @return array Foreign keys
     */
    function getForeignKeysToModel($model)
    {
        $keys = array();
        foreach ($this->_a['cols'] as $col=>$val) {
            $field = new $val['type']();
            if ($field->type == 'foreignkey' and $val['model'] == $model) {
                $keys[$col] = $val;
            }
        }
        return $keys;
    }

    /**
     * Get the raw data of the object.
     *
     * For the many to many relations, the value is an array of ids.
     *
     * @return array Associative array of the data.
     */
    function getData()
    {
        foreach ($this->_a['cols'] as $col=>$val) {
            $field = new $val['type']();
            if ($field->type == 'manytomany') {
                $this->_data[$col] = array();
                $method = 'get_'.strtolower($col).'_list';
                foreach ($this->$method() as $item) {
                    $this->_data[$col][] = $item->id;
                }
            }
        }
        return $this->_data;
    }

    /**
     * Set the association of a model to another in many to many.
     *
     * @param object Object to associate to the current object
     */
    function setAssoc($model)
    {
        if (!$this->delAssoc($model)) {
            return false;
        }
        $hay = array(strtolower($model->_a['model']), strtolower($this->_a['model']));
        sort($hay);
        $table = $hay[0].'_'.$hay[1].'_assoc';
        $req = 'INSERT INTO '.$this->_con->pfx.$table."\n";
        $req .= '('.$this->_con->qn(strtolower($this->_a['model']).'_id').', '
            .$this->_con->qn(strtolower($model->_a['model']).'_id').') VALUES '."\n";
        $req .= '('.$this->_toDb($this->_data['id'], 'id').', ';
        $req .= $this->_toDb($model->id, 'id').')';
        $this->_con->execute($req);
        return true;
    }

    /**
     * Set the association of a model to another in many to many.
     *
     * @param object Object to associate to the current object
     */
    function delAssoc($model)
    {

        //check if ok to make the association
        //current model has a many to many key with $model
        //$model has a many to many key with current model
        if (!isset($this->_m['many'][$model->_a['model']])
            or strlen($this->_data['id']) == 0
            or strlen($model->id) == 0) {
            return false;
        }
        $hay = array(strtolower($model->_a['model']), strtolower($this->_a['model']));
        sort($hay);
        $table = $hay[0].'_'.$hay[1].'_assoc';
        $req = 'DELETE FROM '.$this->_con->pfx.$table.' WHERE'."\n";
        $req .= $this->_con->qn(strtolower($this->_a['model']).'_id').' = '.$this->_toDb($this->_data['id'], 'id');
        $req .= ' AND '.$this->_con->qn(strtolower($model->_a['model']).'_id').' = '.$this->_toDb($model->id, 'id');
        $this->_con->execute($req);
        return true;
    }

    /**
     * Bulk association of models to the current one.
     *
     * @param string Model name
     * @param array Ids of Model name
     * @return bool Success
     */
    function batchAssoc($model_name, $ids)
    {
        $currents = $this->getRelated($model_name);
        foreach ($currents as $cur) {
            $this->delAssoc($cur);
        }
        foreach ($ids as $id) {
            $m = new $model_name($id);
            if ($m->id == $id) {
                $this->setAssoc($m);
            }
        }
        return true;
    }

    /**
     * Get a database connection.
     */
    function _getConnection()
    {
        static $con = null;
        if ($this->_con !== null) {
            return $this->_con;
        }
        if ($con !== null) {
            $this->_con = $con;
            return $this->_con;
        }
        $this->_con = &Pluf::db($this);
        $con = $this->_con;
        return $this->_con;
    }

    /**
     * Get a database connection.
     */
    function getDbConnection()
    {
        return $this->_getConnection();
    }

    /**
     * Get the table of the model.
     *
     * Avoid doing the concatenation of the prefix and the table
     * manually.
     */
    function getSqlTable()
    {
        return $this->_con->pfx.$this->_a['table'];
    }

    /**
     * Overloading of the get method.
     *
     * @param string Property to get
     */
    function __get($prop)
    {
        return (array_key_exists($prop, $this->_data)) ?
            $this->_data[$prop] : $this->__call($prop, array());
    }

    /**
     * Overloading of the set method.
     *
     * @param string Property to set
     * @param mixed Value to set
     */
    function __set($prop, $val)
    {
        if (null !== $val and isset($this->_cache['fk'][$prop])) {
            $this->_data[$prop] = $val->id;
            unset($this->_cache['get_'.$prop]);
        } else {
            $this->_data[$prop] = $val;
        }
    }

    /**
     * Overloading of the method call.
     *
     * @param string Method
     * @param array Arguments
     */
    function __call($method, $args)
    {
        // The foreign keys of the current object.
        if (isset($this->_m['get'][$method])) {
            if (isset($this->_cache[$method])) {
                return $this->_cache[$method];
            } else {
                $this->_cache[$method] = Pluf::factory($this->_m['get'][$method][0], $this->_data[$this->_m['get'][$method][1]]);
                if ($this->_cache[$method]->id == '') $this->_cache[$method] = null;
                return $this->_cache[$method];
            }
        }
        // Many to many or foreign keys on the other objects.
        if (isset($this->_m['list'][$method])) {
            if (is_array($this->_m['list'][$method])) {
                $model = $this->_m['list'][$method][0];
            } else {
                $model = $this->_m['list'][$method];
            }
            $args = array_merge(array($model, $method), $args);
            return call_user_func_array(array($this, 'getRelated'), $args);
        }
        // Extra methods added by fields
        if (isset($this->_m['extra'][$method])) {
            $args = array_merge(array($this->_m['extra'][$method][0], $method, $this), $args);
            Pluf::loadFunction($this->_m['extra'][$method][1]);
            return call_user_func_array($this->_m['extra'][$method][1], $args);
        }
        throw new Exception(sprintf('Method "%s" not available.', $method));
    }

    /**
     * Get a given item.
     *
     * @param int Id of the item.
     * @return mixed Item or false if not found.
     */
    function get($id)
    {
        $req = 'SELECT * FROM '.$this->getSqlTable().' WHERE id='.$this->_toDb($id, 'id');
        if (false === ($rs = $this->_con->select($req))) {
            throw new Exception($this->_con->getError());
        }
        if (count($rs) == 0) {
            return false;
        }
        foreach ($this->_a['cols'] as $col => $val) {
            $field = new $val['type']();
            if ($field->type != 'manytomany' && array_key_exists($col, $rs[0])) {
                $this->_data[$col] = $this->_fromDb($rs[0][$col], $col);
            }
        }
        $this->restore();
        return $this;
    }

    /**
     * Get one item.
     *
     * The parameters are the same as the ones of the getList method,
     * but, the return value is either:
     *
     * - The object
     * - null if no match
     * - Exception if the match results in more than one item.
     *
     * Usage:
     *
     * <pre>
     * $m = Pluf::factory('My_Model')->getOne(array('filter' => 'id=1'));
     * </pre>
     * <pre>
     * $m = Pluf::factory('My_Model')->getOne('id=1');
     * </pre>
     *
     * @param array|string Filter string or array given to getList
     * @see self::getList
     */
    public function getOne($p=array()) 
    {
        if (!is_array($p)) {
            $p = array('filter' => $p);
        }
        $items = $this->getList($p);
        if ($items->count() == 1) {
            return $items[0];
        }
        if ($items->count() == 0) {
            return null;
        }
        throw new Exception(__('Error: More than one matching item found.'));
    }

    /**
     * Get a list of items.
     *
     * The filter should be used only for simple filtering. If you want
     * a complex query, you should create a new view.
     * Both filter and order accept an array or a string in case of multiple
     * parameters:
     * Filter:
     *    array('col1=toto', 'col2=titi') will be used in a AND query
     *    or simply 'col1=toto'
     * Order:
     *    array('col1 ASC', 'col2 DESC') or 'col1 ASC'
     * 
     * This is modelled on the DB_Table pear module interface.
     *
     * @param array Associative array with the possible following
     *              keys:
     *    'view': The view to use
     *  'filter': The where clause to use
     *   'order': The ordering of the result set
     *   'start': The number of skipped rows in the result set
     *      'nb': The number of items to get in the result set
     *   'count': Run a count query and not a select if set to true
     * @return ArrayObject of items or through an exception if
     * database failure
     */
    function getList($p=array()) 
    {
        $default = array('view' => null, 
                         'filter' => null, 
                         'order' => null, 
                         'start' => null, 
                         'select' => null,
                         'nb' => null, 
                         'count' => false);
        $p = array_merge($default, $p);
        if (!is_null($p['view']) && !isset($this->_a['views'][$p['view']])) {
            throw new Exception(sprintf(__('The view "%s" is not defined.'), $p['view']));
        }
        $query = array(
                       'select' => $this->getSelect(),
                       'from' => $this->_a['table'],
                       'join' => '',
                       'where' => '',
                       'group' => '',
                       'having' => '',
                       'order' => '',
                       'limit' => '',
                       'props' => array(),
                       );
        if (!is_null($p['view'])) {
            $query = array_merge($query, $this->_a['views'][$p['view']]);
        }
        if (!is_null($p['select'])) {
            $query['select'] = $p['select'];
        }
        if (!is_null($p['filter'])) {
            if (is_array($p['filter'])) {
                $p['filter'] = implode(' AND ', $p['filter']);
            }
            if (strlen($query['where']) > 0) {
                $query['where'] .= ' AND ';
            }
            $query['where'] .= ' ('.$p['filter'].') ';
        }
        if (!is_null($p['order'])) {
            if (is_array($p['order'])) {
                $p['order'] = implode(', ', $p['order']);
            }
            if (strlen($query['order']) > 0 and strlen($p['order']) > 0) {
                $query['order'] .= ', ';
            }
            $query['order'] .= $p['order'];
        }
        if (!is_null($p['start']) && is_null($p['nb'])) {
            $p['nb'] = 10000000;
        }
        if (!is_null($p['start'])) {
            if ($p['start'] != 0) {
                $p['start'] = (int) $p['start'];
            }
            $p['nb'] = (int) $p['nb'];
            $query['limit'] = 'LIMIT '.$p['nb'].' OFFSET '.$p['start'];
        }
        if (!is_null($p['nb']) && is_null($p['start'])) {
            $p['nb'] = (int) $p['nb'];
            $query['limit'] = 'LIMIT '.$p['nb'];
        }
        if ($p['count'] == true) {
            if (isset($query['select_count'])) {
                $query['select'] = $query['select_count'];
            } else {
                $query['select'] = 'COUNT(*) as nb_items';
            }
            $query['order'] = '';
            $query['limit'] = '';
        }
        $req = 'SELECT '.$query['select'].' FROM '
            .$this->_con->pfx.$query['from'].' '.$query['join'];
        if (strlen($query['where'])) {
            $req .= "\n".'WHERE '.$query['where'];
        }
        if (strlen($query['group'])) {
            $req .= "\n".'GROUP BY '.$query['group'];
        }
        if (strlen($query['having'])) {
            $req .= "\n".'HAVING '.$query['having'];
        }
        if (strlen($query['order'])) {
            $req .= "\n".'ORDER BY '.$query['order'];
        }
        if (strlen($query['limit'])) {
            $req .= "\n".$query['limit'];
        }
        if (false === ($rs=$this->_con->select($req))) {
            throw new Exception($this->_con->getError());
        }
        if (count($rs) == 0) {
            return new ArrayObject();
        } 
        if ($p['count'] == true) {
            return $rs;
        }
        $res = new ArrayObject();
        foreach ($rs as $row) {
            $this->_reset();
            foreach ($this->_a['cols'] as $col => $val) {
                if (isset($row[$col])) $this->_data[$col] = $this->_fromDb($row[$col], $col);
            }
            // FIXME: The associated properties need to be converted too.
            foreach ($query['props'] as $prop => $key) {
                $this->_data[$key] = (isset($row[$prop])) ? $row[$prop] : null;
            }
            $this->restore();
            $res[] = clone($this);
        }
        return $res;
    }

    /**
     * Get the number of items.
     *
     * @see getList() for definition of the keys
     *
     * @param array with associative keys 'view' and 'filter'
     * @return int The number of items
     */
    function getCount($p=array())
    {
        $p['count'] = true;
        $count = $this->getList($p);
        if (empty($count) or count($count) == 0) { 
            return 0; 
        } else {
            return (int) $count[0]['nb_items'];
        }
    }

    /**
     * Get a list of related items.
     *
     * See the getList() method for usage of the view and filters.
     *
     * @param string Class of the related items
     * @param string Method call in a many to many related
     * @param array Parameters, see getList() for the definition of
     *              the keys
     * @return array Array of items
     */
    function getRelated($model, $method=null, $p=array())
    {
        $default = array('view' => null, 
                         'filter' => null, 
                         'order' => null, 
                         'start' => null, 
                         'nb' => null, 
                         'count' => false);
        $p = array_merge($default, $p);
        if ('' == $this->_data['id']) {
            return new ArrayObject();
        }
        $m = new $model();
        if (isset($this->_m['list'][$method]) 
            and is_array($this->_m['list'][$method])) {
            $foreignkey = $this->_m['list'][$method][1];
            if (strlen($foreignkey) == 0) {
                throw new Exception(sprintf(__('No matching foreign key found in model: %s for model %s'), $model, $this->_a['model']));
            }
            if (!is_null($p['filter'])) {
                if (is_array($p['filter'])) {
                    $p['filter'] = implode(' AND ', $p['filter']);
                }
                $p['filter'] .=  ' AND ';
            } else {
                $p['filter'] = '';
            }
            $p['filter'] .= $this->_con->qn($foreignkey).'='.$this->_toDb($this->_data['id'], 'id');
        } else {
            // Many to many: We generate a special view that is making
            // the join
            $hay = array(strtolower(Pluf::factory($model)->_a['model']), 
                         strtolower($this->_a['model']));
            sort($hay);
            $table = $hay[0].'_'.$hay[1].'_assoc';
            if (isset($m->_a['views'][$p['view']])) {
                $m->_a['views'][$p['view'].'__manytomany__'] = $m->_a['views'][$p['view']];
                if (!isset($m->_a['views'][$p['view'].'__manytomany__']['join'])) {
                    $m->_a['views'][$p['view'].'__manytomany__']['join'] = '';
                }
                if (!isset($m->_a['views'][$p['view'].'__manytomany__']['where'])) {
                    $m->_a['views'][$p['view'].'__manytomany__']['where'] = '';
                }
            } else {
                $m->_a['views']['__manytomany__'] = array('join' => '',
                                                     'where' => '');
                $p['view'] = '';
            }
            $m->_a['views'][$p['view'].'__manytomany__']['join'] .= 
                ' LEFT JOIN '.$this->_con->pfx.$table.' ON '
                .$this->_con->qn(strtolower($m->_a['model']).'_id').' = '.$this->_con->pfx.$m->_a['table'].'.id';

            $m->_a['views'][$p['view'].'__manytomany__']['where'] = $this->_con->qn(strtolower($this->_a['model']).'_id').'='.$this->_data['id'];
            $p['view'] = $p['view'].'__manytomany__';
        }
        return $m->getList($p);
    }

    /**
     * Generate the SQL select from the columns
     */
    function getSelect()
    {
        if (isset($this->_cache['getSelect'])) return $this->_cache['getSelect'];
        $select = array();
        $table = $this->getSqlTable();
        foreach ($this->_a['cols'] as $col=>$val) {
            if ($val['type'] != 'Pluf_DB_Field_Manytomany') {
                $select[] = $table.'.'.$this->_con->qn($col).' AS '.$this->_con->qn($col); 
            }
        }
        $this->_cache['getSelect'] = implode(', ', $select);
        return $this->_cache['getSelect'];
    }

    /**
     * Update the model into the database.
     *
     * If no where clause is provided, the index definition is used to
     * find the sequence. These are used to limit the update
     * to the current model.
     *
     * @param string Where clause to update specific items. ('')
     * @return bool Success
     */
    function update($where='')
    {
        $this->preSave();
        $req = 'UPDATE '.$this->getSqlTable().' SET'."\n";
        $fields = array();
        $assoc = array();
        foreach ($this->_a['cols'] as $col=>$val) {
            $field = new $val['type']();
            if ($col == 'id') {
                continue;
            } elseif ($field->type == 'manytomany') {
                if (is_array($this->$col)) {
                    $assoc[$val['model']] = $this->$col;
                }
                continue;
            }
            $fields[] = $this->_con->qn($col).' = '.$this->_toDb($this->$col, $col);
        }
        $req .= implode(','."\n", $fields);
        if (strlen($where) > 0) {
            $req .= ' WHERE '.$where;
        } else {
            $req .= ' WHERE id = '.$this->_toDb($this->_data['id'], 'id');
        }
        $this->_con->execute($req);
        if (false === $this->get($this->_data['id'])) {
            return false;
        }
        foreach ($assoc as $model=>$ids) {
            $this->batchAssoc($model, $ids);
        }
        $this->postSave();
        return true;
    }

    /**
     * Create the model into the database.
     *
     * If raw insert is requested, the preSave/postSave methods are
     * not called and the current id of the object is directly
     * used. This is particularily used when doing backup/restore of
     * data.
     * 
     * @param bool Raw insert (false)
     * @return bool Success
     */
    function create($raw=false)
    {
        if (!$raw) {
            $this->preSave(true);
        }
        $req = 'INSERT INTO '.$this->getSqlTable()."\n";
        $icols = array();
        $ivals = array();
        $assoc = array();
        foreach ($this->_a['cols'] as $col=>$val) {
            $field = new $val['type']();
            if ($col == 'id' and !$raw) {
                continue;
            } elseif ($field->type == 'manytomany') {
                // If is a defined array, we need to associate.
                if (is_array($this->_data[$col])) {
                    $assoc[$val['model']] = $this->_data[$col];
                }
                continue;
            }
            $icols[] = $this->_con->qn($col);
            $ivals[] = $this->_toDb($this->_data[$col], $col);
        }
        $req .= '('.implode(', ', $icols).') VALUES ';
        $req .= '('.implode(','."\n", $ivals).')';
        $this->_con->execute($req);
        if (!$raw) {
            if (false === ($id=$this->_con->getLastID())) {
                throw new Exception($this->_con->getError());
            }
            $this->_data['id'] = $id;
        }
        foreach ($assoc as $model=>$ids) {
            $this->batchAssoc($model, $ids);
        }
        if (!$raw) {
            $this->postSave(true);
        }
        return true;
    }

    /**
     * Get models affected by delete.
     *
     * @return array Models deleted if deleting current model.
     */
    function getDeleteSideEffect()
    {
        $affected = array();
        foreach ($this->_m['list'] as $method=>$details) {
            if (is_array($details)) {
                // foreignkey
                $related = $this->$method();
                $affected = array_merge($affected, (array) $related);
                foreach ($related as $rel) {
                    if ($details[0] == $this->_a['model']
                        and $rel->id == $this->_data['id']) {
                        continue; // $rel == $this
                    }
                    $affected = array_merge($affected, (array) $rel->getDeleteSideEffect());
                }
            }
        }
        return Pluf_Model_RemoveDuplicates($affected);
    }

    /**
     * Delete the current model from the database.
     *
     * If another model link to the current model through a foreign
     * key, find it and delete it. If this model is linked to other
     * through a many to many, delete the association.
     *
     * FIXME: No real test of circular references. It can break.
     */
    function delete()
    {
        if (false === $this->get($this->_data['id'])) {
            return false;
        }
        $this->preDelete();
        // Drop the row level permissions if we are using them
        if (Pluf::f('pluf_use_rowpermission', false)) {
            $_rpt = Pluf::factory('Pluf_RowPermission')->getSqlTable();
            $sql = new Pluf_SQL('model_class=%s AND model_id=%s',
                                array($this->_a['model'], $this->_data['id']));
            $this->_con->execute('DELETE FROM '.$_rpt.' WHERE '.$sql->gen());
        }
        // Find the models linking to the current one through a foreign key.
        foreach ($this->_m['list'] as $method=>$details) {
            if (is_array($details)) {
                // foreignkey
                $related = $this->$method();
                foreach ($related as $rel) {
                    if ($details[0] == $this->_a['model']
                        and $rel->id == $this->_data['id']) {
                        continue; // $rel == $this
                    }
                    // We do not really control if it can be deleted
                    // as we can find many times the same to delete.
                    $rel->delete();
                }
            } else {
                // manytomany
                $related = $this->$method();
                foreach ($related as $rel) {
                    $this->delAssoc($rel);
                }
            }
        }
        $req = 'DELETE FROM '.$this->getSqlTable().' WHERE id = '.$this->_toDb($this->_data['id'], 'id');
        $this->_con->execute($req);
        $this->_reset();
        return true;
    }

    /**
     * Reset the fields to default values.
     */
    function _reset()
    {
        foreach ($this->_a['cols'] as $col => $val) {
            if (isset($val['default'])) {
                $this->_data[$col] = $val['default'];
            } elseif (isset($val['is_null'])) {
                $this->_data[$col] = null;
            } else {
                 $this->_data[$col] = '';
            }
        }
    }


    /**
     * Represents the model in auto generated lists.
     * 
     * You need to overwrite this method to have a nice display of
     * your objects in the select boxes, logs.
     */
    function __toString()
    {
        return $this->_a['model'].'('.$this->_data['id'].')';
    }


    /**
     * Hook run just after loading a model from the database.
     *
     * Just overwrite it into your model to perform custom actions.
     */
    function restore()
    {
    }

    /**
     * Hook run just before saving a model in the database.
     *
     * Just overwrite it into your model to perform custom actions.
     *
     * @param bool Create.
     */
    function preSave($create=false)
    {
    }

    function postSave($create=false)
    {
    }

    /**
     * Hook run just before deleting a model from the database.
     *
     * Just overwrite it into your model to perform custom actions.
     */
    function preDelete()
    {
    }

    /**
     * Set the values from form data.
     */
    function setFromFormData($cleaned_values)
    {
        foreach ($cleaned_values as $key=>$val) {
            $this->_data[$key] = $val;
        }
    }

    /**
     * Set a view.
     *
     * @param string Name of the view.
     * @param array Definition of the view.
     */
    function setView($view, $def)
    {
        $this->_a['views'][$view] = $def;
    }

    /**
     * Prepare the value to be put in the DB.
     *
     * @param mixed Value.
     * @param string Column name.
     * @return string SQL ready string.
     */
    function _toDb($val, $col)
    {
        $m = $this->_con->type_cast[$this->_a['cols'][$col]['type']][1];
        return $m($val, $this->_con);
    }

    /**
     * Get the value from the DB.
     *
     * @param mixed Value.
     * @param string Column name.
     * @return mixed Value.
     */
    function _fromDb($val, $col)
    {
        $m = $this->_con->type_cast[$this->_a['cols'][$col]['type']][0];
        return ($m == 'Pluf_DB_IdentityFromDb') ? $val : $m($val);
    }

    /**
     * Display value.
     *
     * When you have a list of choices for a field and you want to get
     * the display value of the current stored value.
     *
     * @param string Field to display the value.
     * @return mixed Display value, if not available default to the value.
     */
    function displayVal($col)
    {
        if (!isset($this->_a['cols'][$col]['choices'])) {
            return $this->_data[$col]; // will on purposed failed if not set
        }
        $val = array_search($this->_data[$col], $this->_a['cols'][$col]['choices']);
        if ($val !== false) {
            return $val;
        }
        return $this->_data[$col];
    }
}


/**
 * Check if a model is already in an array of models.
 *
 * It is not possible to override the == function in PHP to directly
 * use in_array.
 *
 * @param Pluf_Model The model to test
 * @param Array The models
 * @return bool
 */
function Pluf_Model_InArray($model, $array) 
{
    if ($model->id == '') {
        return false;
    }
    foreach ($array as $modelin) {
        if ($modelin->_a['model'] == $model->_a['model']
            and $modelin->id == $model->id) {
            return true;
        }
    }
    return false;
}

/**
 * Return a list of unique models.
 *
 * @param array Models with duplicates
 * @return array Models with duplicates.
 */
function Pluf_Model_RemoveDuplicates($array)
{
    $res = array();
    foreach ($array as $model) {
        if (!Pluf_Model_InArray($model, $res)) {
            $res[] = $model;
        }
    }
    return $res;
}

Archive Download this file

Branches

Tags

Number of commits:
Page rendered in 0.07870s using 11 queries.