* @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