/** * Generates a SQL sub-query for replacing in ORDER BY clause. * * @param string $column Name of the column being replaced by this sub-query * @param string|null $bundle Consider attributes only for a specific bundle * @return string SQL sub-query statement */ protected function _subQuery($column, $bundle = null) { $alias = $this->_table->alias(); $pk = $this->_table->primaryKey(); $type = $this->_toolbox->getType($column); $subConditions = ['EavAttribute.table_alias' => $this->_table->table(), 'EavValues.entity_id' => "{$alias}.{$pk}", 'EavAttribute.name' => $column]; if (!empty($bundle)) { $subConditions['EavAttribute.bundle'] = $bundle; } $subQuery = TableRegistry::get('Eav.EavValues')->find()->contain(['EavAttribute'])->select(["EavValues.value_{$type}"])->where($subConditions)->sql(); return str_replace([':c0', ':c1', ':c2', ':c3'], ['"' . $this->_table->table() . '"', "{$alias}.{$pk}", '"' . $column . '"', '"' . $bundle . '"'], $subQuery); }
/** * Gets a list of all virtual columns present in given $query's SELECT clause. * * This method will alter the given Query object removing any virtual column * present in its SELECT clause in order to avoid incorrect SQL statements. * Selected virtual columns should be fetched after query is executed using * mapReduce or similar. * * @param \Cake\ORM\Query $query The query object to be scoped * @param string|null $bundle Consider attributes only for a specific bundle * @return array List of virtual columns names */ public function getVirtualColumns(Query $query, $bundle = null) { static $selectedVirtual = []; $cacheKey = md5($query->sql()) . '_' . $bundle; if (isset($selectedVirtual[$cacheKey])) { return $selectedVirtual[$cacheKey]; } $selectClause = (array) $query->clause('select'); if (empty($selectClause)) { $selectedVirtual[$cacheKey] = array_keys($this->_toolbox->attributes($bundle)); return $selectedVirtual[$cacheKey]; } $selectedVirtual[$cacheKey] = []; $virtualColumns = array_keys($this->_toolbox->attributes($bundle)); foreach ($selectClause as $index => $column) { list($table, $column) = pluginSplit($column); if ((empty($table) || $table == $this->_table->alias()) && in_array($column, $virtualColumns)) { $selectedVirtual[$cacheKey][$index] = $column; unset($selectClause[$index]); } } if (empty($selectClause) && !empty($selectedVirtual[$cacheKey])) { $selectClause[] = $this->_table->primaryKey(); } $query->select($selectClause, true); return $selectedVirtual[$cacheKey]; }
/** * After an entity was removed from database. Here is when EAV values are * removed from DB. * * @param \Cake\Event\Event $event The event that was triggered * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted * @param \ArrayObject $options Additional options given as an array * @throws \Cake\Error\FatalErrorException When using this behavior in non-atomic mode * @return void */ public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $options) { if (!$options['atomic']) { throw new FatalErrorException(__d('eav', 'Entities in fieldable tables can only be deleted using transactions. Set [atomic = true]')); } $valuesToDelete = TableRegistry::get('Eav.EavValues')->find()->contain('EavAttribute')->where(['EavAttribute.table_alias' => $this->_table->table(), 'EavValues.entity_id' => $this->_toolbox->getEntityId($entity)]); foreach ($valuesToDelete as $value) { TableRegistry::get('Eav.EavValues')->delete($value); } }
/** * Analyzes the given unary expression and alters it according. * * @param \Cake\Database\Expression\UnaryExpression $expression Unary expression * @param string $bundle Consider attributes only for a specific bundle * @param \Cake\ORM\Query $query The query instance this expression comes from * @return \Cake\Database\Expression\UnaryExpression Scoped expression (or not) */ protected function _inspectUnaryExpression(UnaryExpression $expression, $bundle, Query $query) { $class = new \ReflectionClass($expression); $property = $class->getProperty('_value'); $property->setAccessible(true); $value = $property->getValue($expression); if ($value instanceof IdentifierExpression) { $field = $value->getIdentifier(); $column = is_string($field) ? $this->_toolbox->columnName($field) : ''; if (empty($column) || in_array($column, (array) $this->_table->schema()->columns()) || !in_array($column, $this->_toolbox->getAttributeNames($bundle)) || !$this->_toolbox->isSearchable($column)) { // nothing to alter return $expression; } $pk = $this->_tablePrimaryKey(); $driverClass = $this->_driverClass($query); switch ($driverClass) { case 'sqlite': $concat = implode(' || ', $pk); $field = "({$concat} || '')"; break; case 'mysql': case 'postgres': case 'sqlserver': default: $concat = implode(', ', $pk); $field = "CONCAT({$concat}, '')"; break; } $attr = $this->_toolbox->attributes($bundle)[$column]; $type = $this->_toolbox->getType($column); $subQuery = TableRegistry::get('Eav.EavValues')->find()->select("EavValues.value_{$type}")->where(['EavValues.entity_id' => $field, 'EavValues.eav_attribute_id' => $attr['id']])->sql(); $subQuery = str_replace([':c0', ':c1'], [$field, $attr['id']], $subQuery); $property->setValue($expression, "({$subQuery})"); } return $expression; }