Dbp.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Data\Tree;
  7. use Magento\Framework\DB\Select;
  8. /**
  9. * Data DB tree
  10. *
  11. * Data model:
  12. * id | path | order
  13. *
  14. * @author Magento Core Team <core@magentocommerce.com>
  15. */
  16. class Dbp extends \Magento\Framework\Data\Tree
  17. {
  18. const ID_FIELD = 'id';
  19. const PATH_FIELD = 'path';
  20. const ORDER_FIELD = 'order';
  21. const LEVEL_FIELD = 'level';
  22. /**
  23. * DB connection
  24. *
  25. * @var \Magento\Framework\DB\Adapter\AdapterInterface
  26. */
  27. protected $_conn;
  28. /**
  29. * Data table name
  30. *
  31. * @var string
  32. */
  33. protected $_table;
  34. /**
  35. * Indicates if loaded
  36. *
  37. * @var bool
  38. */
  39. protected $_loaded = false;
  40. /**
  41. * SQL select object
  42. *
  43. * @var \Magento\Framework\DB\Select
  44. */
  45. protected $_select;
  46. /**
  47. * Tree structure field: id
  48. *
  49. * @var string
  50. */
  51. protected $_idField;
  52. /**
  53. * Tree structure field: path
  54. *
  55. * @var string
  56. */
  57. protected $_pathField;
  58. /**
  59. * Tree structure field: order
  60. *
  61. * @var string
  62. */
  63. protected $_orderField;
  64. /**
  65. * Tree structure field: level
  66. *
  67. * @var string
  68. */
  69. protected $_levelField;
  70. /**
  71. * Db tree constructor
  72. *
  73. * $fields = array(
  74. * \Magento\Framework\Data\Tree\Dbp::ID_FIELD => string,
  75. * \Magento\Framework\Data\Tree\Dbp::PATH_FIELD => string,
  76. * \Magento\Framework\Data\Tree\Dbp::ORDER_FIELD => string
  77. * \Magento\Framework\Data\Tree\Dbp::LEVEL_FIELD => string
  78. * )
  79. *
  80. * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
  81. * @param string $table
  82. * @param array $fields
  83. * @throws \Exception
  84. */
  85. public function __construct(\Magento\Framework\DB\Adapter\AdapterInterface $connection, $table, $fields)
  86. {
  87. parent::__construct();
  88. if (!$connection) {
  89. throw new \Exception('Wrong "$connection" parametr');
  90. }
  91. $this->_conn = $connection;
  92. $this->_table = $table;
  93. if (!isset(
  94. $fields[self::ID_FIELD]
  95. ) || !isset(
  96. $fields[self::PATH_FIELD]
  97. ) || !isset(
  98. $fields[self::LEVEL_FIELD]
  99. ) || !isset(
  100. $fields[self::ORDER_FIELD]
  101. )
  102. ) {
  103. throw new \Exception('"$fields" tree configuratin array');
  104. }
  105. $this->_idField = $fields[self::ID_FIELD];
  106. $this->_pathField = $fields[self::PATH_FIELD];
  107. $this->_orderField = $fields[self::ORDER_FIELD];
  108. $this->_levelField = $fields[self::LEVEL_FIELD];
  109. $this->_select = $this->_conn->select();
  110. $this->_select->from($this->_table);
  111. }
  112. /**
  113. * Retrieve current select object
  114. *
  115. * @return Select
  116. */
  117. public function getDbSelect()
  118. {
  119. return $this->_select;
  120. }
  121. /**
  122. * Set Select object
  123. *
  124. * @param Select $select
  125. * @return void
  126. */
  127. public function setDbSelect($select)
  128. {
  129. $this->_select = $select;
  130. }
  131. /**
  132. * Load tree
  133. *
  134. * @param int|Node|string $parentNode
  135. * @param int $recursionLevel
  136. * @return $this
  137. */
  138. public function load($parentNode = null, $recursionLevel = 0)
  139. {
  140. if (!$this->_loaded) {
  141. $startLevel = 1;
  142. $parentPath = '';
  143. if ($parentNode instanceof Node) {
  144. $parentPath = $parentNode->getData($this->_pathField);
  145. $startLevel = $parentNode->getData($this->_levelField);
  146. } elseif (is_numeric($parentNode)) {
  147. $select = $this->_conn->select()
  148. ->from($this->_table, [$this->_pathField, $this->_levelField])
  149. ->where("{$this->_idField} = ?", $parentNode);
  150. $parent = $this->_conn->fetchRow($select);
  151. $startLevel = $parent[$this->_levelField];
  152. $parentPath = $parent[$this->_pathField];
  153. $parentNode = null;
  154. } elseif (is_string($parentNode)) {
  155. $parentPath = $parentNode;
  156. $startLevel = count(explode(',', $parentPath)) - 1;
  157. $parentNode = null;
  158. }
  159. $select = clone $this->_select;
  160. $select->order($this->_table . '.' . $this->_orderField . ' ASC');
  161. if ($parentPath) {
  162. $pathField = $this->_conn->quoteIdentifier([$this->_table, $this->_pathField]);
  163. $select->where("{$pathField} LIKE ?", "{$parentPath}/%");
  164. }
  165. if ($recursionLevel != 0) {
  166. $levelField = $this->_conn->quoteIdentifier([$this->_table, $this->_levelField]);
  167. $select->where("{$levelField} <= ?", $startLevel + $recursionLevel);
  168. }
  169. $arrNodes = $this->_conn->fetchAll($select);
  170. $childrenItems = [];
  171. foreach ($arrNodes as $nodeInfo) {
  172. $pathToParent = explode('/', $nodeInfo[$this->_pathField]);
  173. array_pop($pathToParent);
  174. $pathToParent = implode('/', $pathToParent);
  175. $childrenItems[$pathToParent][] = $nodeInfo;
  176. }
  177. $this->addChildNodes($childrenItems, $parentPath, $parentNode);
  178. $this->_loaded = true;
  179. }
  180. return $this;
  181. }
  182. /**
  183. * Add child nodes
  184. *
  185. * @param array $children
  186. * @param string $path
  187. * @param Node $parentNode
  188. * @param int $level
  189. * @return void
  190. */
  191. public function addChildNodes($children, $path, $parentNode, $level = 0)
  192. {
  193. if (isset($children[$path])) {
  194. foreach ($children[$path] as $child) {
  195. $nodeId = isset($child[$this->_idField]) ? $child[$this->_idField] : false;
  196. if ($parentNode && $nodeId && ($node = $parentNode->getChildren()->searchById($nodeId))) {
  197. $node->addData($child);
  198. } else {
  199. $node = new Node($child, $this->_idField, $this, $parentNode);
  200. }
  201. //$node->setLevel(count(explode('/', $node->getData($this->_pathField)))-1);
  202. $node->setLevel($node->getData($this->_levelField));
  203. $node->setPathId($node->getData($this->_pathField));
  204. $this->addNode($node, $parentNode);
  205. if ($path) {
  206. $childrenPath = explode('/', $path);
  207. } else {
  208. $childrenPath = [];
  209. }
  210. $childrenPath[] = $node->getId();
  211. $childrenPath = implode('/', $childrenPath);
  212. $this->addChildNodes($children, $childrenPath, $node, $level + 1);
  213. }
  214. }
  215. }
  216. /**
  217. * Load node
  218. *
  219. * @param int|string $nodeId
  220. * @return Node
  221. */
  222. public function loadNode($nodeId)
  223. {
  224. $select = clone $this->_select;
  225. if (is_numeric($nodeId)) {
  226. $condField = $this->_conn->quoteIdentifier([$this->_table, $this->_idField]);
  227. } else {
  228. $condField = $this->_conn->quoteIdentifier([$this->_table, $this->_pathField]);
  229. }
  230. $select->where("{$condField} = ?", $nodeId);
  231. $node = new Node($this->_conn->fetchRow($select), $this->_idField, $this);
  232. $this->addNode($node);
  233. return $node;
  234. }
  235. /**
  236. * Get children
  237. *
  238. * @param Node $node
  239. * @param bool $recursive
  240. * @param array $result
  241. * @return array
  242. */
  243. public function getChildren($node, $recursive = true, $result = [])
  244. {
  245. if (is_numeric($node)) {
  246. $node = $this->getNodeById($node);
  247. }
  248. if (!$node) {
  249. return $result;
  250. }
  251. foreach ($node->getChildren() as $child) {
  252. if ($recursive) {
  253. if ($child->getChildren()) {
  254. $result = $this->getChildren($child, $recursive, $result);
  255. }
  256. }
  257. $result[] = $child->getId();
  258. }
  259. return $result;
  260. }
  261. /**
  262. * Move tree node
  263. *
  264. * @param Node $node
  265. * @param Node $newParent
  266. * @param Node $prevNode
  267. * @return void
  268. * @throws \Exception
  269. * @todo Use adapter for generate conditions
  270. */
  271. public function move($node, $newParent, $prevNode = null)
  272. {
  273. $position = 1;
  274. $oldPath = $node->getData($this->_pathField);
  275. $newPath = $newParent->getData($this->_pathField);
  276. $newPath = $newPath . '/' . $node->getId();
  277. $oldPathLength = strlen($oldPath);
  278. $newLevel = $newParent->getLevel() + 1;
  279. $levelDisposition = $newLevel - $node->getLevel();
  280. $data = [
  281. $this->_levelField => new \Zend_Db_Expr("{$this->_levelField} + '{$levelDisposition}'"),
  282. $this->_pathField => new \Zend_Db_Expr(
  283. "CONCAT('{$newPath}', RIGHT({$this->_pathField}, LENGTH({$this->_pathField}) - {$oldPathLength}))"
  284. ),
  285. ];
  286. $condition = $this->_conn->quoteInto("{$this->_pathField} REGEXP ?", "^{$oldPath}(/|\$)");
  287. $this->_conn->beginTransaction();
  288. $reorderData = [$this->_orderField => new \Zend_Db_Expr("{$this->_orderField} + 1")];
  289. try {
  290. if ($prevNode && $prevNode->getId()) {
  291. $reorderCondition = "{$this->_orderField} > {$prevNode->getData($this->_orderField)}";
  292. $position = $prevNode->getData($this->_orderField) + 1;
  293. } else {
  294. $reorderCondition = $this->_conn->quoteInto(
  295. "{$this->_pathField} REGEXP ?",
  296. "^{$newParent->getData($this->_pathField)}/[0-9]+\$"
  297. );
  298. $select = $this->_conn->select()->from(
  299. $this->_table,
  300. new \Zend_Db_Expr("MIN({$this->_orderField})")
  301. )->where(
  302. $reorderCondition
  303. );
  304. $position = (int)$this->_conn->fetchOne($select);
  305. }
  306. $this->_conn->update($this->_table, $reorderData, $reorderCondition);
  307. $this->_conn->update($this->_table, $data, $condition);
  308. $this->_conn->update(
  309. $this->_table,
  310. [$this->_orderField => $position, $this->_levelField => $newLevel],
  311. $this->_conn->quoteInto("{$this->_idField} = ?", $node->getId())
  312. );
  313. $this->_conn->commit();
  314. } catch (\Exception $e) {
  315. $this->_conn->rollBack();
  316. throw new \Exception("Can't move tree node due to error: " . $e->getMessage());
  317. }
  318. }
  319. /**
  320. * Load ensured nodes
  321. *
  322. * @param object $category
  323. * @param Node $rootNode
  324. * @return void
  325. */
  326. public function loadEnsuredNodes($category, $rootNode)
  327. {
  328. $pathIds = $category->getPathIds();
  329. $rootNodeId = $rootNode->getId();
  330. $rootNodePath = $rootNode->getData($this->_pathField);
  331. $select = clone $this->_select;
  332. $select->order($this->_table . '.' . $this->_orderField . ' ASC');
  333. if ($pathIds) {
  334. $condition = $this->_conn->quoteInto("{$this->_table}.{$this->_idField} in (?)", $pathIds);
  335. $select->where($condition);
  336. }
  337. $arrNodes = $this->_conn->fetchAll($select);
  338. if ($arrNodes) {
  339. $childrenItems = [];
  340. foreach ($arrNodes as $nodeInfo) {
  341. $nodeId = $nodeInfo[$this->_idField];
  342. if ($nodeId <= $rootNodeId) {
  343. continue;
  344. }
  345. $pathToParent = explode('/', $nodeInfo[$this->_pathField]);
  346. array_pop($pathToParent);
  347. $pathToParent = implode('/', $pathToParent);
  348. $childrenItems[$pathToParent][] = $nodeInfo;
  349. }
  350. $this->_addChildNodes($childrenItems, $rootNodePath, $rootNode, true);
  351. }
  352. }
  353. /**
  354. * Add child nodes
  355. *
  356. * @param array $children
  357. * @param string $path
  358. * @param Node $parentNode
  359. * @param bool $withChildren
  360. * @param int $level
  361. * @return void
  362. */
  363. protected function _addChildNodes($children, $path, $parentNode, $withChildren = false, $level = 0)
  364. {
  365. if (isset($children[$path])) {
  366. foreach ($children[$path] as $child) {
  367. $nodeId = isset($child[$this->_idField]) ? $child[$this->_idField] : false;
  368. if ($parentNode && $nodeId && ($node = $parentNode->getChildren()->searchById($nodeId))) {
  369. $node->addData($child);
  370. } else {
  371. $node = new Node($child, $this->_idField, $this, $parentNode);
  372. $node->setLevel($node->getData($this->_levelField));
  373. $node->setPathId($node->getData($this->_pathField));
  374. $this->addNode($node, $parentNode);
  375. }
  376. if ($withChildren) {
  377. $this->_loaded = false;
  378. $node->loadChildren(1);
  379. $this->_loaded = false;
  380. }
  381. if ($path) {
  382. $childrenPath = explode('/', $path);
  383. } else {
  384. $childrenPath = [];
  385. }
  386. $childrenPath[] = $node->getId();
  387. $childrenPath = implode('/', $childrenPath);
  388. $this->_addChildNodes($children, $childrenPath, $node, $withChildren, $level + 1);
  389. }
  390. }
  391. }
  392. }