This case likeli only occurs when doing native queries. A guard clause that prevents hydration from breaking when RIGHT JOIN queries with null root entities appear has been added aswell.
251 lines
No EOL
10 KiB
PHP
251 lines
No EOL
10 KiB
PHP
<?php
|
|
/*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* This software consists of voluntary contributions made by many individuals
|
|
* and is licensed under the LGPL. For more information, see
|
|
* <http://www.doctrine-project.org>.
|
|
*/
|
|
|
|
namespace Doctrine\ORM\Internal\Hydration;
|
|
|
|
use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
|
|
|
|
/**
|
|
* The ArrayHydrator produces a nested array "graph" that is often (not always)
|
|
* interchangeable with the corresponding object graph for read-only access.
|
|
*
|
|
* @author Roman Borschel <roman@code-factory.org>
|
|
* @since 1.0
|
|
*/
|
|
class ArrayHydrator extends AbstractHydrator
|
|
{
|
|
private $_ce = array();
|
|
private $_rootAliases = array();
|
|
private $_isSimpleQuery = false;
|
|
private $_identifierMap = array();
|
|
private $_resultPointers = array();
|
|
private $_idTemplate = array();
|
|
private $_resultCounter = 0;
|
|
|
|
/** @override */
|
|
protected function _prepare()
|
|
{
|
|
$this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
|
|
$this->_identifierMap = array();
|
|
$this->_resultPointers = array();
|
|
$this->_idTemplate = array();
|
|
$this->_resultCounter = 0;
|
|
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
|
|
$this->_identifierMap[$dqlAlias] = array();
|
|
$this->_resultPointers[$dqlAlias] = array();
|
|
$this->_idTemplate[$dqlAlias] = '';
|
|
}
|
|
}
|
|
|
|
/** @override */
|
|
protected function _hydrateAll()
|
|
{
|
|
$result = array();
|
|
$cache = array();
|
|
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
$this->_hydrateRow($data, $cache, $result);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/** @override */
|
|
protected function _hydrateRow(array $data, array &$cache, array &$result)
|
|
{
|
|
// 1) Initialize
|
|
$id = $this->_idTemplate; // initialize the id-memory
|
|
$nonemptyComponents = array();
|
|
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
|
|
|
|
// Extract scalar values. They're appended at the end.
|
|
if (isset($rowData['scalars'])) {
|
|
$scalars = $rowData['scalars'];
|
|
unset($rowData['scalars']);
|
|
if (empty($rowData)) {
|
|
++$this->_resultCounter;
|
|
}
|
|
}
|
|
|
|
// 2) Now hydrate the data found in the current row.
|
|
foreach ($rowData as $dqlAlias => $data) {
|
|
$index = false;
|
|
|
|
if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
|
|
// It's a joined result
|
|
|
|
$parent = $this->_rsm->parentAliasMap[$dqlAlias];
|
|
$path = $parent . '.' . $dqlAlias;
|
|
|
|
// missing parent data, skipping as RIGHT JOIN hydration is not supported.
|
|
if ( ! isset($nonemptyComponents[$parent]) ) {
|
|
continue;
|
|
}
|
|
|
|
// Get a reference to the right element in the result tree.
|
|
// This element will get the associated element attached.
|
|
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
|
|
$first = reset($this->_resultPointers);
|
|
// TODO: Exception if $key === null ?
|
|
$baseElement =& $this->_resultPointers[$parent][key($first)];
|
|
} else if (isset($this->_resultPointers[$parent])) {
|
|
$baseElement =& $this->_resultPointers[$parent];
|
|
} else {
|
|
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
|
|
continue;
|
|
}
|
|
|
|
$relationAlias = $this->_rsm->relationMap[$dqlAlias];
|
|
$relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
|
|
|
|
// Check the type of the relation (many or single-valued)
|
|
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
|
|
$oneToOne = false;
|
|
if (isset($nonemptyComponents[$dqlAlias])) {
|
|
if ( ! isset($baseElement[$relationAlias])) {
|
|
$baseElement[$relationAlias] = array();
|
|
}
|
|
|
|
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
|
|
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
|
|
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
|
|
|
|
if ( ! $indexExists || ! $indexIsValid) {
|
|
$element = $data;
|
|
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
|
|
$field = $this->_rsm->indexByMap[$dqlAlias];
|
|
$baseElement[$relationAlias][$element[$field]] = $element;
|
|
} else {
|
|
$baseElement[$relationAlias][] = $element;
|
|
}
|
|
end($baseElement[$relationAlias]);
|
|
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
|
|
key($baseElement[$relationAlias]);
|
|
}
|
|
} else if ( ! isset($baseElement[$relationAlias])) {
|
|
$baseElement[$relationAlias] = array();
|
|
}
|
|
} else {
|
|
$oneToOne = true;
|
|
if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
|
|
$baseElement[$relationAlias] = null;
|
|
} else if ( ! isset($baseElement[$relationAlias])) {
|
|
$baseElement[$relationAlias] = $data;
|
|
}
|
|
}
|
|
|
|
$coll =& $baseElement[$relationAlias];
|
|
|
|
if ($coll !== null) {
|
|
$this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
|
|
}
|
|
|
|
} else {
|
|
// It's a root result element
|
|
|
|
$this->_rootAliases[$dqlAlias] = true; // Mark as root
|
|
|
|
// if this row has a NULL value for the root result id then make it a null result.
|
|
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
|
|
if ($this->_rsm->isMixed) {
|
|
$result[] = array(0 => null);
|
|
} else {
|
|
$result[] = null;
|
|
}
|
|
++$this->_resultCounter;
|
|
continue;
|
|
}
|
|
|
|
// Check for an existing element
|
|
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
|
|
$element = $rowData[$dqlAlias];
|
|
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
|
|
$field = $this->_rsm->indexByMap[$dqlAlias];
|
|
if ($this->_rsm->isMixed) {
|
|
$result[] = array($element[$field] => $element);
|
|
++$this->_resultCounter;
|
|
} else {
|
|
$result[$element[$field]] = $element;
|
|
}
|
|
} else {
|
|
if ($this->_rsm->isMixed) {
|
|
$result[] = array($element);
|
|
++$this->_resultCounter;
|
|
} else {
|
|
$result[] = $element;
|
|
}
|
|
}
|
|
end($result);
|
|
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
|
|
} else {
|
|
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
|
|
/*if ($this->_rsm->isMixed) {
|
|
$result[] =& $result[$index];
|
|
++$this->_resultCounter;
|
|
}*/
|
|
}
|
|
$this->updateResultPointer($result, $index, $dqlAlias, false);
|
|
}
|
|
}
|
|
|
|
// Append scalar values to mixed result sets
|
|
if (isset($scalars)) {
|
|
foreach ($scalars as $name => $value) {
|
|
$result[$this->_resultCounter - 1][$name] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the result pointer for an Entity. The result pointers point to the
|
|
* last seen instance of each Entity type. This is used for graph construction.
|
|
*
|
|
* @param array $coll The element.
|
|
* @param boolean|integer $index Index of the element in the collection.
|
|
* @param string $dqlAlias
|
|
* @param boolean $oneToOne Whether it is a single-valued association or not.
|
|
*/
|
|
private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
|
|
{
|
|
if ($coll === null) {
|
|
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
|
|
return;
|
|
}
|
|
if ($index !== false) {
|
|
$this->_resultPointers[$dqlAlias] =& $coll[$index];
|
|
return;
|
|
} else {
|
|
if ($coll) {
|
|
if ($oneToOne) {
|
|
$this->_resultPointers[$dqlAlias] =& $coll;
|
|
} else {
|
|
end($coll);
|
|
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private function _getClassMetadata($className)
|
|
{
|
|
if ( ! isset($this->_ce[$className])) {
|
|
$this->_ce[$className] = $this->_em->getClassMetadata($className);
|
|
}
|
|
return $this->_ce[$className];
|
|
}
|
|
} |