* @package propel.generator.builder.om
*/
class PHP5NodePeerBuilder extends PeerBuilder
{
/**
* Gets the package for the [base] object classes.
* @return string
*/
public function getPackage()
{
return parent::getPackage() . ".om";
}
/**
* Returns the name of the current class being built.
* @return string
*/
public function getUnprefixedClassname()
{
return $this->getBuildProperty('basePrefix') . $this->getStubNodePeerBuilder()->getUnprefixedClassname();
}
/**
* Adds the include() statements for files that this class depends on or utilizes.
* @param string &$script The script will be modified in this method.
*/
protected function addIncludes(&$script)
{
} // addIncludes()
/**
* Adds class phpdoc comment and openning of class.
* @param string &$script The script will be modified in this method.
*/
protected function addClassOpen(&$script)
{
$table = $this->getTable();
$tableName = $table->getName();
$tableDesc = $table->getDescription();
$script .= "
/**
* Base static class for performing query operations on the tree contained by the '$tableName' table.
*
* $tableDesc
*";
if ($this->getBuildProperty('addTimeStamp')) {
$now = strftime('%c');
$script .= "
* This class was autogenerated by Propel " . $this->getBuildProperty('version') . " on:
*
* $now
*";
}
$script .= "
* @package propel.generator.".$this->getPackage()."
*/
abstract class ".$this->getClassname()." {
";
}
/**
* 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.
* @see ObjectBuilder::addClassBody()
*/
protected function addClassBody(&$script)
{
$table = $this->getTable();
// FIXME
// - Probably the build needs to be customized for supporting
// tables that are "aliases". -- definitely a fringe usecase, though.
$this->addConstants($script);
$this->addIsCodeBase($script);
$this->addRetrieveMethods($script);
$this->addCreateNewRootNode($script);
$this->addInsertNewRootNode($script);
$this->addMoveNodeSubTree($script);
$this->addDeleteNodeSubTree($script);
$this->addBuildFamilyCriteria($script);
$this->addBuildTree($script);
$this->addPopulateNodes($script);
}
/**
* Closes class.
* @param string &$script The script will be modified in this method.
*/
protected function addClassClose(&$script)
{
$script .= "
} // " . $this->getClassname() . "
";
}
protected function addConstants(&$script)
{
$table = $this->getTable();
$npath_colname = '';
$npath_phpname = '';
$npath_len = 0;
$npath_sep = '';
foreach ($table->getColumns() as $col) {
if ($col->isNodeKey()) {
$npath_colname = $table->getName() . '.' . strtoupper($col->getName());
$npath_phpname = $col->getPhpName();
$npath_len = $col->getSize();
$npath_sep = $col->getNodeKeySep();
break;
}
}
$script .= "
const NPATH_COLNAME = '$npath_colname';
const NPATH_PHPNAME = '$npath_phpname';
const NPATH_SEP = '$npath_sep';
const NPATH_LEN = $npath_len;
";
}
protected function addIsCodeBase(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$script .= "
/**
* Temp function for CodeBase hacks that will go away.
*/
public static function isCodeBase(\$con = null)
{
if (\$con === null)
\$con = Propel::getConnection($peerClassname::DATABASE_NAME);
return (get_class(\$con) == 'ODBCConnection' &&
get_class(\$con->getAdapter()) == 'CodeBaseAdapter');
}
";
}
protected function addCreateNewRootNode(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Create a new Node at the top of tree. This method will destroy any
* existing root node (along with its children).
*
* Use at your own risk!
*
* @param $objectClassname Object wrapped by new node.
* @param PropelPDO Connection to use.
* @return $nodeObjectClassname
* @throws PropelException
*/
public static function createNewRootNode(\$obj, PropelPDO \$con = null)
{
if (\$con === null)
\$con = Propel::getConnection($peerClassname::DATABASE_NAME, Propel::CONNECTION_WRITE);
\$con->beginTransaction();
try {
self::deleteNodeSubTree('1', \$con);
\$setNodePath = 'set' . self::NPATH_PHPNAME;
\$obj->\$setNodePath('1');
\$obj->save(\$con);
\$con->commit();
} catch (PropelException \$e) {
\$con->rollBack();
throw \$e;
}
return new $nodeObjectClassname(\$obj);
}
";
}
protected function addInsertNewRootNode(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Inserts a new Node at the top of tree. Any existing root node (along with
* its children) will be made a child of the new root node. This is a
* safer alternative to createNewRootNode().
*
* @param $objectClassname Object wrapped by new node.
* @param PropelPDO Connection to use.
* @return $nodeObjectClassname
* @throws PropelException
*/
public static function insertNewRootNode(\$obj, PropelPDO \$con = null)
{
if (\$con === null)
\$con = Propel::getConnection($peerClassname::DATABASE_NAME, Propel::CONNECTION_WRITE);
\$con->beginTransaction();
try {
// Move root tree to an invalid node path.
$nodePeerClassname::moveNodeSubTree('1', '0', \$con);
\$setNodePath = 'set' . self::NPATH_PHPNAME;
// Insert the new root node.
\$obj->\$setNodePath('1');
\$obj->save(\$con);
// Move the old root tree as a child of the new root.
$nodePeerClassname::moveNodeSubTree('0', '1' . self::NPATH_SEP . '1', \$con);
\$con->commit();
} catch (PropelException \$e) {
\$con->rollBack();
throw \$e;
}
return new $nodeObjectClassname(\$obj);
}
";
}
/**
* Adds the methods for retrieving nodes.
*/
protected function addRetrieveMethods(&$script)
{
$this->addRetrieveNodes($script);
$this->addRetrieveNodeByPK($script);
$this->addRetrieveNodeByNP($script);
$this->addRetrieveRootNode($script);
}
protected function addRetrieveNodes(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$script .= "
/**
* Retrieves an array of tree nodes based on specified criteria. Optionally
* includes all parent and/or child nodes of the matching nodes.
*
* @param Criteria Criteria to use.
* @param boolean True if ancestors should also be retrieved.
* @param boolean True if descendants should also be retrieved.
* @param PropelPDO Connection to use.
* @return array Array of root nodes.
*/
public static function retrieveNodes(\$criteria, \$ancestors = false, \$descendants = false, PropelPDO \$con = null)
{
\$criteria = $nodePeerClassname::buildFamilyCriteria(\$criteria, \$ancestors, \$descendants);
\$stmt = ".$this->getStubPeerBuilder()->getClassname()."::doSelectStmt(\$criteria, \$con);
return self::populateNodes(\$stmt, \$criteria);
}
";
}
protected function addRetrieveNodeByPK(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Retrieves a tree node based on a primary key. Optionally includes all
* parent and/or child nodes of the matching node.
*
* @param mixed $objectClassname primary key (array for composite keys)
* @param boolean True if ancestors should also be retrieved.
* @param boolean True if descendants should also be retrieved.
* @param PropelPDO Connection to use.
* @return $nodeObjectClassname
*/
public static function retrieveNodeByPK(\$pk, \$ancestors = false, \$descendants = false, PropelPDO \$con = null)
{
throw new PropelException('retrieveNodeByPK() not implemented yet.');
}
";
}
protected function addRetrieveNodeByNP(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Retrieves a tree node based on a node path. Optionally includes all
* parent and/or child nodes of the matching node.
*
* @param string Node path to retrieve.
* @param boolean True if ancestors should also be retrieved.
* @param boolean True if descendants should also be retrieved.
* @param PropelPDO Connection to use.
* @return $objectClassname
*/
public static function retrieveNodeByNP(\$np, \$ancestors = false, \$descendants = false, PropelPDO \$con = null)
{
\$criteria = new Criteria($peerClassname::DATABASE_NAME);
\$criteria->add(self::NPATH_COLNAME, \$np, Criteria::EQUAL);
\$criteria = self::buildFamilyCriteria(\$criteria, \$ancestors, \$descendants);
\$stmt = $peerClassname::doSelectStmt(\$criteria, \$con);
\$nodes = self::populateNodes(\$stmt, \$criteria);
return (count(\$nodes) == 1 ? \$nodes[0] : null);
}
";
}
protected function addRetrieveRootNode(&$script)
{
$script .= "
/**
* Retrieves the root node.
*
* @param string Node path to retrieve.
* @param boolean True if descendants should also be retrieved.
* @param PropelPDO Connection to use.
* @return ".$this->getStubNodeBuilder()->getClassname()."
*/
public static function retrieveRootNode(\$descendants = false, PropelPDO \$con = null)
{
return self::retrieveNodeByNP('1', false, \$descendants, \$con);
}
";
}
protected function addMoveNodeSubTree(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Moves the node subtree at srcpath to the dstpath. This method is intended
* for internal use by the BaseNode object. Note that it does not check for
* preexisting nodes at the dstpath. It also does not update the node path
* of any Node objects that might currently be in memory.
*
* Use at your own risk!
*
* @param string Source node path to move (root of the src subtree).
* @param string Destination node path to move to (root of the dst subtree).
* @param PropelPDO Connection to use.
* @return void
* @throws PropelException
* @todo This is currently broken for simulated 'onCascadeDelete's.
* @todo Need to abstract the SQL better. The CONCAT sql function doesn't
* seem to be standardized (i.e. mssql), so maybe it needs to be moved
* to DBAdapter.
*/
public static function moveNodeSubTree(\$srcPath, \$dstPath, PropelPDO \$con = null)
{
if (substr(\$dstPath, 0, strlen(\$srcPath)) == \$srcPath)
throw new PropelException('Cannot move a node subtree within itself.');
if (\$con === null)
\$con = Propel::getConnection($peerClassname::DATABASE_NAME, Propel::CONNECTION_WRITE);
/**
* Example:
* UPDATE table
* SET npath = CONCAT('1.3', SUBSTRING(npath, 6, 74))
* WHERE npath = '1.2.2' OR npath LIKE '1.2.2.%'
*/
\$npath = $nodePeerClassname::NPATH_COLNAME;
//the following dot isn`t mean`t a nodeKeySeperator
\$setcol = substr(\$npath, strrpos(\$npath, '.')+1);
\$setcollen = $nodePeerClassname::NPATH_LEN;
\$db = Propel::getDb($peerClassname::DATABASE_NAME);
//
if ($nodePeerClassname::isCodeBase(\$con))
{
// This is a hack to get CodeBase working. It will eventually be removed.
// It is a workaround for the following CodeBase bug:
// -Prepared statement parameters cannot be embedded in SQL functions (i.e. CONCAT)
\$sql = \"UPDATE \" . $peerClassname::TABLE_NAME . \" \" .
\"SET \$setcol=\" . \$db->concatString(\"'\$dstPath'\", \$db->subString(\$npath, strlen(\$srcPath)+1, \$setcollen)) . \" \" .
\"WHERE \$npath = '\$srcPath' OR \$npath LIKE '\" . \$srcPath . $nodePeerClassname::NPATH_SEP . \"%'\";
\$con->executeUpdate(\$sql);
}
else
{
//
\$sql = \"UPDATE \" . $peerClassname::TABLE_NAME . \" \" .
\"SET \$setcol=\" . \$db->concatString('?', \$db->subString(\$npath, '?', '?')) . \" \" .
\"WHERE \$npath = ? OR \$npath LIKE ?\";
\$stmt = \$con->prepare(\$sql);
\$stmt->bindValue(1, \$dstPath); // string
\$srcPathPlus1 = strlen(\$srcPath)+1;
\$stmt->bindValue(2, \$srcPathPlus1); // int
\$stmt->bindValue(3, \$setcollen);// int
\$stmt->bindValue(4, \$srcPath);// string
\$srcPathWC = \$srcPath . $nodePeerClassname::NPATH_SEP . '%';
\$stmt->bindValue(5, \$srcPathWC); // string
\$stmt->execute();
//
}
}
";
}
protected function addDeleteNodeSubTree(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Deletes the node subtree at the specified node path from the database.
*
* @param string Node path to delete
* @param PropelPDO Connection to use.
* @return void
* @throws PropelException
* @todo This is currently broken for simulated 'onCascadeDelete's.
*/
public static function deleteNodeSubTree(\$nodePath, PropelPDO \$con = null)
{
if (\$con === null)
\$con = Propel::getConnection($peerClassname::DATABASE_NAME, Propel::CONNECTION_WRITE);
/**
* DELETE FROM table
* WHERE npath = '1.2.2' OR npath LIKE '1.2.2.%'
*/
\$criteria = new Criteria($peerClassname::DATABASE_NAME);
\$criteria->add($nodePeerClassname::NPATH_COLNAME, \$nodePath, Criteria::EQUAL);
\$criteria->addOr($nodePeerClassname::NPATH_COLNAME, \$nodePath . self::NPATH_SEP . '%', Criteria::LIKE);
{$this->basePeerClassname}::doDelete(\$criteria, \$con);
}
";
}
protected function addBuildFamilyCriteria(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Builds the criteria needed to retrieve node ancestors and/or descendants.
*
* @param Criteria Criteria to start with
* @param boolean True if ancestors should be retrieved.
* @param boolean True if descendants should be retrieved.
* @return Criteria
*/
public static function buildFamilyCriteria(\$criteria, \$ancestors = false, \$descendants = false)
{
/*
Example SQL to retrieve nodepath '1.2.3' with both ancestors and descendants:
SELECT L.NPATH, L.LABEL, test.NPATH, UCASE(L.NPATH)
FROM test L, test
WHERE test.NPATH='1.2.3' AND
(L.NPATH=SUBSTRING(test.NPATH, 1, LENGTH(L.NPATH)) OR
test.NPATH=SUBSTRING(L.NPATH, 1, LENGTH(test.NPATH)))
ORDER BY UCASE(L.NPATH) ASC
*/
if (\$criteria === null)
\$criteria = new Criteria($peerClassname::DATABASE_NAME);
if (!\$criteria->getSelectColumns())
$peerClassname::addSelectColumns(\$criteria);
\$db = Propel::getDb(\$criteria->getDbName());
if ((\$ancestors || \$descendants) && \$criteria->size())
{
// If we are retrieving ancestors/descendants, we need to do a
// self-join to locate them. The exception to this is if no search
// criteria is specified. In this case we're retrieving all nodes
// anyway, so there is no need to do a self-join.
// The left-side of the self-join will contain the columns we'll
// use to build node objects (target node records along with their
// ancestors and/or descendants). The right-side of the join will
// contain the target node records specified by the initial criteria.
// These are used to match the appropriate ancestor/descendant on
// the left.
// Specify an alias for the left-side table to use.
\$criteria->addAlias('L', $peerClassname::TABLE_NAME);
// Make sure we have select columns to begin with.
if (!\$criteria->getSelectColumns())
$peerClassname::addSelectColumns(\$criteria);
// Replace any existing columns for the right-side table with the
// left-side alias.
\$selectColumns = \$criteria->getSelectColumns();
\$criteria->clearSelectColumns();
foreach (\$selectColumns as \$colName)
\$criteria->addSelectColumn(str_replace($peerClassname::TABLE_NAME, 'L', \$colName));
\$a = null;
\$d = null;
\$npathL = $peerClassname::alias('L', $nodePeerClassname::NPATH_COLNAME);
\$npathR = $nodePeerClassname::NPATH_COLNAME;
\$npath_len = $nodePeerClassname::NPATH_LEN;
if (\$ancestors)
{
// For ancestors, match left-side node paths which are contained
// by right-side node paths.
\$a = \$criteria->getNewCriterion(\$npathL,
\"\$npathL=\" . \$db->subString(\$npathR, 1, \$db->strLength(\$npathL), \$npath_len),
Criteria::CUSTOM);
}
if (\$descendants)
{
// For descendants, match left-side node paths which contain
// right-side node paths.
\$d = \$criteria->getNewCriterion(\$npathR,
\"\$npathR=\" . \$db->subString(\$npathL, 1, \$db->strLength(\$npathR), \$npath_len),
Criteria::CUSTOM);
}
if (\$a)
{
if (\$d) \$a->addOr(\$d);
\$criteria->addAnd(\$a);
}
else if (\$d)
{
\$criteria->addAnd(\$d);
}
// Add the target node path column. This is used by populateNodes().
\$criteria->addSelectColumn(\$npathR);
// Sort by node path to speed up tree construction in populateNodes()
\$criteria->addAsColumn('npathlen', \$db->strLength(\$npathL));
\$criteria->addAscendingOrderByColumn('npathlen');
\$criteria->addAscendingOrderByColumn(\$npathL);
}
else
{
// Add the target node path column. This is used by populateNodes().
\$criteria->addSelectColumn($nodePeerClassname::NPATH_COLNAME);
// Sort by node path to speed up tree construction in populateNodes()
\$criteria->addAsColumn('npathlen', \$db->strLength($nodePeerClassname::NPATH_COLNAME));
\$criteria->addAscendingOrderByColumn('npathlen');
\$criteria->addAscendingOrderByColumn($nodePeerClassname::NPATH_COLNAME);
}
return \$criteria;
}
";
}
protected function addBuildTree(&$script)
{
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* This method reconstructs as much of the tree structure as possible from
* the given array of objects. Depending on how you execute your query, it
* is possible for the ResultSet to contain multiple tree fragments (i.e.
* subtrees). The array returned by this method will contain one entry
* for each subtree root node it finds. The remaining subtree nodes are
* accessible from the $nodeObjectClassname methods of the
* subtree root nodes.
*
* @param array Array of $nodeObjectClassname objects
* @return array Array of $nodeObjectClassname objects
*/
public static function buildTree(\$nodes)
{
// Subtree root nodes to return
\$rootNodes = array();
// Build the tree relations
foreach (\$nodes as \$node)
{
\$sep = strrpos(\$node->getNodePath(), $nodePeerClassname::NPATH_SEP);
\$parentPath = (\$sep !== false ? substr(\$node->getNodePath(), 0, \$sep) : '');
\$parentNode = null;
// Scan other nodes for parent.
foreach (\$nodes as \$pnode)
{
if (\$pnode->getNodePath() === \$parentPath)
{
\$parentNode = \$pnode;
break;
}
}
// If parent was found, attach as child, otherwise its a subtree root
if (\$parentNode)
\$parentNode->attachChildNode(\$node);
else
\$rootNodes[] = \$node;
}
return \$rootNodes;
}
";
}
protected function addPopulateNodes(&$script)
{
$table = $this->getTable();
$peerClassname = $this->getStubPeerBuilder()->getClassname();
$objectClassname = $this->getStubObjectBuilder()->getClassname();
$nodePeerClassname = $this->getStubNodePeerBuilder()->getClassname();
$nodeObjectClassname = $this->getStubNodeBuilder()->getClassname();
$script .= "
/**
* Populates the $objectClassname objects from the
* specified ResultSet, wraps them in $nodeObjectClassname
* objects and build the appropriate node relationships.
* The array returned by this method will only include the initial targets
* of the query, even if ancestors/descendants were also requested.
* The ancestors/descendants will be cached in memory and are accessible via
* the getNode() methods.
*
* @param PDOStatement \$stmt Executed PDOStatement
* @param Criteria
* @return array Array of $nodeObjectClassname objects.
*/
public static function populateNodes(PDOStatement \$stmt, \$criteria)
{
\$nodes = array();
\$targets = array();
\$targetfld = count(\$criteria->getSelectColumns());
";
if (!$table->getChildrenColumn()) {
$script .= "
// set the class once to avoid overhead in the loop
\$cls = $peerClassname::getOMClass();
\$cls = substr('.'.\$cls, strrpos('.'.\$cls, '.') + 1);
";
}
$script .= "
// populate the object(s)
foreach(\$stmt->fetchAll() AS \$row)
{
if (!isset(\$nodes[\$row[0]]))
{
";
if ($table->getChildrenColumn()) {
$script .= "
// class must be set each time from the record row
\$cls = $peerClassname::getOMClass(\$row, 1);
\$cls = substr('.'.\$cls, strrpos('.'.\$cls, '.') + 1);
";
}
$script .= "
" . $this->buildObjectInstanceCreationCode('$obj', '$cls') . "
\$obj->hydrate(\$row);
\$nodes[\$row[0]] = new $nodeObjectClassname(\$obj);
}
\$node = \$nodes[\$row[0]];
if (\$node->getNodePath() === \$row[\$targetfld])
\$targets[\$node->getNodePath()] = \$node;
}
$nodePeerClassname::buildTree(\$nodes);
return array_values(\$targets);
}
";
}
} // PHP5NodePeerBuilder