<?php

/*
 *  $Id: PHP5ComplexObjectBuilder.php 536 2007-01-10 14:30:38Z heltem $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information please see
 * <http://propel.phpdb.org>.
 */

require_once 'propel/engine/builder/om/php5/PHP5BasicObjectBuilder.php';

/**
 * Generates a PHP5 base Object class with complex object model methods.
 *
 * This class adds on to the PHP5BasicObjectBuilder class by adding more complex
 * logic related to relationships to methods like the setters, and save method. Also,
 * new get*Join*() methods are added to fetch related rows.
 *
 * @author     Hans Lellelid <hans@xmpl.org>
 * @package    propel.engine.builder.om.php5
 */
class PHP5ComplexObjectBuilder extends PHP5BasicObjectBuilder {

    /**
     * Adds additional attributes used for complex object model.
     * @param      string &$script The script will be modified in this method.
     * @see        addFKAttributes()
     * @see        addRefFKAttributes()
     * @see        addAlreadyInSaveAttribute()
     * @see        addAlreadyInValidationAttribute()
     */
    protected function addAttributes(&$script)
    {
        $table = $this->getTable();
        parent::addAttributes($script);

        foreach ($table->getForeignKeys() as $fk) {
            $this->addFKAttributes($script, $fk);
        }

        foreach($table->getReferrers() as $refFK) {
            // if ($refFK->getTable()->getName() != $table->getName()) {
                $this->addRefFKAttributes($script, $refFK);
            // }
        }

        $this->addAlreadyInSaveAttribute($script);
        $this->addAlreadyInValidationAttribute($script);
    }

    /**
     * Specifies the methods that are added as part of the basic OM class.
     * This can be overridden by subclasses that wish to add more methods.
     * @param      string &$script The script will be modified in this method.
     * @see        PHP5BasicObjectBuilder::addClassBody()
     */
    protected function addClassBody(&$script)
    {
        $table = $this->getTable();
        parent::addClassBody($script);


        $this->addFKMethods($script);
        $this->addRefFKMethods($script);

    }

    /**
     * Adds the close of mutator (setter) method for a column.
     * This method overrides the method from PHP5BasicObjectBuilder in order to
     * account for updating related objects.
     * @param      string &$script The script will be modified in this method.
     * @param      Column $col The current column.
     * @see        PHP5BasicObjectBuilder::addMutatorClose()
     */
    protected function addMutatorClose(&$script, Column $col)
    {
        $table = $this->getTable();
        $cfc=$col->getPhpName();
        $clo=strtolower($col->getName());

        if ($col->isForeignKey()) {

            $tblFK = $table->getDatabase()->getTable($col->getRelatedTableName());
            $colFK = $tblFK->getColumn($col->getRelatedColumnName());

            $varName = $this->getFKVarName($col->getForeignKey());

            $script .= "
        if (\$this->$varName !== null && \$this->".$varName."->get".$colFK->getPhpName()."() !== \$v) {
            \$this->$varName = null;
        }
";
        } /* if col is foreign key */

        foreach ($col->getReferrers() as $fk) {

            $tblFK = $this->getDatabase()->getTable($fk->getForeignTableName());

            if ( $tblFK->getName() != $table->getName() ) {

                $collName = $this->getRefFKCollVarName($fk);

                $tblFK = $table->getDatabase()->getTable($col->getRelatedTableName());
                $colFK = $tblFK->getColumn($col->getRelatedColumnName());

                $script .= "

        // update associated ".$tblFK->getPhpName()."
        if (\$this->$collName !== null) {
            foreach(\$this->$collName as \$referrerObject) {
                  \$referrerObject->set".$colFK->getPhpName()."(\$v);
              }
          }
";
            } // if
        } // foreach

        $script .= "
    } // set$cfc()
";
    } // addMutatorClose()

    /**
     * Adds the methods related to validating, saving and deleting the object.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addManipulationMethods(&$script)
    {
        $this->addDelete($script);
        $this->addSave($script);
        $this->addDoSave($script);
    }

    /**
     * Adds the methods related to validationg the object.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addValidationMethods(&$script)
    {
        parent::addValidationMethods($script);
        $this->addDoValidate($script);
    }

    /**
     * Convenience method to get the foreign Table object for an fkey.
     * @return     Table
     */
    protected function getForeignTable(ForeignKey $fk)
    {
        return $this->getTable()->getDatabase()->getTable($fk->getForeignTableName());
    }

    /**
     * Gets the PHP method name affix to be used for fkeys for the current table (not referrers to this table).
     *
     * The difference between this method and the getRefFKPhpNameAffix() method is that in this method the
     * classname in the affix is the foreign table classname.
     *
     * @param      ForeignKey $fk The local FK that we need a name for.
     * @param      boolean $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
     * @return     string
     */
    public function getFKPhpNameAffix(ForeignKey $fk, $plural = false)
    {
        $className = $this->getForeignTable($fk)->getPhpName();
        return $className . ($plural ? 's' : '') . $this->getRelatedBySuffix($fk);
    }

    /**
     * Gets the PHP method name affix to be used for referencing foreign key methods and variable names (e.g. set????(), $coll???).
     *
     * The difference between this method and the getFKPhpNameAffix() method is that in this method the
     * classname in the affix is the classname of the local fkey table.
     *
     * @param      ForeignKey $fk The referrer FK that we need a name for.
     * @param      boolean $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
     * @return     string
     */
    public function getRefFKPhpNameAffix(ForeignKey $fk, $plural = false)
    {
        $className = $fk->getTable()->getPhpName();
        return $className . ($plural ? 's' : '') . $this->getRelatedBySuffix($fk);
    }

    /**
     * Gets the "RelatedBy*" suffix (if needed) that is attached to method and variable names.
     *
     * The related by suffix is based on the local columns of the foreign key.  If there is more than
     * one column in a table that points to the same foreign table, then a 'RelatedByLocalColName' suffix
     * will be appended.
     *
     * @return     string
     */
    protected function getRelatedBySuffix(ForeignKey $fk)
    {
        $relCol = "";
        foreach ($fk->getLocalColumns() as $columnName) {
            $column = $fk->getTable()->getColumn($columnName);
            if (!$column) {
                $e = new Exception("Could not fetch column: $columnName in table " . $fk->getTable()->getName());
                print $e;
                throw $e;
            }
            if ($column->isMultipleFK() || $fk->getForeignTableName() == $fk->getTable()->getName()) {
                // if there are seeral foreign keys that point to the same table
                // then we need to generate methods like getAuthorRelatedByColName()
                // instead of just getAuthor().  Currently we are doing the same
                // for self-referential foreign keys, to avoid confusion.
                $relCol .= $column->getPhpName();
            }
        }

        if ($relCol != "") {
            $relCol = "RelatedBy" . $relCol;
        }

        return $relCol;
    }

    protected function getFKVarName(ForeignKey $fk)
    {
        return 'a' . $this->getFKPhpNameAffix($fk, $plural = false);
    }

    protected function getRefFKCollVarName(ForeignKey $fk)
    {
        return 'coll' . $this->getRefFKPhpNameAffix($fk, $plural = true);
    }

    protected function getRefFKLastCriteriaVarName(ForeignKey $fk)
    {
        return 'last' . $this->getRefFKPhpNameAffix($fk, $plural = false) . 'Criteria';
    }

    // ----------------------------------------------------------------
    //
    // F K    M E T H O D S
    //
    // ----------------------------------------------------------------

    /**
     * Adds the methods that get & set objects related by foreign key to the current object.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addFKMethods(&$script)
    {
        foreach ($this->getTable()->getForeignKeys() as $fk) {
            $this->addFKMutator($script, $fk);
            $this->addFKAccessor($script, $fk);
        } // foreach fk
    }

    /**
     * Adds the class attributes that are needed to store fkey related objects.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addFKAttributes(&$script, ForeignKey $fk)
    {
        $className = $this->getForeignTable($fk)->getPhpName();
        $varName = $this->getFKVarName($fk);

        $script .= "
    /**
     * @var        $className
     */
    protected $".$varName.";
";
    }

    /**
     * Adds the mutator (setter) method for setting an fkey related object.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addFKMutator(&$script, ForeignKey $fk)
    {
        $table = $this->getTable();
        $tblFK = $this->getForeignTable($fk);
        $className = $this->getForeignTable($fk)->getPhpName();
        $varName = $this->getFKVarName($fk);

        $script .= "
    /**
     * Declares an association between this object and a $className object.
     *
     * @param      $className \$v
     * @return     void
     * @throws     PropelException
     */
    public function set".$this->getFKPhpNameAffix($fk, $plural = false)."(\$v)
    {
";
            foreach ($fk->getLocalColumns() as $columnName) {
                $column = $table->getColumn($columnName);
                $lfmap = $fk->getLocalForeignMapping();
                $colFKName = $lfmap[$columnName];
                $colFK = $tblFK->getColumn($colFKName);
                $script .= "

        if (\$v === null) {
            \$this->set".$column->getPhpName()."(".var_export($column->getDefaultValue(), true).");
        } else {
            \$this->set".$column->getPhpName()."(\$v->get".$colFK->getPhpName()."());
        }
";

            } /* foreach local col */

            $script .= "

        \$this->$varName = \$v;
    }
";
    }

    /**
     * Adds the accessor (getter) method for getting an fkey related object.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addFKAccessor(&$script, ForeignKey $fk)
    {
        $table = $this->getTable();

        $className = $this->getForeignTable($fk)->getPhpName();
        $varName = $this->getFKVarName($fk);

        $and = "";
        $comma = "";
        $conditional = "";
        $arglist = "";
        $argsize = 0;
        foreach ($fk->getLocalColumns() as $columnName) {
            $column = $table->getColumn($columnName);
            $cptype = $column->getPhpNative();
            $clo = strtolower($column->getName());

            // FIXME: is this correct? what about negative numbers?
            if ($cptype == "integer" || $cptype == "float" || $cptype == "double") {
                $conditional .= $and . "\$this->". $clo ." > 0";
            } elseif($cptype == "string") {
                $conditional .= $and . "(\$this->" . $clo ." !== \"\" && \$this->".$clo." !== null)";
            } else {
                $conditional .= $and . "\$this->" . $clo ." !== null";
            }
            $arglist .= $comma . "\$this->" . $clo;
            $and = " && ";
            $comma = ", ";
            $argsize = $argsize + 1;
        }

        $pCollName = $this->getFKPhpNameAffix($fk, $plural = true);

        $fkPeerBuilder = OMBuilder::getNewPeerBuilder($this->getForeignTable($fk));

        $script .= "

    /**
     * Get the associated $className object
     *
     * @param      Connection Optional Connection object.
     * @return     $className The associated $className object.
     * @throws     PropelException
     */
    public function get".$this->getFKPhpNameAffix($fk, $plural = false)."(\$con = null)
    {
        // include the related Peer class
        include_once '".$fkPeerBuilder->getClassFilePath()."';

        if (\$this->$varName === null && ($conditional)) {
";
        $script .= "
            \$this->$varName = ".$fkPeerBuilder->getPeerClassname()."::".$fkPeerBuilder->getRetrieveMethodName()."($arglist, \$con);

            /* The following can be used instead of the line above to
               guarantee the related object contains a reference
               to this object, but this level of coupling
               may be undesirable in many circumstances.
               As it can lead to a db query with many results that may
               never be used.
               \$obj = ".$fkPeerBuilder->getPeerClassname()."::retrieveByPK($arglist, \$con);
               \$obj->add$pCollName(\$this);
             */
        }
        return \$this->$varName;
    }
";

    } // addFKAccessor

    /**
     * Adds a convenience method for setting a related object by specifying the primary key.
     * This can be used in conjunction with the getPrimaryKey() for systems where nothing is known
     * about the actual objects being related.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addFKByKeyMutator(&$script, ForeignKey $fk)
    {
        $table = $this->getTable();

        #$className = $this->getForeignTable($fk)->getPhpName();
        $methodAffix = $this->getFKPhpNameAffix($fk);
        #$varName = $this->getFKVarName($fk);

        $script .= "
    /**
     * Provides convenient way to set a relationship based on a
     * key.  e.g.
     * <code>\$bar->setFooKey(\$foo->getPrimaryKey())</code>
     *";
        if (count($fk->getLocalColumns()) > 1) {
            $script .= "
     * Note: It is important that the xml schema used to create this class
     * maintains consistency in the order of related columns between
     * ".$table->getName()." and ". $tblFK->getName().".
     * If for some reason this is impossible, this method should be
     * overridden in <code>".$table->getPhpName()."</code>.";
        }
        $script .= "
     * @return     void
     * @throws     PropelException
     */
    public function set".$methodAffix."Key(\$key)
    {
";
        if (count($fk->getLocalColumns()) > 1) {
            $i = 0;
            foreach ($fk->getLocalColumns() as $colName) {
                $col = $table->getColumn($colName);
                $fktype = $col->getPhpNative();
                $script .= "
            \$this->set".$col->getPhpName()."( ($fktype) \$key[$i] );
";
                $i++;
            } /* foreach */
        } else {
            $lcols = $fk->getLocalColumns();
            $colName = $lcols[0];
            $col = $table->getColumn($colName);
            $fktype = $col->getPhpNative();
            $script .= "
        \$this->set".$col->getPhpName()."( ($fktype) \$key);
";
        }
        $script .= "
    }
";
    } // addFKByKeyMutator()

    /**
     * Adds the method that fetches fkey-related (referencing) objects but also joins in data from another table.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addRefFKGetJoinMethods(&$script, ForeignKey $refFK)
    {
        $table = $this->getTable();
        $tblFK = $refFK->getTable();

        $relCol = $this->getRefFKPhpNameAffix($refFK, $plural=true);
        $collName = $this->getRefFKCollVarName($refFK);
        $lastCriteriaName = $this->getRefFKLastCriteriaVarName($refFK);

        $fkPeerBuilder = OMBuilder::getNewPeerBuilder($tblFK);

        $lastTable = "";
        foreach ($tblFK->getForeignKeys() as $fk2) {

            // Add join methods if the fk2 table is not this table or
            // the fk2 table references this table multiple times.

            $doJoinGet = true;

            if ( $fk2->getForeignTableName() == $table->getName() ) {
                $doJoinGet = false;
            }

            foreach ($fk2->getLocalColumns() as $columnName) {
                $column = $tblFK->getColumn($columnName);
                if ($column->isMultipleFK()) {
                    $doJoinGet = true;
                }
            }

            $tblFK2 = $this->getForeignTable($fk2);
            $doJoinGet = !$tblFK2->isForReferenceOnly();

            // it doesn't make sense to join in rows from the curent table, since we are fetching
            // objects related to *this* table (i.e. the joined rows will all be the same row as current object)
            if ($this->getTable()->getPhpName() == $tblFK2->getPhpName()) {
                $doJoinGet = false;
            }

            $relCol2 = $this->getFKPhpNameAffix($fk2, $plural = false);

            if ( $this->getRelatedBySuffix($refFK) != "" &&
                            ($this->getRelatedBySuffix($refFK) == $this->getRelatedBySuffix($fk2))) {
                $doJoinGet = false;
            }

            if ($doJoinGet) {
                $script .= "

    /**
     * If this collection has already been initialized with
     * an identical criteria, it returns the collection.
     * Otherwise if this ".$table->getPhpName()." is new, it will return
     * an empty collection; or if this ".$table->getPhpName()." has previously
     * been saved, it will retrieve related $relCol from storage.
     *
     * This method is protected by default in order to keep the public
     * api reasonable.  You can provide public methods for those you
     * actually need in ".$table->getPhpName().".
     */
    public function get".$relCol."Join".$relCol2."(\$criteria = null, \$con = null)
    {
        // include the Peer class
        include_once '".$fkPeerBuilder->getClassFilePath()."';
        if (\$criteria === null) {
            \$criteria = new Criteria();
        }
        elseif (\$criteria instanceof Criteria)
        {
            \$criteria = clone \$criteria;
        }

        if (\$this->$collName === null) {
            if (\$this->isNew()) {
                \$this->$collName = array();
            } else {
";
                foreach ($refFK->getForeignColumns() as $columnName) {
                    $column = $table->getColumn($columnName);
                    $flMap = $refFK->getForeignLocalMapping();
                    $colFKName = $flMap[$columnName];
                    $colFK = $tblFK->getColumn($colFKName);
                    if ($colFK === null) {
                        $e = new Exception("Column $colFKName not found in " . $tblFK->getName());
                        print $e;
                        throw $e;
                    }
                    $script .= "
                \$criteria->add(".$fkPeerBuilder->getColumnConstant($colFK).", \$this->get".$column->getPhpName()."());
";
                } // end foreach ($fk->getForeignColumns()

                $script .= "
                \$this->$collName = ".$fkPeerBuilder->getPeerClassname()."::doSelectJoin$relCol2(\$criteria, \$con);
            }
        } else {
            // the following code is to determine if a new query is
            // called for.  If the criteria is the same as the last
            // one, just return the collection.
";
                foreach ($refFK->getForeignColumns() as $columnName) {
                    $column = $table->getColumn($columnName);
                    $flMap = $refFK->getForeignLocalMapping();
                    $colFKName = $flMap[$columnName];
                    $colFK = $tblFK->getColumn($colFKName);
                    $script .= "
            \$criteria->add(".$fkPeerBuilder->getColumnConstant($colFK).", \$this->get".$column->getPhpName()."());
";
                } /* end foreach ($fk->getForeignColumns() */

                $script .= "
            if (!isset(\$this->$lastCriteriaName) || !\$this->".$lastCriteriaName."->equals(\$criteria)) {
                \$this->$collName = ".$fkPeerBuilder->getPeerClassname()."::doSelectJoin$relCol2(\$criteria, \$con);
            }
        }
        \$this->$lastCriteriaName = \$criteria;

        return \$this->$collName;
    }
";
            } /* end if($doJoinGet) */

        } /* end foreach ($tblFK->getForeignKeys() as $fk2) { */

    } // function


    // ----------------------------------------------------------------
    //
    // R E F E R R E R    F K    M E T H O D S
    //
    // ----------------------------------------------------------------

    /**
     * Adds the attributes used to store objects that have referrer fkey relationships to this object.
     * <code>protected collVarName;</code>
     * <code>private lastVarNameCriteria = null;</code>
     * @param      string &$script The script will be modified in this method.
     */
    protected function addRefFKAttributes(&$script, ForeignKey $refFK)
    {
        $collName = $this->getRefFKCollVarName($refFK);
        $lastCriteriaName = $this->getRefFKLastCriteriaVarName($refFK);

        $script .= "
    /**
     * Collection to store aggregation of $collName.
     * @var        array
     */
    protected $".$collName.";

    /**
     * The criteria used to select the current contents of $collName.
     * @var        Criteria
     */
    protected \$".$lastCriteriaName." = null;
";
    }

    /**
     * Adds the methods for retrieving, initializing, adding objects that are related to this one by foreign keys.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addRefFKMethods(&$script)
    {
        foreach($this->getTable()->getReferrers() as $refFK) {
            // if ( $refFK->getTable()->getName() != $this->getTable()->getName() ) {
                $this->addRefFKInit($script, $refFK);
                $this->addRefFKGet($script, $refFK);
                $this->addRefFKCount($script, $refFK);
                $this->addRefFKAdd($script, $refFK);
                $this->addRefFKGetJoinMethods($script, $refFK);
            // }
        }
    }

    /**
     * Adds the method that initializes the referrer fkey collection.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addRefFKInit(&$script, ForeignKey $refFK) {

        $relCol = $this->getRefFKPhpNameAffix($refFK, $plural = true);
        $collName = $this->getRefFKCollVarName($refFK);

        $script .= "
    /**
     * Temporary storage of $collName to save a possible db hit in
     * the event objects are add to the collection, but the
     * complete collection is never requested.
     * @return     void
     */
    public function init$relCol()
    {
        if (\$this->$collName === null) {
            \$this->$collName = array();
        }
    }
";
    } // addRefererInit()

    /**
     * Adds the method that adds an object into the referrer fkey collection.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addRefFKAdd(&$script, ForeignKey $refFK)
    {
        $tblFK = $refFK->getTable();
        $className = $refFK->getTable()->getPhpName();

        $joinedTableObjectBuilder = OMBuilder::getNewObjectBuilder($refFK->getTable());

        $script .= "
    /**
     * Method called to associate a ".$tblFK->getPhpName()." object to this object
     * through the $className foreign key attribute
     *
     * @param      $className \$l $className
     * @return     void
     * @throws     PropelException
     */
    public function add".$this->getRefFKPhpNameAffix($refFK, $plural = false)."($className \$l)
    {
        \$this->coll".$this->getRefFKPhpNameAffix($refFK, $plural = true)."[] = \$l;
        \$l->set".$this->getFKPhpNameAffix($refFK, $plural = false)."(\$this);
    }
";
    } // addRefererAdd

    /**
     * Adds the method that returns the size of the referrer fkey collection.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addRefFKCount(&$script, ForeignKey $refFK)
    {
        $relCol = $this->getRefFKPhpNameAffix($refFK, $plural = true);

        $fkPeerBuilder = OMBuilder::getNewPeerBuilder($refFK->getTable());

        $script .= "
    /**
     * Returns the number of related $relCol.
     *
     * @param      Criteria \$criteria
     * @param      boolean \$distinct
     * @param      Connection \$con
     * @throws     PropelException
     */
    public function count$relCol(\$criteria = null, \$distinct = false, \$con = null)
    {
        // include the Peer class
        include_once '".$fkPeerBuilder->getClassFilePath()."';
        if (\$criteria === null) {
            \$criteria = new Criteria();
        }
        elseif (\$criteria instanceof Criteria)
        {
            \$criteria = clone \$criteria;
        }
";
        foreach ($refFK->getForeignColumns() as $columnName) {
            $column = $this->getTable()->getColumn($columnName);
            $flmap = $refFK->getForeignLocalMapping();
            $colFKName = $flmap[$columnName];
            $colFK = $refFK->getTable()->getColumn($colFKName);
            $script .= "
        \$criteria->add(".$fkPeerBuilder->getColumnConstant($colFK).", \$this->get".$column->getPhpName()."());
";
        } // end foreach ($fk->getForeignColumns()
        $script .="
        return ".$fkPeerBuilder->getPeerClassname()."::doCount(\$criteria, \$distinct, \$con);
    }
";
    } // addRefererCount

    /**
     * Adds the method that returns the referrer fkey collection.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addRefFKGet(&$script, ForeignKey $refFK)
    {
        $table = $this->getTable();
        $tblFK = $refFK->getTable();

        $fkPeerBuilder = OMBuilder::getNewPeerBuilder($refFK->getTable());
        $relCol = $this->getRefFKPhpNameAffix($refFK, $plural = true);

        $collName = $this->getRefFKCollVarName($refFK);
        $lastCriteriaName = $this->getRefFKLastCriteriaVarName($refFK);

        $script .= "
    /**
     * If this collection has already been initialized with
     * an identical criteria, it returns the collection.
     * Otherwise if this ".$table->getPhpName()." has previously
     * been saved, it will retrieve related $relCol from storage.
     * If this ".$table->getPhpName()." is new, it will return
     * an empty collection or the current collection, the criteria
     * is ignored on a new object.
     *
     * @param      Connection \$con
     * @param      Criteria \$criteria
     * @throws     PropelException
     */
    public function get$relCol(\$criteria = null, \$con = null)
    {
        // include the Peer class
        include_once '".$fkPeerBuilder->getClassFilePath()."';
        if (\$criteria === null) {
            \$criteria = new Criteria();
        }
        elseif (\$criteria instanceof Criteria)
        {
            \$criteria = clone \$criteria;
        }

        if (\$this->$collName === null) {
            if (\$this->isNew()) {
               \$this->$collName = array();
            } else {
";
        foreach ($refFK->getLocalColumns() as $colFKName) {
            // $colFKName is local to the referring table (i.e. foreign to this table)
            $lfmap = $refFK->getLocalForeignMapping();
            $localColumn = $this->getTable()->getColumn($lfmap[$colFKName]);
            $colFK = $refFK->getTable()->getColumn($colFKName);

            $script .= "
                \$criteria->add(".$fkPeerBuilder->getColumnConstant($colFK).", \$this->get".$localColumn->getPhpName()."());
";
        } // end foreach ($fk->getForeignColumns()

        $script .= "
                ".$fkPeerBuilder->getPeerClassname()."::addSelectColumns(\$criteria);
                \$this->$collName = ".$fkPeerBuilder->getPeerClassname()."::doSelect(\$criteria, \$con);
            }
        } else {
            // criteria has no effect for a new object
            if (!\$this->isNew()) {
                // the following code is to determine if a new query is
                // called for.  If the criteria is the same as the last
                // one, just return the collection.
";
        foreach ($refFK->getLocalColumns() as $colFKName) {
            // $colFKName is local to the referring table (i.e. foreign to this table)
            $lfmap = $refFK->getLocalForeignMapping();
            $localColumn = $this->getTable()->getColumn($lfmap[$colFKName]);
            $colFK = $refFK->getTable()->getColumn($colFKName);
            $script .= "

                \$criteria->add(".$fkPeerBuilder->getColumnConstant($colFK).", \$this->get".$localColumn->getPhpName()."());
";
    } // foreach ($fk->getForeignColumns()
$script .= "
                ".$fkPeerBuilder->getPeerClassname()."::addSelectColumns(\$criteria);
                if (!isset(\$this->$lastCriteriaName) || !\$this->".$lastCriteriaName."->equals(\$criteria)) {
                    \$this->$collName = ".$fkPeerBuilder->getPeerClassname()."::doSelect(\$criteria, \$con);
                }
            }
        }
        \$this->$lastCriteriaName = \$criteria;
        return \$this->$collName;
    }
";
    } // addRefererGet()



    // ----------------------------------------------------------------
    //
    // M A N I P U L A T I O N    M E T H O D S
    //
    // ----------------------------------------------------------------

    /**
     * Adds the workhourse doSave() method.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addDoSave(&$script)
    {
        $table = $this->getTable();

        $script .= "
    /**
     * Stores the object in the database.
     *
     * If the object is new, it inserts it; otherwise an update is performed.
     * All related objects are also updated in this method.
     *
     * @param      Connection \$con
     * @return     int The number of rows affected by this insert/update and any referring
     * @throws     PropelException
     * @see        save()
     */
    protected function doSave(\$con)
    {
        \$affectedRows = 0; // initialize var to track total num of affected rows
        if (!\$this->alreadyInSave) {
            \$this->alreadyInSave = true;
";

        if (count($table->getForeignKeys())) {

            $script .= "

            // We call the save method on the following object(s) if they
            // were passed to this object by their coresponding set
            // method.  This object relates to these object(s) by a
            // foreign key reference.
";

            foreach($table->getForeignKeys() as $fk)
            {
                $aVarName = $this->getFKVarName($fk);
                $script .= "
            if (\$this->$aVarName !== null) {
                if (\$this->".$aVarName."->isModified()) {
                    \$affectedRows += \$this->".$aVarName."->save(\$con);
                }
                \$this->set".$this->getFKPhpNameAffix($fk, $plural = false)."(\$this->$aVarName);
            }
";
            } // foreach foreign k
        } // if (count(foreign keys))

        $script .= "

            // If this object has been modified, then save it to the database.
            if (\$this->isModified()";

        /*
        FIXME: this doesn't work right now because the BasePeer::doInsert() method
        expects to be passed a Criteria object that contains columns (which tell BasePeer
        which table is being updated)
        if ($table->hasAutoIncrementPrimaryKey()) {
            $script .= " || \$this->isNew()";
        }
        */

        $script .= ") {
                if (\$this->isNew()) {
                    \$pk = ".$this->getPeerClassname()."::doInsert(\$this, \$con);
                    \$affectedRows += 1; // we are assuming that there is only 1 row per doInsert() which
                                         // should always be true here (even though technically
                                         // BasePeer::doInsert() can insert multiple rows).
";
        if ($table->getIdMethod() != IDMethod::NO_ID_METHOD) {

            if (count($pks = $table->getPrimaryKey())) {
                foreach ($pks as $pk) {
                    if ($pk->isAutoIncrement()) {
                        $script .= "
                    \$this->set".$pk->getPhpName()."(\$pk);  //[IMV] update autoincrement primary key
";
                    }
                }
            }
        } // if (id method != "none")

        $script .= "
                    \$this->setNew(false);
                } else {
                    \$affectedRows += ".$this->getPeerClassname()."::doUpdate(\$this, \$con);
                }
                \$this->resetModified(); // [HL] After being saved an object is no longer 'modified'
            }
";

        foreach ($table->getReferrers() as $fk) {
            $collName = $this->getRefFKCollVarName($fk);
            //HL: commenting out self-referrential check below
            //      it seems to work as expected and is desireable since we are also enabling the copy()ing of these related rows
            //if ( $fk->getTable()->getName() != $table->getName() ) {
                $script .= "
            if (\$this->$collName !== null) {
                foreach(\$this->$collName as \$referrerFK) {
                    if (!\$referrerFK->isDeleted()) {
                        \$affectedRows += \$referrerFK->save(\$con);
                    }
                }
            }
";
            //HL: commenting out close of self-referrential check
            //} /* if tableFK != table */
        } /* foreach getReferrers() */
        $script .= "
            \$this->alreadyInSave = false;
        }
        return \$affectedRows;
    } // doSave()
";

    }

    /**
     * Adds the $alreadyInSave attribute, which prevents attempting to re-save the same object.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addAlreadyInSaveAttribute(&$script)
    {
        $script .= "
    /**
     * Flag to prevent endless save loop, if this object is referenced
     * by another object which falls in this transaction.
     * @var        boolean
     */
    protected \$alreadyInSave = false;
";
    }

    /**
     * Adds the save() method.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addSave(&$script)
    {
        $script .= "
    /**
     * Stores the object in the database.  If the object is new,
     * it inserts it; otherwise an update is performed.  This method
     * wraps the doSave() worker method in a transaction.
     *
     * @param      Connection \$con
     * @return     int The number of rows affected by this insert/update
     * @throws     PropelException
     * @see        doSave()
     */
    public function save(\$con = null)
    {
        if (\$this->isDeleted()) {
            throw new PropelException(\"You cannot save an object that has been deleted.\");
        }

        if (\$con === null) {
            \$con = Propel::getConnection(".$this->getPeerClassname()."::DATABASE_NAME);
        }

        try {
            \$con->begin();
            \$affectedRows = \$this->doSave(\$con);
            \$con->commit();
            return \$affectedRows;
        } catch (PropelException \$e) {
            \$con->rollback();
            throw \$e;
        }
    }
";

    }

    /**
     * Adds the $alreadyInValidation attribute, which prevents attempting to re-validate the same object.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addAlreadyInValidationAttribute(&$script)
    {
        $script .= "
    /**
     * Flag to prevent endless validation loop, if this object is referenced
     * by another object which falls in this transaction.
     * @var        boolean
     */
    protected \$alreadyInValidation = false;
";
    }

    /**
     * Adds the validate() method.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addValidate(&$script)
    {
        $script .= "
    /**
     * Validates the objects modified field values and all objects related to this table.
     *
     * If \$columns is either a column name or an array of column names
     * only those columns are validated.
     *
     * @param      mixed \$columns Column name or an array of column names.
     * @return     boolean Whether all columns pass validation.
     * @see        doValidate()
     * @see        getValidationFailures()
     */
    public function validate(\$columns = null)
    {
        \$res = \$this->doValidate(\$columns);
        if (\$res === true) {
            \$this->validationFailures = array();
            return true;
        } else {
            \$this->validationFailures = \$res;
            return false;
        }
    }
";
    } // addValidate()

    /**
     * Adds the workhourse doValidate() method.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addDoValidate(&$script)
    {
        $table = $this->getTable();

        $script .= "
    /**
     * This function performs the validation work for complex object models.
     *
     * In addition to checking the current object, all related objects will
     * also be validated.  If all pass then <code>true</code> is returned; otherwise
     * an aggreagated array of ValidationFailed objects will be returned.
     *
     * @param      array \$columns Array of column names to validate.
     * @return     mixed <code>true</code> if all validations pass; 
                   array of <code>ValidationFailed</code> objects otherwise.
     */
    protected function doValidate(\$columns = null)
    {
        if (!\$this->alreadyInValidation) {
            \$this->alreadyInValidation = true;
            \$retval = null;

            \$failureMap = array();
";
        if (count($table->getForeignKeys()) != 0) {
            $script .= "

            // We call the validate method on the following object(s) if they
            // were passed to this object by their coresponding set
            // method.  This object relates to these object(s) by a
            // foreign key reference.
";
            foreach($table->getForeignKeys() as $fk) {
                $aVarName = $this->getFKVarName($fk);
                $script .= "
            if (\$this->".$aVarName." !== null) {
                if (!\$this->".$aVarName."->validate(\$columns)) {
                    \$failureMap = array_merge(\$failureMap, \$this->".$aVarName."->getValidationFailures());
                }
            }
";
            } /* for() */
        } /* if count(fkeys) */

        $script .= "

            if ((\$retval = ".$this->getPeerClassname()."::doValidate(\$this, \$columns)) !== true) {
                \$failureMap = array_merge(\$failureMap, \$retval);
            }

";

        foreach ($table->getReferrers() as $fk) {
            $tblFK = $fk->getTable();
            if ( $tblFK->getName() != $table->getName() ) {
                $collName = $this->getRefFKCollVarName($fk);
                $script .= "
                if (\$this->$collName !== null) {
                    foreach(\$this->$collName as \$referrerFK) {
                        if (!\$referrerFK->validate(\$columns)) {
                            \$failureMap = array_merge(\$failureMap, \$referrerFK->getValidationFailures());
                        }
                    }
                }
";
            } /* if tableFK !+ table */
        } /* foreach getReferrers() */

        $script .= "

            \$this->alreadyInValidation = false;
        }

        return (!empty(\$failureMap) ? \$failureMap : true);
    }
";
    } // addDoValidate()

    /**
     * Adds the copy() method, which (in complex OM) includes the $deepCopy param for making copies of related objects.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addCopy(&$script)
    {
        $this->addCopyInto($script);

        $table = $this->getTable();

        $script .= "
    /**
     * Makes a copy of this object that will be inserted as a new row in table when saved.
     * It creates a new object filling in the simple attributes, but skipping any primary
     * keys that are defined for the table.
     *
     * If desired, this method can also make copies of all associated (fkey referrers)
     * objects.
     *
     * @param      boolean \$deepCopy Whether to also copy all rows that refer (by fkey) to the current row.
     * @return     ".$table->getPhpName()." Clone of current object.
     * @throws     PropelException
     */
    public function copy(\$deepCopy = false)
    {
        // we use get_class(), because this might be a subclass
        \$clazz = get_class(\$this);
        \$copyObj = new \$clazz();
        \$this->copyInto(\$copyObj, \$deepCopy);
        return \$copyObj;
    }
";
    } // addCopy()

    /**
     * Adds the copyInto() method, which takes an object and sets contents to match current object.
     * In complex OM this method includes the $deepCopy param for making copies of related objects.
     * @param      string &$script The script will be modified in this method.
     */
    protected function addCopyInto(&$script)
    {
        $table = $this->getTable();

        $script .= "
    /**
     * Sets contents of passed object to values from current object.
     *
     * If desired, this method can also make copies of all associated (fkey referrers)
     * objects.
     *
     * @param      object \$copyObj An object of ".$table->getPhpName()." (or compatible) type.
     * @param      boolean \$deepCopy Whether to also copy all rows that refer (by fkey) to the current row.
     * @throws     PropelException
     */
    public function copyInto(\$copyObj, \$deepCopy = false)
    {
";

        $pkcols = array();
        foreach ($table->getColumns() as $pkcol) {
            if ($pkcol->isPrimaryKey()) {
                $pkcols[] = $pkcol->getName();
            }
        }

        foreach ($table->getColumns() as $col) {
            if (!in_array($col->getName(), $pkcols)) {
                $script .= "
        \$copyObj->set".$col->getPhpName()."(\$this->".strtolower($col->getName()).");
";
            }
        } // foreach

        // Avoid useless code by checking to see if there are any referrers
        // to this table:
        if (count($table->getReferrers()) > 0) {
            $script .= "

        if (\$deepCopy) {
            // important: temporarily setNew(false) because this affects the behavior of
            // the getter/setter methods for fkey referrer objects.
            \$copyObj->setNew(false);
";
            foreach ($table->getReferrers() as $fk) {
                // Continue if $this and $copyObj are the same class and have the same primary key
                // to avoid endless loops
                $script .= "
            foreach(\$this->get".$this->getRefFKPhpNameAffix($fk, true)."() as \$relObj) {";
                if ($table->getName() === $fk->getTableName()) {
                    $script .= "
                if(\$this->getPrimaryKey() === \$relObj->getPrimaryKey()) {
                        continue;
                }
";
                }
                $script .= "
                \$copyObj->add".$this->getRefFKPhpNameAffix($fk)."(\$relObj->copy(\$deepCopy));
            }
";
                // HL: commenting out close of self-referential check
                // } /* if tblFK != table */
            } /* foreach */
            $script .= "
        } // if (\$deepCopy)
";
        } /* if (count referrers > 0 ) */

        $script .= "

        \$copyObj->setNew(true);
";


        foreach ($table->getColumns() as $col) {
            if ($col->isPrimaryKey()) {
                $coldefval = $col->getPhpDefaultValue();
                $coldefval = var_export($coldefval, true);
                $script .= "
        \$copyObj->set".$col->getPhpName() ."($coldefval); // this is a pkey column, so set to default value
";
            } // if col->isPrimaryKey
        } // foreach
        $script .= "
    }
";
    } // addCopyInto()

} // PHP5ComplexObjectBuilder
