/** * 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]; }
/** * Save virtual values after an entity's real values were saved. * * @param \Cake\Event\Event $event The event that was triggered * @param \Cake\Datasource\EntityInterface $entity The entity that was saved * @param \ArrayObject $options Additional options given as an array * @return bool True always */ public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) { $attrsById = []; $updatedAttrs = []; $valuesTable = TableRegistry::get('Eav.EavValues'); foreach ($this->_toolbox->attributes() as $name => $attr) { if (!$this->_toolbox->propertyExists($entity, $name)) { continue; } $attrsById[$attr->get('id')] = $attr; } if (empty($attrsById)) { return true; // nothing to do } $values = $valuesTable->find()->where(['eav_attribute_id IN' => array_keys($attrsById), 'entity_id' => $this->_toolbox->getEntityId($entity)]); foreach ($values as $value) { $updatedAttrs[] = $value->get('eav_attribute_id'); $info = $attrsById[$value->get('eav_attribute_id')]; $type = $this->_toolbox->getType($info->get('name')); $marshaledValue = $this->_toolbox->marshal($entity->get($info->get('name')), $type); $value->set("value_{$type}", $marshaledValue); $entity->set($info->get('name'), $marshaledValue); $valuesTable->save($value); } foreach ($this->_toolbox->attributes() as $name => $attr) { if (!$this->_toolbox->propertyExists($entity, $name)) { continue; } if (!in_array($attr->get('id'), $updatedAttrs)) { $type = $this->_toolbox->getType($name); $value = $valuesTable->newEntity(['eav_attribute_id' => $attr->get('id'), 'entity_id' => $this->_toolbox->getEntityId($entity)]); $marshaledValue = $this->_toolbox->marshal($entity->get($name), $type); $value->set("value_{$type}", $marshaledValue); $entity->set($name, $marshaledValue); $valuesTable->save($value); } } if ($this->config('cacheMap')) { $this->updateEavCache($entity); } return true; }
/** * 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; }