AbstractDb.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Data\Collection;
  7. use Magento\Framework\App\ResourceConnection;
  8. use Magento\Framework\Data\Collection\Db\FetchStrategyInterface;
  9. use Magento\Framework\DB\Adapter\AdapterInterface;
  10. use Magento\Framework\DB\Select;
  11. use Magento\Framework\Api\ExtensionAttribute\JoinDataInterface;
  12. use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
  13. use Psr\Log\LoggerInterface as Logger;
  14. /**
  15. * Base items collection class
  16. *
  17. * @api
  18. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  19. * @since 100.0.2
  20. */
  21. abstract class AbstractDb extends \Magento\Framework\Data\Collection
  22. {
  23. /**
  24. * DB connection
  25. *
  26. * @var \Magento\Framework\DB\Adapter\AdapterInterface
  27. */
  28. protected $_conn;
  29. /**
  30. * Select object
  31. *
  32. * @var \Magento\Framework\DB\Select
  33. */
  34. protected $_select;
  35. /**
  36. * Identifier field name for collection items
  37. *
  38. * Can be used by collections with items without defined
  39. *
  40. * @var string
  41. */
  42. protected $_idFieldName;
  43. /**
  44. * List of bound variables for select
  45. *
  46. * @var array
  47. */
  48. protected $_bindParams = [];
  49. /**
  50. * All collection data array
  51. * Used for getData method
  52. *
  53. * @var array
  54. */
  55. protected $_data = null;
  56. /**
  57. * Fields map for correlation names & real selected fields
  58. *
  59. * @var array
  60. */
  61. protected $_map = null;
  62. /**
  63. * Database's statement for fetch item one by one
  64. *
  65. * @var \Zend_Db_Statement_Pdo
  66. */
  67. protected $_fetchStmt = null;
  68. /**
  69. * Whether orders are rendered
  70. *
  71. * @var bool
  72. */
  73. protected $_isOrdersRendered = false;
  74. /**
  75. * @var Logger
  76. */
  77. protected $_logger;
  78. /**
  79. * @var FetchStrategyInterface
  80. */
  81. private $_fetchStrategy;
  82. /**
  83. * Join processor is set only if extension attributes were joined before the collection was loaded.
  84. *
  85. * @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface|null
  86. */
  87. protected $extensionAttributesJoinProcessor;
  88. /**
  89. * @param EntityFactoryInterface $entityFactory
  90. * @param Logger $logger
  91. * @param FetchStrategyInterface $fetchStrategy
  92. * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
  93. */
  94. public function __construct(
  95. EntityFactoryInterface $entityFactory,
  96. Logger $logger,
  97. FetchStrategyInterface $fetchStrategy,
  98. \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
  99. ) {
  100. parent::__construct($entityFactory);
  101. $this->_fetchStrategy = $fetchStrategy;
  102. if ($connection !== null) {
  103. $this->setConnection($connection);
  104. }
  105. $this->_logger = $logger;
  106. }
  107. /**
  108. * Get resource instance.
  109. *
  110. * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb
  111. */
  112. abstract public function getResource();
  113. /**
  114. * Add variable to bind list
  115. *
  116. * @param string $name
  117. * @param mixed $value
  118. * @return $this
  119. */
  120. public function addBindParam($name, $value)
  121. {
  122. $this->_bindParams[$name] = $value;
  123. return $this;
  124. }
  125. /**
  126. * Specify collection objects id field name
  127. *
  128. * @param string $fieldName
  129. * @return $this
  130. */
  131. protected function _setIdFieldName($fieldName)
  132. {
  133. $this->_idFieldName = $fieldName;
  134. return $this;
  135. }
  136. /**
  137. * Id field name getter
  138. *
  139. * @return string
  140. */
  141. public function getIdFieldName()
  142. {
  143. return $this->_idFieldName;
  144. }
  145. /**
  146. * Get collection item identifier
  147. *
  148. * @param \Magento\Framework\DataObject $item
  149. * @return mixed
  150. */
  151. protected function _getItemId(\Magento\Framework\DataObject $item)
  152. {
  153. if ($field = $this->getIdFieldName()) {
  154. return $item->getData($field);
  155. }
  156. return parent::_getItemId($item);
  157. }
  158. /**
  159. * Set database connection adapter
  160. *
  161. * @param \Magento\Framework\DB\Adapter\AdapterInterface $conn
  162. * @return $this
  163. * @throws \Magento\Framework\Exception\LocalizedException
  164. */
  165. public function setConnection(\Magento\Framework\DB\Adapter\AdapterInterface $conn)
  166. {
  167. $this->_conn = $conn;
  168. $this->_select = $this->_conn->select();
  169. $this->_isOrdersRendered = false;
  170. return $this;
  171. }
  172. /**
  173. * Get \Magento\Framework\DB\Select instance
  174. *
  175. * @return Select
  176. */
  177. public function getSelect()
  178. {
  179. return $this->_select;
  180. }
  181. /**
  182. * Retrieve connection object
  183. *
  184. * @return AdapterInterface
  185. */
  186. public function getConnection()
  187. {
  188. return $this->_conn;
  189. }
  190. /**
  191. * Get collection size
  192. *
  193. * @return int
  194. */
  195. public function getSize()
  196. {
  197. if ($this->_totalRecords === null) {
  198. $sql = $this->getSelectCountSql();
  199. $this->_totalRecords = $this->getConnection()->fetchOne($sql, $this->_bindParams);
  200. }
  201. return (int)$this->_totalRecords;
  202. }
  203. /**
  204. * Get SQL for get record count
  205. *
  206. * @return Select
  207. */
  208. public function getSelectCountSql()
  209. {
  210. $this->_renderFilters();
  211. $countSelect = clone $this->getSelect();
  212. $countSelect->reset(\Magento\Framework\DB\Select::ORDER);
  213. $countSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT);
  214. $countSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET);
  215. $countSelect->reset(\Magento\Framework\DB\Select::COLUMNS);
  216. $part = $this->getSelect()->getPart(\Magento\Framework\DB\Select::GROUP);
  217. if (!is_array($part) || !count($part)) {
  218. $countSelect->columns(new \Zend_Db_Expr('COUNT(*)'));
  219. return $countSelect;
  220. }
  221. $countSelect->reset(\Magento\Framework\DB\Select::GROUP);
  222. $group = $this->getSelect()->getPart(\Magento\Framework\DB\Select::GROUP);
  223. $countSelect->columns(new \Zend_Db_Expr(("COUNT(DISTINCT ".implode(", ", $group).")")));
  224. return $countSelect;
  225. }
  226. /**
  227. * Get sql select string or object
  228. *
  229. * @param bool $stringMode
  230. * @return string|\Magento\Framework\DB\Select
  231. */
  232. public function getSelectSql($stringMode = false)
  233. {
  234. if ($stringMode) {
  235. return $this->_select->__toString();
  236. }
  237. return $this->_select;
  238. }
  239. /**
  240. * Add select order
  241. *
  242. * @param string $field
  243. * @param string $direction
  244. * @return $this
  245. */
  246. public function setOrder($field, $direction = self::SORT_ORDER_DESC)
  247. {
  248. return $this->_setOrder($field, $direction);
  249. }
  250. /**
  251. * Sets order and direction.
  252. *
  253. * @param string $field
  254. * @param string $direction
  255. * @return $this
  256. */
  257. public function addOrder($field, $direction = self::SORT_ORDER_DESC)
  258. {
  259. return $this->_setOrder($field, $direction);
  260. }
  261. /**
  262. * Add select order to the beginning
  263. *
  264. * @param string $field
  265. * @param string $direction
  266. * @return $this
  267. */
  268. public function unshiftOrder($field, $direction = self::SORT_ORDER_DESC)
  269. {
  270. return $this->_setOrder($field, $direction, true);
  271. }
  272. /**
  273. * Add ORDER BY to the end or to the beginning
  274. *
  275. * @param string $field
  276. * @param string $direction
  277. * @param bool $unshift
  278. * @return $this
  279. */
  280. private function _setOrder($field, $direction, $unshift = false)
  281. {
  282. $this->_isOrdersRendered = false;
  283. $field = (string)$this->_getMappedField($field);
  284. $direction = strtoupper($direction) == self::SORT_ORDER_ASC ? self::SORT_ORDER_ASC : self::SORT_ORDER_DESC;
  285. unset($this->_orders[$field]);
  286. // avoid ordering by the same field twice
  287. if ($unshift) {
  288. $orders = [$field => $direction];
  289. foreach ($this->_orders as $key => $dir) {
  290. $orders[$key] = $dir;
  291. }
  292. $this->_orders = $orders;
  293. } else {
  294. $this->_orders[$field] = $direction;
  295. }
  296. return $this;
  297. }
  298. /**
  299. * Render sql select conditions
  300. *
  301. * @return $this
  302. */
  303. protected function _renderFilters()
  304. {
  305. if ($this->_isFiltersRendered) {
  306. return $this;
  307. }
  308. $this->_renderFiltersBefore();
  309. foreach ($this->_filters as $filter) {
  310. switch ($filter['type']) {
  311. case 'or':
  312. $condition = $this->_conn->quoteInto($filter['field'] . '=?', $filter['value']);
  313. $this->_select->orWhere($condition);
  314. break;
  315. case 'string':
  316. $this->_select->where($filter['value']);
  317. break;
  318. case 'public':
  319. $field = $this->_getMappedField($filter['field']);
  320. $condition = $filter['value'];
  321. $this->_select->where($this->_getConditionSql($field, $condition), null, Select::TYPE_CONDITION);
  322. break;
  323. default:
  324. $condition = $this->_conn->quoteInto($filter['field'] . '=?', $filter['value']);
  325. $this->_select->where($condition);
  326. }
  327. }
  328. $this->_isFiltersRendered = true;
  329. return $this;
  330. }
  331. /**
  332. * Hook for operations before rendering filters
  333. *
  334. * @return void
  335. */
  336. protected function _renderFiltersBefore()
  337. {
  338. }
  339. /**
  340. * Add field filter to collection
  341. *
  342. * @see self::_getConditionSql for $condition
  343. *
  344. * @param string|array $field
  345. * @param null|string|array $condition
  346. * @return $this
  347. */
  348. public function addFieldToFilter($field, $condition = null)
  349. {
  350. if (is_array($field)) {
  351. $conditions = [];
  352. foreach ($field as $key => $value) {
  353. $conditions[] = $this->_translateCondition($value, isset($condition[$key]) ? $condition[$key] : null);
  354. }
  355. $resultCondition = '(' . implode(') ' . \Magento\Framework\DB\Select::SQL_OR . ' (', $conditions) . ')';
  356. } else {
  357. $resultCondition = $this->_translateCondition($field, $condition);
  358. }
  359. $this->_select->where($resultCondition, null, Select::TYPE_CONDITION);
  360. return $this;
  361. }
  362. /**
  363. * Build sql where condition part
  364. *
  365. * @param string|array $field
  366. * @param null|string|array $condition
  367. * @return string
  368. */
  369. protected function _translateCondition($field, $condition)
  370. {
  371. $field = $this->_getMappedField($field);
  372. return $this->_getConditionSql($this->getConnection()->quoteIdentifier($field), $condition);
  373. }
  374. /**
  375. * Try to get mapped field name for filter to collection
  376. *
  377. * @param string $field
  378. * @return string
  379. */
  380. protected function _getMappedField($field)
  381. {
  382. $mapper = $this->_getMapper();
  383. if (isset($mapper['fields'][$field])) {
  384. $mappedField = $mapper['fields'][$field];
  385. } else {
  386. $mappedField = $field;
  387. }
  388. return $mappedField;
  389. }
  390. /**
  391. * Retrieve mapper data
  392. *
  393. * @return array|bool|null
  394. */
  395. protected function _getMapper()
  396. {
  397. if (isset($this->_map)) {
  398. return $this->_map;
  399. } else {
  400. return false;
  401. }
  402. }
  403. /**
  404. * Build SQL statement for condition
  405. *
  406. * If $condition integer or string - exact value will be filtered ('eq' condition)
  407. *
  408. * If $condition is array - one of the following structures is expected:
  409. * - array("from" => $fromValue, "to" => $toValue)
  410. * - array("eq" => $equalValue)
  411. * - array("neq" => $notEqualValue)
  412. * - array("like" => $likeValue)
  413. * - array("in" => array($inValues))
  414. * - array("nin" => array($notInValues))
  415. * - array("notnull" => $valueIsNotNull)
  416. * - array("null" => $valueIsNull)
  417. * - array("moreq" => $moreOrEqualValue)
  418. * - array("gt" => $greaterValue)
  419. * - array("lt" => $lessValue)
  420. * - array("gteq" => $greaterOrEqualValue)
  421. * - array("lteq" => $lessOrEqualValue)
  422. * - array("finset" => $valueInSet)
  423. * - array("regexp" => $regularExpression)
  424. * - array("seq" => $stringValue)
  425. * - array("sneq" => $stringValue)
  426. *
  427. * If non matched - sequential array is expected and OR conditions
  428. * will be built using above mentioned structure
  429. *
  430. * @param string $fieldName
  431. * @param integer|string|array $condition
  432. * @return string
  433. */
  434. protected function _getConditionSql($fieldName, $condition)
  435. {
  436. return $this->getConnection()->prepareSqlCondition($fieldName, $condition);
  437. }
  438. /**
  439. * Return the field name for the condition.
  440. *
  441. * @param string $fieldName
  442. * @return string
  443. */
  444. protected function _getConditionFieldName($fieldName)
  445. {
  446. return $fieldName;
  447. }
  448. /**
  449. * Render sql select orders
  450. *
  451. * @return $this
  452. */
  453. protected function _renderOrders()
  454. {
  455. if (!$this->_isOrdersRendered) {
  456. foreach ($this->_orders as $field => $direction) {
  457. $this->_select->order(new \Zend_Db_Expr($field . ' ' . $direction));
  458. }
  459. $this->_isOrdersRendered = true;
  460. }
  461. return $this;
  462. }
  463. /**
  464. * Render sql select limit
  465. *
  466. * @return $this
  467. */
  468. protected function _renderLimit()
  469. {
  470. if ($this->_pageSize) {
  471. $this->_select->limitPage($this->getCurPage(), $this->_pageSize);
  472. }
  473. return $this;
  474. }
  475. /**
  476. * Set select distinct
  477. *
  478. * @param bool $flag
  479. * @return $this
  480. */
  481. public function distinct($flag)
  482. {
  483. $this->_select->distinct($flag);
  484. return $this;
  485. }
  486. /**
  487. * Before load action
  488. *
  489. * @return $this
  490. */
  491. protected function _beforeLoad()
  492. {
  493. return $this;
  494. }
  495. /**
  496. * Load data
  497. *
  498. * @param bool $printQuery
  499. * @param bool $logQuery
  500. * @return $this
  501. */
  502. public function load($printQuery = false, $logQuery = false)
  503. {
  504. if ($this->isLoaded()) {
  505. return $this;
  506. }
  507. return $this->loadWithFilter($printQuery, $logQuery);
  508. }
  509. /**
  510. * Load data with filter in place
  511. *
  512. * @param bool $printQuery
  513. * @param bool $logQuery
  514. * @return $this
  515. */
  516. public function loadWithFilter($printQuery = false, $logQuery = false)
  517. {
  518. $this->_beforeLoad();
  519. $this->_renderFilters()->_renderOrders()->_renderLimit();
  520. $this->printLogQuery($printQuery, $logQuery);
  521. $data = $this->getData();
  522. $this->resetData();
  523. if (is_array($data)) {
  524. foreach ($data as $row) {
  525. $item = $this->getNewEmptyItem();
  526. if ($this->getIdFieldName()) {
  527. $item->setIdFieldName($this->getIdFieldName());
  528. }
  529. $item->addData($row);
  530. $this->beforeAddLoadedItem($item);
  531. $this->addItem($item);
  532. }
  533. }
  534. $this->_setIsLoaded();
  535. $this->_afterLoad();
  536. return $this;
  537. }
  538. /**
  539. * Let do something before add loaded item in collection
  540. *
  541. * @param \Magento\Framework\DataObject $item
  542. * @return \Magento\Framework\DataObject
  543. */
  544. protected function beforeAddLoadedItem(\Magento\Framework\DataObject $item)
  545. {
  546. return $item;
  547. }
  548. /**
  549. * Returns an items collection.
  550. * Returns a collection item that corresponds to the fetched row
  551. * and moves the internal data pointer ahead
  552. *
  553. * @return \Magento\Framework\Model\AbstractModel|bool
  554. */
  555. public function fetchItem()
  556. {
  557. if (null === $this->_fetchStmt) {
  558. $this->_renderOrders()->_renderLimit();
  559. $this->_fetchStmt = $this->getConnection()->query($this->getSelect());
  560. }
  561. $data = $this->_fetchStmt->fetch();
  562. if (!empty($data) && is_array($data)) {
  563. $item = $this->getNewEmptyItem();
  564. if ($this->getIdFieldName()) {
  565. $item->setIdFieldName($this->getIdFieldName());
  566. }
  567. $item->setData($data);
  568. return $item;
  569. }
  570. return false;
  571. }
  572. /**
  573. * Overridden to use _idFieldName by default.
  574. *
  575. * @param string|null $valueField
  576. * @param string $labelField
  577. * @param array $additional
  578. * @return array
  579. */
  580. protected function _toOptionArray($valueField = null, $labelField = 'name', $additional = [])
  581. {
  582. if ($valueField === null) {
  583. $valueField = $this->getIdFieldName();
  584. }
  585. return parent::_toOptionArray($valueField, $labelField, $additional);
  586. }
  587. /**
  588. * Overridden to use _idFieldName by default.
  589. *
  590. * @param string $valueField
  591. * @param string $labelField
  592. * @return array
  593. */
  594. protected function _toOptionHash($valueField = null, $labelField = 'name')
  595. {
  596. if ($valueField === null) {
  597. $valueField = $this->getIdFieldName();
  598. }
  599. return parent::_toOptionHash($valueField, $labelField);
  600. }
  601. /**
  602. * Get all data array for collection
  603. *
  604. * @return array
  605. */
  606. public function getData()
  607. {
  608. if ($this->_data === null) {
  609. $this->_renderFilters()->_renderOrders()->_renderLimit();
  610. $select = $this->getSelect();
  611. $this->_data = $this->_fetchAll($select);
  612. $this->_afterLoadData();
  613. }
  614. return $this->_data;
  615. }
  616. /**
  617. * Process loaded collection data
  618. *
  619. * @return $this
  620. */
  621. protected function _afterLoadData()
  622. {
  623. return $this;
  624. }
  625. /**
  626. * Reset loaded for collection data array
  627. *
  628. * @return $this
  629. */
  630. public function resetData()
  631. {
  632. $this->_data = null;
  633. return $this;
  634. }
  635. /**
  636. * Process loaded collection
  637. *
  638. * @return $this
  639. */
  640. protected function _afterLoad()
  641. {
  642. return $this;
  643. }
  644. /**
  645. * Load the data.
  646. *
  647. * @param bool $printQuery
  648. * @param bool $logQuery
  649. * @return $this
  650. */
  651. public function loadData($printQuery = false, $logQuery = false)
  652. {
  653. return $this->load($printQuery, $logQuery);
  654. }
  655. /**
  656. * Print and/or log query
  657. *
  658. * @param bool $printQuery
  659. * @param bool $logQuery
  660. * @param string $sql
  661. * @return $this
  662. */
  663. public function printLogQuery($printQuery = false, $logQuery = false, $sql = null)
  664. {
  665. if ($printQuery || $this->getFlag('print_query')) {
  666. echo $sql === null ? $this->getSelect()->__toString() : $sql;
  667. }
  668. if ($logQuery || $this->getFlag('log_query')) {
  669. $this->_logQuery($sql);
  670. }
  671. return $this;
  672. }
  673. /**
  674. * Log query
  675. *
  676. * @param string $sql
  677. * @return void
  678. */
  679. protected function _logQuery($sql)
  680. {
  681. $this->_logger->info($sql === null ? $this->getSelect()->__toString() : $sql);
  682. }
  683. /**
  684. * Reset collection
  685. *
  686. * @return $this
  687. */
  688. protected function _reset()
  689. {
  690. $this->getSelect()->reset();
  691. $this->_initSelect();
  692. $this->_setIsLoaded(false);
  693. $this->_items = [];
  694. $this->_data = null;
  695. $this->extensionAttributesJoinProcessor = null;
  696. return $this;
  697. }
  698. /**
  699. * Fetch collection data
  700. *
  701. * @param Select $select
  702. * @return array
  703. */
  704. protected function _fetchAll(Select $select)
  705. {
  706. $data = $this->_fetchStrategy->fetchAll($select, $this->_bindParams);
  707. if ($this->extensionAttributesJoinProcessor) {
  708. foreach ($data as $key => $dataItem) {
  709. $data[$key] = $this->extensionAttributesJoinProcessor->extractExtensionAttributes(
  710. $this->_itemObjectClass,
  711. $dataItem
  712. );
  713. }
  714. }
  715. return $data;
  716. }
  717. /**
  718. * Add filter to Map
  719. *
  720. * @param string $filter
  721. * @param string $alias
  722. * @param string $group default 'fields'
  723. * @return $this
  724. */
  725. public function addFilterToMap($filter, $alias, $group = 'fields')
  726. {
  727. if ($this->_map === null) {
  728. $this->_map = [$group => []];
  729. } elseif (empty($this->_map[$group])) {
  730. $this->_map[$group] = [];
  731. }
  732. $this->_map[$group][$filter] = $alias;
  733. return $this;
  734. }
  735. /**
  736. * Clone $this->_select during cloning collection, otherwise both collections will share the same $this->_select
  737. *
  738. * @return void
  739. */
  740. public function __clone()
  741. {
  742. if (is_object($this->_select)) {
  743. $this->_select = clone $this->_select;
  744. }
  745. }
  746. /**
  747. * Init select
  748. *
  749. * @return void
  750. */
  751. protected function _initSelect()
  752. {
  753. // no implementation, should be overridden in children classes
  754. }
  755. /**
  756. * Join extension attribute.
  757. *
  758. * @param JoinDataInterface $join
  759. * @param JoinProcessorInterface $extensionAttributesJoinProcessor
  760. * @return $this
  761. */
  762. public function joinExtensionAttribute(
  763. JoinDataInterface $join,
  764. JoinProcessorInterface $extensionAttributesJoinProcessor
  765. ) {
  766. $selectFrom = $this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM);
  767. $joinRequired = !isset($selectFrom[$join->getReferenceTableAlias()]);
  768. if ($joinRequired) {
  769. $joinOn = $this->getMainTableAlias() . '.' . $join->getJoinField()
  770. . ' = ' . $join->getReferenceTableAlias() . '.' . $join->getReferenceField();
  771. $this->getSelect()->joinLeft(
  772. [$join->getReferenceTableAlias() => $this->getResource()->getTable($join->getReferenceTable())],
  773. $joinOn,
  774. []
  775. );
  776. }
  777. $columns = [];
  778. foreach ($join->getSelectFields() as $selectField) {
  779. $fieldWIthDbPrefix = $selectField[JoinDataInterface::SELECT_FIELD_WITH_DB_PREFIX];
  780. $columns[$selectField[JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS]] = $fieldWIthDbPrefix;
  781. $this->addFilterToMap($selectField[JoinDataInterface::SELECT_FIELD_EXTERNAL_ALIAS], $fieldWIthDbPrefix);
  782. }
  783. $this->getSelect()->columns($columns);
  784. $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
  785. return $this;
  786. }
  787. /**
  788. * Get collection item object class name.
  789. *
  790. * @return string
  791. */
  792. public function getItemObjectClass()
  793. {
  794. return $this->_itemObjectClass;
  795. }
  796. /**
  797. * Identify main table alias or its name if alias is not defined.
  798. *
  799. * @return string
  800. * @throws \LogicException
  801. */
  802. private function getMainTableAlias()
  803. {
  804. foreach ($this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM) as $tableAlias => $tableMetadata) {
  805. if ($tableMetadata['joinType'] == 'from') {
  806. return $tableAlias;
  807. }
  808. }
  809. throw new \LogicException("Main table cannot be identified.");
  810. }
  811. /**
  812. * @inheritdoc
  813. * @since 100.0.11
  814. */
  815. public function __sleep()
  816. {
  817. return array_diff(
  818. parent::__sleep(),
  819. ['_fetchStrategy', '_logger', '_conn', 'extensionAttributesJoinProcessor']
  820. );
  821. }
  822. /**
  823. * @inheritdoc
  824. * @since 100.0.11
  825. */
  826. public function __wakeup()
  827. {
  828. parent::__wakeup();
  829. $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
  830. $this->_logger = $objectManager->get(Logger::class);
  831. $this->_fetchStrategy = $objectManager->get(FetchStrategyInterface::class);
  832. $this->_conn = $objectManager->get(ResourceConnection::class)->getConnection();
  833. }
  834. }