ArrayHydrator.php 9.4 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 *  $Id$
 *
 * 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>.
20 21
 */

22 23
namespace Doctrine\ORM\Internal\Hydration;

24
use Doctrine\DBAL\Connection;
25

26
/**
27 28
 * The ArrayHydrator produces a nested array "graph" that is often (not always)
 * interchangeable with the corresponding object graph for read-only access.
29
 *
30 31
 * @author Roman Borschel <roman@code-factory.org>
 * @since 1.0
32
 */
33
class ArrayHydrator extends AbstractHydrator
34
{
35
    private $_ce = array();
36
    private $_rootAliases = array();
37 38 39 40 41 42 43
    private $_isSimpleQuery = false;
    private $_identifierMap = array();
    private $_resultPointers = array();
    private $_idTemplate = array();
    private $_resultCounter = 0;

    /** @override */
44
    protected function _prepare()
45
    {
46
        $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
47 48 49 50
        $this->_identifierMap = array();
        $this->_resultPointers = array();
        $this->_idTemplate = array();
        $this->_resultCounter = 0;
51
        foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
52 53 54 55 56 57 58
            $this->_identifierMap[$dqlAlias] = array();
            $this->_resultPointers[$dqlAlias] = array();
            $this->_idTemplate[$dqlAlias] = '';
        }
    }

    /** @override */
59
    protected function _hydrateAll()
60 61 62
    {
        $result = array();
        $cache = array();
63
        while ($data = $this->_stmt->fetch(Connection::FETCH_ASSOC)) {
64 65 66 67 68 69 70
            $this->_hydrateRow($data, $cache, $result);
        }

        return $result;
    }

    /** @override */
71
    protected function _hydrateRow(array &$data, array &$cache, array &$result)
72 73 74 75
    {
        // 1) Initialize
        $id = $this->_idTemplate; // initialize the id-memory
        $nonemptyComponents = array();
76
        $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
77 78 79 80 81 82 83 84 85 86 87 88

        // Extract scalar values. They're appended at the end.
        if (isset($rowData['scalars'])) {
            $scalars = $rowData['scalars'];
            unset($rowData['scalars']);
        }

        // 3) Now hydrate the rest of the data found in the current row, that
        // belongs to other (related) entities.
        foreach ($rowData as $dqlAlias => $data) {
            $index = false;

89
            if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
90
                // It's a joined result
91

92
                $parent = $this->_rsm->parentAliasMap[$dqlAlias];
93 94 95 96
                $path = $parent . '.' . $dqlAlias;

                // Get a reference to the right element in the result tree.
                // This element will get the associated element attached.
97
                if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
98
                	$first = reset($this->_resultPointers);
99
                    // TODO: Exception if $key === null ?
100
                    $baseElement =& $this->_resultPointers[$parent][key($first)];
101 102 103 104 105 106
                } else if (isset($this->_resultPointers[$parent])) {
                    $baseElement =& $this->_resultPointers[$parent];
                } else {
                    unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
                    continue;
                }
107
                
108 109
                $relationAlias = $this->_rsm->relationMap[$dqlAlias];
                $relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
110 111 112 113 114 115 116 117

                // Check the type of the relation (many or single-valued)
                if ( ! $relation->isOneToOne()) {
                    $oneToOne = false;
                    if (isset($nonemptyComponents[$dqlAlias])) {
                        if ( ! isset($baseElement[$relationAlias])) {
                            $baseElement[$relationAlias] = array();
                        }
118
                        
119 120 121
                        $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;
122
                        
123 124
                        if ( ! $indexExists || ! $indexIsValid) {
                            $element = $data;
125 126
                            if (isset($this->_rsm->indexByMap[$dqlAlias])) {
                                $field = $this->_rsm->indexByMap[$dqlAlias];
127 128 129 130 131 132 133 134 135
                                $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])) {
136 137
                        $baseElement[$relationAlias] = array();
                    }
138 139 140 141 142 143
                } else {
                    $oneToOne = true;
                    if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
                        $baseElement[$relationAlias] = null;
                    } else if ( ! isset($baseElement[$relationAlias])) {
                        $baseElement[$relationAlias] = $data;
144 145 146
                    }
                }

147 148 149 150 151
                $coll =& $baseElement[$relationAlias];

                if ($coll !== null) {
                    $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
                }
152

153
            } else {
154 155
                // It's a root result element
                
156
                $this->_rootAliases[$dqlAlias] = true; // Mark as root
157
                
158 159 160
                // Check for an existing element
                if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
                    $element = $rowData[$dqlAlias];
161 162
                    if (isset($this->_rsm->indexByMap[$dqlAlias])) {
                        $field = $this->_rsm->indexByMap[$dqlAlias];
163
                        if ($this->_rsm->isMixed) {
164 165 166 167 168 169
                            $result[] = array($element[$field] => $element);
                            ++$this->_resultCounter;
                        } else {
                            $result[$element[$field]] = $element;
                        }
                    } else {
170
                        if ($this->_rsm->isMixed) {
171 172 173 174 175 176 177 178 179 180 181 182
                            $result[] = array($element);
                            ++$this->_resultCounter;
                        } else {
                            $result[] = $element;
                        }
                    }
                    end($result);
                    $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
                } else {
                    $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
                }
                $this->updateResultPointer($result, $index, $dqlAlias, false);
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
            }
        }

        // 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.
     */
203
    private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
    {
        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)];
                }
            }
        }
    }
223 224 225 226 227 228 229 230
    
    private function _getClassMetadata($className)
    {
        if ( ! isset($this->_ce[$className])) {
            $this->_ce[$className] = $this->_em->getClassMetadata($className);
        }
        return $this->_ce[$className];
    }
231
}