123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Framework\Data\Collection;
- use Magento\Framework\App\ResourceConnection;
- use Magento\Framework\Data\Collection\Db\FetchStrategyInterface;
- use Magento\Framework\DB\Adapter\AdapterInterface;
- use Magento\Framework\DB\Select;
- use Magento\Framework\Api\ExtensionAttribute\JoinDataInterface;
- use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
- use Psr\Log\LoggerInterface as Logger;
- /**
- * Base items collection class
- *
- * @api
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- * @since 100.0.2
- */
- abstract class AbstractDb extends \Magento\Framework\Data\Collection
- {
- /**
- * DB connection
- *
- * @var \Magento\Framework\DB\Adapter\AdapterInterface
- */
- protected $_conn;
- /**
- * Select object
- *
- * @var \Magento\Framework\DB\Select
- */
- protected $_select;
- /**
- * Identifier field name for collection items
- *
- * Can be used by collections with items without defined
- *
- * @var string
- */
- protected $_idFieldName;
- /**
- * List of bound variables for select
- *
- * @var array
- */
- protected $_bindParams = [];
- /**
- * All collection data array
- * Used for getData method
- *
- * @var array
- */
- protected $_data = null;
- /**
- * Fields map for correlation names & real selected fields
- *
- * @var array
- */
- protected $_map = null;
- /**
- * Database's statement for fetch item one by one
- *
- * @var \Zend_Db_Statement_Pdo
- */
- protected $_fetchStmt = null;
- /**
- * Whether orders are rendered
- *
- * @var bool
- */
- protected $_isOrdersRendered = false;
- /**
- * @var Logger
- */
- protected $_logger;
- /**
- * @var FetchStrategyInterface
- */
- private $_fetchStrategy;
- /**
- * Join processor is set only if extension attributes were joined before the collection was loaded.
- *
- * @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface|null
- */
- protected $extensionAttributesJoinProcessor;
- /**
- * @param EntityFactoryInterface $entityFactory
- * @param Logger $logger
- * @param FetchStrategyInterface $fetchStrategy
- * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
- */
- public function __construct(
- EntityFactoryInterface $entityFactory,
- Logger $logger,
- FetchStrategyInterface $fetchStrategy,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
- ) {
- parent::__construct($entityFactory);
- $this->_fetchStrategy = $fetchStrategy;
- if ($connection !== null) {
- $this->setConnection($connection);
- }
- $this->_logger = $logger;
- }
- /**
- * Get resource instance.
- *
- * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb
- */
- abstract public function getResource();
- /**
- * Add variable to bind list
- *
- * @param string $name
- * @param mixed $value
- * @return $this
- */
- public function addBindParam($name, $value)
- {
- $this->_bindParams[$name] = $value;
- return $this;
- }
- /**
- * Specify collection objects id field name
- *
- * @param string $fieldName
- * @return $this
- */
- protected function _setIdFieldName($fieldName)
- {
- $this->_idFieldName = $fieldName;
- return $this;
- }
- /**
- * Id field name getter
- *
- * @return string
- */
- public function getIdFieldName()
- {
- return $this->_idFieldName;
- }
- /**
- * Get collection item identifier
- *
- * @param \Magento\Framework\DataObject $item
- * @return mixed
- */
- protected function _getItemId(\Magento\Framework\DataObject $item)
- {
- if ($field = $this->getIdFieldName()) {
- return $item->getData($field);
- }
- return parent::_getItemId($item);
- }
- /**
- * Set database connection adapter
- *
- * @param \Magento\Framework\DB\Adapter\AdapterInterface $conn
- * @return $this
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- public function setConnection(\Magento\Framework\DB\Adapter\AdapterInterface $conn)
- {
- $this->_conn = $conn;
- $this->_select = $this->_conn->select();
- $this->_isOrdersRendered = false;
- return $this;
- }
- /**
- * Get \Magento\Framework\DB\Select instance
- *
- * @return Select
- */
- public function getSelect()
- {
- return $this->_select;
- }
- /**
- * Retrieve connection object
- *
- * @return AdapterInterface
- */
- public function getConnection()
- {
- return $this->_conn;
- }
- /**
- * Get collection size
- *
- * @return int
- */
- public function getSize()
- {
- if ($this->_totalRecords === null) {
- $sql = $this->getSelectCountSql();
- $this->_totalRecords = $this->getConnection()->fetchOne($sql, $this->_bindParams);
- }
- return (int)$this->_totalRecords;
- }
- /**
- * Get SQL for get record count
- *
- * @return Select
- */
- public function getSelectCountSql()
- {
- $this->_renderFilters();
- $countSelect = clone $this->getSelect();
- $countSelect->reset(\Magento\Framework\DB\Select::ORDER);
- $countSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT);
- $countSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET);
- $countSelect->reset(\Magento\Framework\DB\Select::COLUMNS);
- $part = $this->getSelect()->getPart(\Magento\Framework\DB\Select::GROUP);
- if (!is_array($part) || !count($part)) {
- $countSelect->columns(new \Zend_Db_Expr('COUNT(*)'));
- return $countSelect;
- }
- $countSelect->reset(\Magento\Framework\DB\Select::GROUP);
- $group = $this->getSelect()->getPart(\Magento\Framework\DB\Select::GROUP);
- $countSelect->columns(new \Zend_Db_Expr(("COUNT(DISTINCT ".implode(", ", $group).")")));
- return $countSelect;
- }
- /**
- * Get sql select string or object
- *
- * @param bool $stringMode
- * @return string|\Magento\Framework\DB\Select
- */
- public function getSelectSql($stringMode = false)
- {
- if ($stringMode) {
- return $this->_select->__toString();
- }
- return $this->_select;
- }
- /**
- * Add select order
- *
- * @param string $field
- * @param string $direction
- * @return $this
- */
- public function setOrder($field, $direction = self::SORT_ORDER_DESC)
- {
- return $this->_setOrder($field, $direction);
- }
- /**
- * Sets order and direction.
- *
- * @param string $field
- * @param string $direction
- * @return $this
- */
- public function addOrder($field, $direction = self::SORT_ORDER_DESC)
- {
- return $this->_setOrder($field, $direction);
- }
- /**
- * Add select order to the beginning
- *
- * @param string $field
- * @param string $direction
- * @return $this
- */
- public function unshiftOrder($field, $direction = self::SORT_ORDER_DESC)
- {
- return $this->_setOrder($field, $direction, true);
- }
- /**
- * Add ORDER BY to the end or to the beginning
- *
- * @param string $field
- * @param string $direction
- * @param bool $unshift
- * @return $this
- */
- private function _setOrder($field, $direction, $unshift = false)
- {
- $this->_isOrdersRendered = false;
- $field = (string)$this->_getMappedField($field);
- $direction = strtoupper($direction) == self::SORT_ORDER_ASC ? self::SORT_ORDER_ASC : self::SORT_ORDER_DESC;
- unset($this->_orders[$field]);
- // avoid ordering by the same field twice
- if ($unshift) {
- $orders = [$field => $direction];
- foreach ($this->_orders as $key => $dir) {
- $orders[$key] = $dir;
- }
- $this->_orders = $orders;
- } else {
- $this->_orders[$field] = $direction;
- }
- return $this;
- }
- /**
- * Render sql select conditions
- *
- * @return $this
- */
- protected function _renderFilters()
- {
- if ($this->_isFiltersRendered) {
- return $this;
- }
- $this->_renderFiltersBefore();
- foreach ($this->_filters as $filter) {
- switch ($filter['type']) {
- case 'or':
- $condition = $this->_conn->quoteInto($filter['field'] . '=?', $filter['value']);
- $this->_select->orWhere($condition);
- break;
- case 'string':
- $this->_select->where($filter['value']);
- break;
- case 'public':
- $field = $this->_getMappedField($filter['field']);
- $condition = $filter['value'];
- $this->_select->where($this->_getConditionSql($field, $condition), null, Select::TYPE_CONDITION);
- break;
- default:
- $condition = $this->_conn->quoteInto($filter['field'] . '=?', $filter['value']);
- $this->_select->where($condition);
- }
- }
- $this->_isFiltersRendered = true;
- return $this;
- }
- /**
- * Hook for operations before rendering filters
- *
- * @return void
- */
- protected function _renderFiltersBefore()
- {
- }
- /**
- * Add field filter to collection
- *
- * @see self::_getConditionSql for $condition
- *
- * @param string|array $field
- * @param null|string|array $condition
- * @return $this
- */
- public function addFieldToFilter($field, $condition = null)
- {
- if (is_array($field)) {
- $conditions = [];
- foreach ($field as $key => $value) {
- $conditions[] = $this->_translateCondition($value, isset($condition[$key]) ? $condition[$key] : null);
- }
- $resultCondition = '(' . implode(') ' . \Magento\Framework\DB\Select::SQL_OR . ' (', $conditions) . ')';
- } else {
- $resultCondition = $this->_translateCondition($field, $condition);
- }
- $this->_select->where($resultCondition, null, Select::TYPE_CONDITION);
- return $this;
- }
- /**
- * Build sql where condition part
- *
- * @param string|array $field
- * @param null|string|array $condition
- * @return string
- */
- protected function _translateCondition($field, $condition)
- {
- $field = $this->_getMappedField($field);
- return $this->_getConditionSql($this->getConnection()->quoteIdentifier($field), $condition);
- }
- /**
- * Try to get mapped field name for filter to collection
- *
- * @param string $field
- * @return string
- */
- protected function _getMappedField($field)
- {
- $mapper = $this->_getMapper();
- if (isset($mapper['fields'][$field])) {
- $mappedField = $mapper['fields'][$field];
- } else {
- $mappedField = $field;
- }
- return $mappedField;
- }
- /**
- * Retrieve mapper data
- *
- * @return array|bool|null
- */
- protected function _getMapper()
- {
- if (isset($this->_map)) {
- return $this->_map;
- } else {
- return false;
- }
- }
- /**
- * Build SQL statement for condition
- *
- * If $condition integer or string - exact value will be filtered ('eq' condition)
- *
- * If $condition is array - one of the following structures is expected:
- * - array("from" => $fromValue, "to" => $toValue)
- * - array("eq" => $equalValue)
- * - array("neq" => $notEqualValue)
- * - array("like" => $likeValue)
- * - array("in" => array($inValues))
- * - array("nin" => array($notInValues))
- * - array("notnull" => $valueIsNotNull)
- * - array("null" => $valueIsNull)
- * - array("moreq" => $moreOrEqualValue)
- * - array("gt" => $greaterValue)
- * - array("lt" => $lessValue)
- * - array("gteq" => $greaterOrEqualValue)
- * - array("lteq" => $lessOrEqualValue)
- * - array("finset" => $valueInSet)
- * - array("regexp" => $regularExpression)
- * - array("seq" => $stringValue)
- * - array("sneq" => $stringValue)
- *
- * If non matched - sequential array is expected and OR conditions
- * will be built using above mentioned structure
- *
- * @param string $fieldName
- * @param integer|string|array $condition
- * @return string
- */
- protected function _getConditionSql($fieldName, $condition)
- {
- return $this->getConnection()->prepareSqlCondition($fieldName, $condition);
- }
- /**
- * Return the field name for the condition.
- *
- * @param string $fieldName
- * @return string
- */
- protected function _getConditionFieldName($fieldName)
- {
- return $fieldName;
- }
- /**
- * Render sql select orders
- *
- * @return $this
- */
- protected function _renderOrders()
- {
- if (!$this->_isOrdersRendered) {
- foreach ($this->_orders as $field => $direction) {
- $this->_select->order(new \Zend_Db_Expr($field . ' ' . $direction));
- }
- $this->_isOrdersRendered = true;
- }
- return $this;
- }
- /**
- * Render sql select limit
- *
- * @return $this
- */
- protected function _renderLimit()
- {
- if ($this->_pageSize) {
- $this->_select->limitPage($this->getCurPage(), $this->_pageSize);
- }
- return $this;
- }
- /**
- * Set select distinct
- *
- * @param bool $flag
- * @return $this
- */
- public function distinct($flag)
- {
- $this->_select->distinct($flag);
- return $this;
- }
- /**
- * Before load action
- *
- * @return $this
- */
- protected function _beforeLoad()
- {
- return $this;
- }
- /**
- * Load data
- *
- * @param bool $printQuery
- * @param bool $logQuery
- * @return $this
- */
- public function load($printQuery = false, $logQuery = false)
- {
- if ($this->isLoaded()) {
- return $this;
- }
- return $this->loadWithFilter($printQuery, $logQuery);
- }
- /**
- * Load data with filter in place
- *
- * @param bool $printQuery
- * @param bool $logQuery
- * @return $this
- */
- public function loadWithFilter($printQuery = false, $logQuery = false)
- {
- $this->_beforeLoad();
- $this->_renderFilters()->_renderOrders()->_renderLimit();
- $this->printLogQuery($printQuery, $logQuery);
- $data = $this->getData();
- $this->resetData();
- if (is_array($data)) {
- foreach ($data as $row) {
- $item = $this->getNewEmptyItem();
- if ($this->getIdFieldName()) {
- $item->setIdFieldName($this->getIdFieldName());
- }
- $item->addData($row);
- $this->beforeAddLoadedItem($item);
- $this->addItem($item);
- }
- }
- $this->_setIsLoaded();
- $this->_afterLoad();
- return $this;
- }
- /**
- * Let do something before add loaded item in collection
- *
- * @param \Magento\Framework\DataObject $item
- * @return \Magento\Framework\DataObject
- */
- protected function beforeAddLoadedItem(\Magento\Framework\DataObject $item)
- {
- return $item;
- }
- /**
- * Returns an items collection.
- * Returns a collection item that corresponds to the fetched row
- * and moves the internal data pointer ahead
- *
- * @return \Magento\Framework\Model\AbstractModel|bool
- */
- public function fetchItem()
- {
- if (null === $this->_fetchStmt) {
- $this->_renderOrders()->_renderLimit();
- $this->_fetchStmt = $this->getConnection()->query($this->getSelect());
- }
- $data = $this->_fetchStmt->fetch();
- if (!empty($data) && is_array($data)) {
- $item = $this->getNewEmptyItem();
- if ($this->getIdFieldName()) {
- $item->setIdFieldName($this->getIdFieldName());
- }
- $item->setData($data);
- return $item;
- }
- return false;
- }
- /**
- * Overridden to use _idFieldName by default.
- *
- * @param string|null $valueField
- * @param string $labelField
- * @param array $additional
- * @return array
- */
- protected function _toOptionArray($valueField = null, $labelField = 'name', $additional = [])
- {
- if ($valueField === null) {
- $valueField = $this->getIdFieldName();
- }
- return parent::_toOptionArray($valueField, $labelField, $additional);
- }
- /**
- * Overridden to use _idFieldName by default.
- *
- * @param string $valueField
- * @param string $labelField
- * @return array
- */
- protected function _toOptionHash($valueField = null, $labelField = 'name')
- {
- if ($valueField === null) {
- $valueField = $this->getIdFieldName();
- }
- return parent::_toOptionHash($valueField, $labelField);
- }
- /**
- * Get all data array for collection
- *
- * @return array
- */
- public function getData()
- {
- if ($this->_data === null) {
- $this->_renderFilters()->_renderOrders()->_renderLimit();
- $select = $this->getSelect();
- $this->_data = $this->_fetchAll($select);
- $this->_afterLoadData();
- }
- return $this->_data;
- }
- /**
- * Process loaded collection data
- *
- * @return $this
- */
- protected function _afterLoadData()
- {
- return $this;
- }
- /**
- * Reset loaded for collection data array
- *
- * @return $this
- */
- public function resetData()
- {
- $this->_data = null;
- return $this;
- }
- /**
- * Process loaded collection
- *
- * @return $this
- */
- protected function _afterLoad()
- {
- return $this;
- }
- /**
- * Load the data.
- *
- * @param bool $printQuery
- * @param bool $logQuery
- * @return $this
- */
- public function loadData($printQuery = false, $logQuery = false)
- {
- return $this->load($printQuery, $logQuery);
- }
- /**
- * Print and/or log query
- *
- * @param bool $printQuery
- * @param bool $logQuery
- * @param string $sql
- * @return $this
- */
- public function printLogQuery($printQuery = false, $logQuery = false, $sql = null)
- {
- if ($printQuery || $this->getFlag('print_query')) {
- echo $sql === null ? $this->getSelect()->__toString() : $sql;
- }
- if ($logQuery || $this->getFlag('log_query')) {
- $this->_logQuery($sql);
- }
- return $this;
- }
- /**
- * Log query
- *
- * @param string $sql
- * @return void
- */
- protected function _logQuery($sql)
- {
- $this->_logger->info($sql === null ? $this->getSelect()->__toString() : $sql);
- }
- /**
- * Reset collection
- *
- * @return $this
- */
- protected function _reset()
- {
- $this->getSelect()->reset();
- $this->_initSelect();
- $this->_setIsLoaded(false);
- $this->_items = [];
- $this->_data = null;
- $this->extensionAttributesJoinProcessor = null;
- return $this;
- }
- /**
- * Fetch collection data
- *
- * @param Select $select
- * @return array
- */
- protected function _fetchAll(Select $select)
- {
- $data = $this->_fetchStrategy->fetchAll($select, $this->_bindParams);
- if ($this->extensionAttributesJoinProcessor) {
- foreach ($data as $key => $dataItem) {
- $data[$key] = $this->extensionAttributesJoinProcessor->extractExtensionAttributes(
- $this->_itemObjectClass,
- $dataItem
- );
- }
- }
- return $data;
- }
- /**
- * Add filter to Map
- *
- * @param string $filter
- * @param string $alias
- * @param string $group default 'fields'
- * @return $this
- */
- public function addFilterToMap($filter, $alias, $group = 'fields')
- {
- if ($this->_map === null) {
- $this->_map = [$group => []];
- } elseif (empty($this->_map[$group])) {
- $this->_map[$group] = [];
- }
- $this->_map[$group][$filter] = $alias;
- return $this;
- }
- /**
- * Clone $this->_select during cloning collection, otherwise both collections will share the same $this->_select
- *
- * @return void
- */
- public function __clone()
- {
- if (is_object($this->_select)) {
- $this->_select = clone $this->_select;
- }
- }
- /**
- * Init select
- *
- * @return void
- */
- protected function _initSelect()
- {
- // no implementation, should be overridden in children classes
- }
- /**
- * Join extension attribute.
- *
- * @param JoinDataInterface $join
- * @param JoinProcessorInterface $extensionAttributesJoinProcessor
- * @return $this
- */
- public function joinExtensionAttribute(
- JoinDataInterface $join,
- JoinProcessorInterface $extensionAttributesJoinProcessor
- ) {
- $selectFrom = $this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM);
- $joinRequired = !isset($selectFrom[$join->getReferenceTableAlias()]);
- if ($joinRequired) {
- $joinOn = $this->getMainTableAlias() . '.' . $join->getJoinField()
- . ' = ' . $join->getReferenceTableAlias() . '.' . $join->getReferenceField();
- $this->getSelect()->joinLeft(
- [$join->getReferenceTableAlias() => $this->getResource()->getTable($join->getReferenceTable())],
- $joinOn,
- []
- );
- }
- $columns = [];
- foreach ($join->getSelectFields() as $selectField) {
- $fieldWIthDbPrefix = $selectField[JoinDataInterface::SELECT_FIELD_WITH_DB_PREFIX];
- $columns[$selectField[JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS]] = $fieldWIthDbPrefix;
- $this->addFilterToMap($selectField[JoinDataInterface::SELECT_FIELD_EXTERNAL_ALIAS], $fieldWIthDbPrefix);
- }
- $this->getSelect()->columns($columns);
- $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
- return $this;
- }
- /**
- * Get collection item object class name.
- *
- * @return string
- */
- public function getItemObjectClass()
- {
- return $this->_itemObjectClass;
- }
- /**
- * Identify main table alias or its name if alias is not defined.
- *
- * @return string
- * @throws \LogicException
- */
- private function getMainTableAlias()
- {
- foreach ($this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM) as $tableAlias => $tableMetadata) {
- if ($tableMetadata['joinType'] == 'from') {
- return $tableAlias;
- }
- }
- throw new \LogicException("Main table cannot be identified.");
- }
- /**
- * @inheritdoc
- * @since 100.0.11
- */
- public function __sleep()
- {
- return array_diff(
- parent::__sleep(),
- ['_fetchStrategy', '_logger', '_conn', 'extensionAttributesJoinProcessor']
- );
- }
- /**
- * @inheritdoc
- * @since 100.0.11
- */
- public function __wakeup()
- {
- parent::__wakeup();
- $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
- $this->_logger = $objectManager->get(Logger::class);
- $this->_fetchStrategy = $objectManager->get(FetchStrategyInterface::class);
- $this->_conn = $objectManager->get(ResourceConnection::class)->getConnection();
- }
- }
|