Collection.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Data;
  7. use Magento\Framework\Data\Collection\EntityFactoryInterface;
  8. use Magento\Framework\Option\ArrayInterface;
  9. /**
  10. * Data collection
  11. *
  12. * TODO: Refactor use of \Magento\Framework\Option\ArrayInterface in library.
  13. *
  14. * @api
  15. * @since 100.0.2
  16. */
  17. class Collection implements \IteratorAggregate, \Countable, ArrayInterface, CollectionDataSourceInterface
  18. {
  19. const SORT_ORDER_ASC = 'ASC';
  20. const SORT_ORDER_DESC = 'DESC';
  21. /**
  22. * Collection items
  23. *
  24. * @var \Magento\Framework\DataObject[]
  25. */
  26. protected $_items = [];
  27. /**
  28. * Item object class name
  29. *
  30. * @var string
  31. */
  32. protected $_itemObjectClass = \Magento\Framework\DataObject::class;
  33. /**
  34. * Order configuration
  35. *
  36. * @var array
  37. */
  38. protected $_orders = [];
  39. /**
  40. * Filters configuration
  41. *
  42. * @var \Magento\Framework\DataObject[]
  43. */
  44. protected $_filters = [];
  45. /**
  46. * Filter rendered flag
  47. *
  48. * @var bool
  49. */
  50. protected $_isFiltersRendered = false;
  51. /**
  52. * Current page number for items pager
  53. *
  54. * @var int
  55. */
  56. protected $_curPage = 1;
  57. /**
  58. * Pager page size
  59. *
  60. * if page size is false, then we works with all items
  61. *
  62. * @var int|false
  63. */
  64. protected $_pageSize = false;
  65. /**
  66. * Total items number
  67. *
  68. * @var int
  69. */
  70. protected $_totalRecords;
  71. /**
  72. * Loading state flag
  73. *
  74. * @var bool
  75. */
  76. protected $_isCollectionLoaded;
  77. /**
  78. * Additional collection flags
  79. *
  80. * @var array
  81. */
  82. protected $_flags = [];
  83. /**
  84. * @var EntityFactoryInterface
  85. */
  86. protected $_entityFactory;
  87. /**
  88. * @param EntityFactoryInterface $entityFactory
  89. */
  90. public function __construct(EntityFactoryInterface $entityFactory)
  91. {
  92. $this->_entityFactory = $entityFactory;
  93. }
  94. /**
  95. * Add collection filter
  96. *
  97. * @param string $field
  98. * @param string $value
  99. * @param string $type and|or|string
  100. * @return $this
  101. */
  102. public function addFilter($field, $value, $type = 'and')
  103. {
  104. $filter = new \Magento\Framework\DataObject();
  105. // implements ArrayAccess
  106. $filter['field'] = $field;
  107. $filter['value'] = $value;
  108. $filter['type'] = strtolower($type);
  109. $this->_filters[] = $filter;
  110. $this->_isFiltersRendered = false;
  111. return $this;
  112. }
  113. /**
  114. * Add field filter to collection
  115. *
  116. * If $condition integer or string - exact value will be filtered ('eq' condition)
  117. *
  118. * If $condition is array - one of the following structures is expected:
  119. * <pre>
  120. * - ["from" => $fromValue, "to" => $toValue]
  121. * - ["eq" => $equalValue]
  122. * - ["neq" => $notEqualValue]
  123. * - ["like" => $likeValue]
  124. * - ["in" => [$inValues]]
  125. * - ["nin" => [$notInValues]]
  126. * - ["notnull" => $valueIsNotNull]
  127. * - ["null" => $valueIsNull]
  128. * - ["moreq" => $moreOrEqualValue]
  129. * - ["gt" => $greaterValue]
  130. * - ["lt" => $lessValue]
  131. * - ["gteq" => $greaterOrEqualValue]
  132. * - ["lteq" => $lessOrEqualValue]
  133. * - ["finset" => $valueInSet]
  134. * </pre>
  135. *
  136. * If non matched - sequential parallel arrays are expected and OR conditions
  137. * will be built using above mentioned structure.
  138. *
  139. * Example:
  140. * <pre>
  141. * $field = ['age', 'name'];
  142. * $condition = [42, ['like' => 'Mage']];
  143. * </pre>
  144. * The above would find where age equal to 42 OR name like %Mage%.
  145. *
  146. * @param string|array $field
  147. * @param string|int|array $condition
  148. * @throws \Magento\Framework\Exception\LocalizedException if some error in the input could be detected.
  149. * @return $this
  150. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  151. */
  152. public function addFieldToFilter($field, $condition)
  153. {
  154. throw new \Magento\Framework\Exception\LocalizedException(new \Magento\Framework\Phrase('Not implemented'));
  155. }
  156. /**
  157. * Search for a filter by specified field
  158. *
  159. * Multiple filters can be matched if an array is specified:
  160. * - 'foo' -- get the first filter with field name 'foo'
  161. * - array('foo') -- get all filters with field name 'foo'
  162. * - array('foo', 'bar') -- get all filters with field name 'foo' or 'bar'
  163. * - array() -- get all filters
  164. *
  165. * @param string|string[] $field
  166. * @return \Magento\Framework\DataObject|\Magento\Framework\DataObject[]|void
  167. */
  168. public function getFilter($field)
  169. {
  170. if (is_array($field)) {
  171. // empty array: get all filters
  172. if (empty($field)) {
  173. return $this->_filters;
  174. }
  175. // non-empty array: collect all filters that match specified field names
  176. $result = [];
  177. foreach ($this->_filters as $filter) {
  178. if (in_array($filter['field'], $field)) {
  179. $result[] = $filter;
  180. }
  181. }
  182. return $result;
  183. }
  184. // get a first filter by specified name
  185. foreach ($this->_filters as $filter) {
  186. if ($filter['field'] === $field) {
  187. return $filter;
  188. }
  189. }
  190. }
  191. /**
  192. * Retrieve collection loading status
  193. *
  194. * @return bool
  195. */
  196. public function isLoaded()
  197. {
  198. return $this->_isCollectionLoaded;
  199. }
  200. /**
  201. * Set collection loading status flag
  202. *
  203. * @param bool $flag
  204. * @return $this
  205. */
  206. protected function _setIsLoaded($flag = true)
  207. {
  208. $this->_isCollectionLoaded = $flag;
  209. return $this;
  210. }
  211. /**
  212. * Get current collection page
  213. *
  214. * @param int $displacement
  215. * @return int
  216. */
  217. public function getCurPage($displacement = 0)
  218. {
  219. if ($this->_curPage + $displacement < 1) {
  220. return 1;
  221. } elseif ($this->_curPage + $displacement > $this->getLastPageNumber()) {
  222. return $this->getLastPageNumber();
  223. } else {
  224. return $this->_curPage + $displacement;
  225. }
  226. }
  227. /**
  228. * Retrieve collection last page number
  229. *
  230. * @return int
  231. */
  232. public function getLastPageNumber()
  233. {
  234. $collectionSize = (int)$this->getSize();
  235. if (0 === $collectionSize) {
  236. return 1;
  237. } elseif ($this->_pageSize) {
  238. return ceil($collectionSize / $this->_pageSize);
  239. } else {
  240. return 1;
  241. }
  242. }
  243. /**
  244. * Retrieve collection page size
  245. *
  246. * @return int
  247. */
  248. public function getPageSize()
  249. {
  250. return $this->_pageSize;
  251. }
  252. /**
  253. * Retrieve collection all items count
  254. *
  255. * @return int
  256. */
  257. public function getSize()
  258. {
  259. $this->load();
  260. if ($this->_totalRecords === null) {
  261. $this->_totalRecords = count($this->getItems());
  262. }
  263. return (int)$this->_totalRecords;
  264. }
  265. /**
  266. * Retrieve collection first item
  267. *
  268. * @return \Magento\Framework\DataObject
  269. */
  270. public function getFirstItem()
  271. {
  272. $this->load();
  273. if (count($this->_items)) {
  274. reset($this->_items);
  275. return current($this->_items);
  276. }
  277. return $this->_entityFactory->create($this->_itemObjectClass);
  278. }
  279. /**
  280. * Retrieve collection last item
  281. *
  282. * @return \Magento\Framework\DataObject
  283. */
  284. public function getLastItem()
  285. {
  286. $this->load();
  287. if (count($this->_items)) {
  288. return end($this->_items);
  289. }
  290. return $this->_entityFactory->create($this->_itemObjectClass);
  291. }
  292. /**
  293. * Retrieve collection items
  294. *
  295. * @return \Magento\Framework\DataObject[]
  296. */
  297. public function getItems()
  298. {
  299. $this->load();
  300. return $this->_items;
  301. }
  302. /**
  303. * Retrieve field values from all items
  304. *
  305. * @param string $colName
  306. * @return array
  307. */
  308. public function getColumnValues($colName)
  309. {
  310. $this->load();
  311. $col = [];
  312. foreach ($this->getItems() as $item) {
  313. $col[] = $item->getData($colName);
  314. }
  315. return $col;
  316. }
  317. /**
  318. * Search all items by field value
  319. *
  320. * @param string $column
  321. * @param mixed $value
  322. * @return array
  323. */
  324. public function getItemsByColumnValue($column, $value)
  325. {
  326. $this->load();
  327. $res = [];
  328. foreach ($this as $item) {
  329. if ($item->getData($column) == $value) {
  330. $res[] = $item;
  331. }
  332. }
  333. return $res;
  334. }
  335. /**
  336. * Search first item by field value
  337. *
  338. * @param string $column
  339. * @param mixed $value
  340. * @return \Magento\Framework\DataObject || null
  341. */
  342. public function getItemByColumnValue($column, $value)
  343. {
  344. $this->load();
  345. foreach ($this as $item) {
  346. if ($item->getData($column) == $value) {
  347. return $item;
  348. }
  349. }
  350. return null;
  351. }
  352. /**
  353. * Adding item to item array
  354. *
  355. * @param \Magento\Framework\DataObject $item
  356. * @return $this
  357. * @throws \Exception
  358. */
  359. public function addItem(\Magento\Framework\DataObject $item)
  360. {
  361. $itemId = $this->_getItemId($item);
  362. if ($itemId !== null) {
  363. if (isset($this->_items[$itemId])) {
  364. throw new \Exception(
  365. 'Item (' . get_class($item) . ') with the same ID "' . $item->getId() . '" already exists.'
  366. );
  367. }
  368. $this->_items[$itemId] = $item;
  369. } else {
  370. $this->_addItem($item);
  371. }
  372. return $this;
  373. }
  374. /**
  375. * Add item that has no id to collection
  376. *
  377. * @param \Magento\Framework\DataObject $item
  378. * @return $this
  379. */
  380. protected function _addItem($item)
  381. {
  382. $this->_items[] = $item;
  383. return $this;
  384. }
  385. /**
  386. * Retrieve item id
  387. *
  388. * @param \Magento\Framework\DataObject $item
  389. * @return mixed
  390. */
  391. protected function _getItemId(\Magento\Framework\DataObject $item)
  392. {
  393. return $item->getId();
  394. }
  395. /**
  396. * Retrieve ids of all items
  397. *
  398. * @return array
  399. */
  400. public function getAllIds()
  401. {
  402. $ids = [];
  403. foreach ($this->getItems() as $item) {
  404. $ids[] = $this->_getItemId($item);
  405. }
  406. return $ids;
  407. }
  408. /**
  409. * Remove item from collection by item key
  410. *
  411. * @param mixed $key
  412. * @return $this
  413. */
  414. public function removeItemByKey($key)
  415. {
  416. if (isset($this->_items[$key])) {
  417. unset($this->_items[$key]);
  418. }
  419. return $this;
  420. }
  421. /**
  422. * Remove all items from collection
  423. *
  424. * @return $this
  425. */
  426. public function removeAllItems()
  427. {
  428. $this->_items = [];
  429. return $this;
  430. }
  431. /**
  432. * Clear collection
  433. *
  434. * @return $this
  435. */
  436. public function clear()
  437. {
  438. $this->_setIsLoaded(false);
  439. $this->_items = [];
  440. return $this;
  441. }
  442. /**
  443. * Walk through the collection and run model method or external callback with optional arguments
  444. *
  445. * Returns array with results of callback for each item
  446. *
  447. * @param callable $callback
  448. * @param array $args
  449. * @return array
  450. */
  451. public function walk($callback, array $args = [])
  452. {
  453. $results = [];
  454. $useItemCallback = is_string($callback) && strpos($callback, '::') === false;
  455. foreach ($this->getItems() as $id => $item) {
  456. $params = $args;
  457. if ($useItemCallback) {
  458. $cb = [$item, $callback];
  459. } else {
  460. $cb = $callback;
  461. array_unshift($params, $item);
  462. }
  463. $results[$id] = call_user_func_array($cb, $params);
  464. }
  465. return $results;
  466. }
  467. /**
  468. * Call method or callback on each item in the collection.
  469. *
  470. * @param string|array|\Closure $objMethod
  471. * @param array $args
  472. * @return void
  473. */
  474. public function each($objMethod, $args = [])
  475. {
  476. if ($objMethod instanceof \Closure) {
  477. foreach ($this->getItems() as $item) {
  478. $objMethod($item, ...$args);
  479. }
  480. } elseif (is_array($objMethod)) {
  481. foreach ($this->getItems() as $item) {
  482. call_user_func($objMethod, $item, ...$args);
  483. }
  484. } else {
  485. foreach ($this->getItems() as $item) {
  486. $item->$objMethod(...$args);
  487. }
  488. }
  489. }
  490. /**
  491. * Setting data for all collection items
  492. *
  493. * @param mixed $key
  494. * @param mixed $value
  495. * @return $this
  496. */
  497. public function setDataToAll($key, $value = null)
  498. {
  499. if (is_array($key)) {
  500. foreach ($key as $k => $v) {
  501. $this->setDataToAll($k, $v);
  502. }
  503. return $this;
  504. }
  505. foreach ($this->getItems() as $item) {
  506. $item->setData($key, $value);
  507. }
  508. return $this;
  509. }
  510. /**
  511. * Set current page
  512. *
  513. * @param int $page
  514. * @return $this
  515. */
  516. public function setCurPage($page)
  517. {
  518. $this->_curPage = $page;
  519. return $this;
  520. }
  521. /**
  522. * Set collection page size
  523. *
  524. * @param int $size
  525. * @return $this
  526. */
  527. public function setPageSize($size)
  528. {
  529. $this->_pageSize = $size;
  530. return $this;
  531. }
  532. /**
  533. * Set select order
  534. *
  535. * @param string $field
  536. * @param string $direction
  537. * @return $this
  538. */
  539. public function setOrder($field, $direction = self::SORT_ORDER_DESC)
  540. {
  541. $this->_orders[$field] = $direction;
  542. return $this;
  543. }
  544. /**
  545. * Set collection item class name
  546. *
  547. * @param string $className
  548. * @return $this
  549. * @throws \InvalidArgumentException
  550. */
  551. public function setItemObjectClass($className)
  552. {
  553. if (!is_a($className, \Magento\Framework\DataObject::class, true)) {
  554. throw new \InvalidArgumentException($className . ' does not extend \Magento\Framework\DataObject');
  555. }
  556. $this->_itemObjectClass = $className;
  557. return $this;
  558. }
  559. /**
  560. * Retrieve collection empty item
  561. *
  562. * @return \Magento\Framework\DataObject
  563. */
  564. public function getNewEmptyItem()
  565. {
  566. return $this->_entityFactory->create($this->_itemObjectClass);
  567. }
  568. /**
  569. * Render sql select conditions
  570. *
  571. * @return $this
  572. */
  573. protected function _renderFilters()
  574. {
  575. return $this;
  576. }
  577. /**
  578. * Render sql select orders
  579. *
  580. * @return $this
  581. */
  582. protected function _renderOrders()
  583. {
  584. return $this;
  585. }
  586. /**
  587. * Render sql select limit
  588. *
  589. * @return $this
  590. */
  591. protected function _renderLimit()
  592. {
  593. return $this;
  594. }
  595. /**
  596. * Set select distinct
  597. *
  598. * @param bool $flag
  599. * @return $this
  600. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  601. */
  602. public function distinct($flag)
  603. {
  604. return $this;
  605. }
  606. /**
  607. * Load data
  608. *
  609. * @param bool $printQuery
  610. * @param bool $logQuery
  611. * @return $this
  612. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  613. */
  614. public function loadData($printQuery = false, $logQuery = false)
  615. {
  616. return $this;
  617. }
  618. /**
  619. * Load data
  620. *
  621. * @param bool $printQuery
  622. * @param bool $logQuery
  623. * @return $this
  624. */
  625. public function load($printQuery = false, $logQuery = false)
  626. {
  627. return $this->loadData($printQuery, $logQuery);
  628. }
  629. /**
  630. * Load data with filter in place
  631. *
  632. * @param bool $printQuery
  633. * @param bool $logQuery
  634. * @return $this
  635. */
  636. public function loadWithFilter($printQuery = false, $logQuery = false)
  637. {
  638. return $this->loadData($printQuery, $logQuery);
  639. }
  640. /**
  641. * Convert collection to XML
  642. *
  643. * @return string
  644. */
  645. public function toXml()
  646. {
  647. $xml = '<?xml version="1.0" encoding="UTF-8"?>
  648. <collection>
  649. <totalRecords>' .
  650. $this->_totalRecords .
  651. '</totalRecords>
  652. <items>';
  653. foreach ($this as $item) {
  654. $xml .= $item->toXml();
  655. }
  656. $xml .= '</items>
  657. </collection>';
  658. return $xml;
  659. }
  660. /**
  661. * Convert collection to array
  662. *
  663. * @param array $arrRequiredFields
  664. * @return array
  665. */
  666. public function toArray($arrRequiredFields = [])
  667. {
  668. $arrItems = [];
  669. $arrItems['totalRecords'] = $this->getSize();
  670. $arrItems['items'] = [];
  671. foreach ($this as $item) {
  672. $arrItems['items'][] = $item->toArray($arrRequiredFields);
  673. }
  674. return $arrItems;
  675. }
  676. /**
  677. * Convert items array to array for select options
  678. *
  679. * Return items array
  680. * array(
  681. * $index => array(
  682. * 'value' => mixed
  683. * 'label' => mixed
  684. * )
  685. * )
  686. *
  687. * @param string $valueField
  688. * @param string $labelField
  689. * @param array $additional
  690. * @return array
  691. */
  692. protected function _toOptionArray($valueField = 'id', $labelField = 'name', $additional = [])
  693. {
  694. $res = [];
  695. $additional['value'] = $valueField;
  696. $additional['label'] = $labelField;
  697. foreach ($this as $item) {
  698. foreach ($additional as $code => $field) {
  699. $data[$code] = $item->getData($field);
  700. }
  701. $res[] = $data;
  702. }
  703. return $res;
  704. }
  705. /**
  706. * Returns option array
  707. *
  708. * @return array
  709. */
  710. public function toOptionArray()
  711. {
  712. return $this->_toOptionArray();
  713. }
  714. /**
  715. * Returns options hash
  716. *
  717. * @return array
  718. */
  719. public function toOptionHash()
  720. {
  721. return $this->_toOptionHash();
  722. }
  723. /**
  724. * Convert items array to hash for select options
  725. *
  726. * Return items hash
  727. * array($value => $label)
  728. *
  729. * @param string $valueField
  730. * @param string $labelField
  731. * @return array
  732. */
  733. protected function _toOptionHash($valueField = 'id', $labelField = 'name')
  734. {
  735. $res = [];
  736. foreach ($this as $item) {
  737. $res[$item->getData($valueField)] = $item->getData($labelField);
  738. }
  739. return $res;
  740. }
  741. /**
  742. * Retrieve item by id
  743. *
  744. * @param mixed $idValue
  745. * @return \Magento\Framework\DataObject
  746. */
  747. public function getItemById($idValue)
  748. {
  749. $this->load();
  750. if (isset($this->_items[$idValue])) {
  751. return $this->_items[$idValue];
  752. }
  753. return null;
  754. }
  755. /**
  756. * Implementation of \IteratorAggregate::getIterator()
  757. *
  758. * @return \ArrayIterator
  759. */
  760. public function getIterator()
  761. {
  762. $this->load();
  763. return new \ArrayIterator($this->_items);
  764. }
  765. /**
  766. * Retrieve count of collection loaded items
  767. *
  768. * @return int
  769. */
  770. public function count()
  771. {
  772. $this->load();
  773. return count($this->_items);
  774. }
  775. /**
  776. * Retrieve Flag
  777. *
  778. * @param string $flag
  779. * @return bool|null
  780. */
  781. public function getFlag($flag)
  782. {
  783. return $this->_flags[$flag] ?? null;
  784. }
  785. /**
  786. * Set Flag
  787. *
  788. * @param string $flag
  789. * @param bool|null $value
  790. * @return $this
  791. */
  792. public function setFlag($flag, $value = null)
  793. {
  794. $this->_flags[$flag] = $value;
  795. return $this;
  796. }
  797. /**
  798. * Has Flag
  799. *
  800. * @param string $flag
  801. * @return bool
  802. */
  803. public function hasFlag($flag)
  804. {
  805. return array_key_exists($flag, $this->_flags);
  806. }
  807. /**
  808. * Sleep handler
  809. *
  810. * @return string[]
  811. * @since 100.0.11
  812. */
  813. public function __sleep()
  814. {
  815. $properties = array_keys(get_object_vars($this));
  816. $properties = array_diff(
  817. $properties,
  818. [
  819. '_entityFactory',
  820. ]
  821. );
  822. return $properties;
  823. }
  824. /**
  825. * Init not serializable fields
  826. *
  827. * @return void
  828. * @since 100.0.11
  829. */
  830. public function __wakeup()
  831. {
  832. $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
  833. $this->_entityFactory = $objectManager->get(EntityFactoryInterface::class);
  834. }
  835. }