Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: NestedSet.php 7490 2010-03-29 19:53:27Z jwage $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.doctrine-project.org>.
20
 */
21
 
22
/**
23
 * Doctrine_Tree_NestedSet
24
 *
25
 * @package     Doctrine
26
 * @subpackage  Tree
27
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
28
 * @link        www.doctrine-project.org
29
 * @since       1.0
30
 * @version     $Revision: 7490 $
31
 * @author      Joe Simms <joe.simms@websites4.com>
32
 * @author      Roman Borschel <roman@code-factory.org>
33
 */
34
class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Interface
35
{
36
    private $_baseQuery;
37
    private $_baseAlias = "base";
38
 
39
    /**
40
     * constructor, creates tree with reference to table and sets default root options
41
     *
42
     * @param object $table                     instance of Doctrine_Table
43
     * @param array $options                    options
44
     */
45
    public function __construct(Doctrine_Table $table, $options)
46
    {
47
        // set default many root attributes
48
        $options['hasManyRoots'] = isset($options['hasManyRoots']) ? $options['hasManyRoots'] : false;
49
        if ($options['hasManyRoots']) {
50
            $options['rootColumnName'] = isset($options['rootColumnName']) ? $options['rootColumnName'] : 'root_id';
51
        }
52
 
53
        parent::__construct($table, $options);
54
    }
55
 
56
    /**
57
     * used to define table attributes required for the NestetSet implementation
58
     * adds lft and rgt columns for corresponding left and right values
59
     *
60
     */
61
    public function setTableDefinition()
62
    {
63
        if (($root = $this->getAttribute('rootColumnName')) && (!$this->table->hasColumn($root))) {
64
            $this->table->setColumn($root, 'integer');
65
        }
66
 
67
        $this->table->setColumn('lft', 'integer', 4);
68
        $this->table->setColumn('rgt', 'integer', 4);
69
        if ($level = $this->getAttribute('levelColumnName')) {
70
            $this->table->setColumn($level . ' AS level', 'integer', 2);
71
        } else {
72
            $this->table->setColumn('level', 'integer', 2);
73
        }
74
    }
75
 
76
    /**
77
     * Creates root node from given record or from a new record.
78
     *
79
     * Note: When using a tree with multiple root nodes (hasManyRoots), you MUST pass in a
80
     * record to use as the root. This can either be a new/transient record that already has
81
     * the root id column set to some numeric value OR a persistent record. In the latter case
82
     * the records id will be assigned to the root id. You must use numeric columns for the id
83
     * and root id columns.
84
     *
85
     * @param object $record        instance of Doctrine_Record
86
     */
87
    public function createRoot(Doctrine_Record $record = null)
88
    {
89
        if ($this->getAttribute('hasManyRoots')) {
90
            if ( ! $record || ( ! $record->exists() && ! $record->getNode()->getRootValue())
91
                    || $record->getTable()->isIdentifierComposite()) {
92
                throw new Doctrine_Tree_Exception("Node must have a root id set or must "
93
                        . " be persistent and have a single-valued numeric primary key in order to"
94
                        . " be created as a root node. Automatic assignment of a root id on"
95
                        . " transient/new records is no longer supported.");
96
            }
97
 
98
            if ($record->exists() && ! $record->getNode()->getRootValue()) {
99
                // Default: root_id = id
100
                $identifier = $record->getTable()->getIdentifier();
101
                $record->getNode()->setRootValue($record->get($identifier));
102
            }
103
        }
104
 
105
        if ( ! $record) {
106
            $record = $this->table->create();
107
        }
108
 
109
        $record->set('lft', '1');
110
        $record->set('rgt', '2');
111
        $record->set('level', 0);
112
 
113
        $record->save();
114
 
115
        return $record;
116
    }
117
 
118
    /**
119
     * Fetches a/the root node.
120
     *
121
     * @param integer $rootId
122
     * @todo Better $rootid = null and exception if $rootId == null && hasManyRoots?
123
     *       Fetching with id = 1 is too magical and cant work reliably anyway.
124
     */
125
    public function fetchRoot($rootId = 1)
126
    {
127
        $q = $this->getBaseQuery();
128
        $q = $q->addWhere($this->_baseAlias . '.lft = ?', 1);
129
 
130
        // if tree has many roots, then specify root id
131
        $q = $this->returnQueryWithRootId($q, $rootId);
132
        $data = $q->execute();
133
 
134
        if (count($data) <= 0) {
135
            return false;
136
        }
137
 
138
        if ($data instanceof Doctrine_Collection) {
139
            $root = $data->getFirst();
140
            $root['level'] = 0;
141
        } else if (is_array($data)) {
142
            $root = array_shift($data);
143
            $root['level'] = 0;
144
        } else {
145
            throw new Doctrine_Tree_Exception("Unexpected data structure returned.");
146
        }
147
 
148
        return $root;
149
    }
150
 
151
    /**
152
     * Fetches a tree.
153
     *
154
     * @param array $options  Options
155
     * @param integer $fetchmode  One of the Doctrine_Core::HYDRATE_* constants.
156
     * @return mixed          The tree or FALSE if the tree could not be found.
157
     */
158
    public function fetchTree($options = array(), $hydrationMode = null)
159
    {
160
        // fetch tree
161
        $q = $this->getBaseQuery();
162
 
163
        $depth = isset($options['depth']) ? $options['depth'] : null;
164
 
165
        $q->addWhere($this->_baseAlias . ".lft >= ?", 1);
166
 
167
        // if tree has many roots, then specify root id
168
        $rootId = isset($options['root_id']) ? $options['root_id'] : '1';
169
        if (is_array($rootId)) {
170
            $q->addOrderBy($this->_baseAlias . "." . $this->getAttribute('rootColumnName') .
171
                    ", " . $this->_baseAlias . ".lft ASC");
172
        } else {
173
            $q->addOrderBy($this->_baseAlias . ".lft ASC");
174
        }
175
 
176
        if ( ! is_null($depth)) {
177
            $q->addWhere($this->_baseAlias . ".level BETWEEN ? AND ?", array(0, $depth));
178
        }
179
 
180
        $q = $this->returnQueryWithRootId($q, $rootId);
181
 
182
        $tree = $q->execute(array(), $hydrationMode);
183
 
184
        if (count($tree) <= 0) {
185
            return false;
186
        }
187
 
188
        return $tree;
189
    }
190
 
191
    /**
192
     * Fetches a branch of a tree.
193
     *
194
     * @param mixed $pk              primary key as used by table::find() to locate node to traverse tree from
195
     * @param array $options         Options.
196
     * @param integer $fetchmode  One of the Doctrine_Core::HYDRATE_* constants.
197
     * @return mixed                 The branch or FALSE if the branch could not be found.
198
     * @todo Only fetch the lft and rgt values of the initial record. more is not needed.
199
     */
200
    public function fetchBranch($pk, $options = array(), $hydrationMode = null)
201
    {
202
        $record = $this->table->find($pk);
203
        if ( ! ($record instanceof Doctrine_Record) || !$record->exists()) {
204
            // TODO: if record doesn't exist, throw exception or similar?
205
            return false;
206
        }
207
 
208
        $depth = isset($options['depth']) ? $options['depth'] : null;
209
 
210
        $q = $this->getBaseQuery();
211
        $params = array($record->get('lft'), $record->get('rgt'));
212
        $q->addWhere($this->_baseAlias . ".lft >= ? AND " . $this->_baseAlias . ".rgt <= ?", $params)
213
                ->addOrderBy($this->_baseAlias . ".lft asc");
214
 
215
        if ( ! is_null($depth)) {
216
            $q->addWhere($this->_baseAlias . ".level BETWEEN ? AND ?", array($record->get('level'), $record->get('level')+$depth));
217
        }
218
 
219
        $q = $this->returnQueryWithRootId($q, $record->getNode()->getRootValue());
220
 
221
        return $q->execute(array(), $hydrationMode);
222
    }
223
 
224
    /**
225
     * Fetches all root nodes. If the tree has only one root this is the same as
226
     * fetchRoot().
227
     *
228
     * @return mixed  The root nodes.
229
     */
230
    public function fetchRoots()
231
    {
232
        $q = $this->getBaseQuery();
233
        $q = $q->addWhere($this->_baseAlias . '.lft = ?', 1);
234
        return $q->execute();
235
    }
236
 
237
    /**
238
     * returns parsed query with root id where clause added if applicable
239
     *
240
     * @param object    $query    Doctrine_Query
241
     * @param integer   $root_id  id of destination root
242
     * @return Doctrine_Query
243
     */
244
    public function returnQueryWithRootId($query, $rootId = 1)
245
    {
246
        if ($root = $this->getAttribute('rootColumnName')) {
247
            if (is_array($rootId)) {
248
               $query->addWhere($root . ' IN (' . implode(',', array_fill(0, count($rootId), '?')) . ')',
249
                       $rootId);
250
            } else {
251
               $query->addWhere($root . ' = ?', $rootId);
252
            }
253
        }
254
 
255
        return $query;
256
    }
257
 
258
    /**
259
     * Enter description here...
260
     *
261
     * @param array $options
262
     * @return unknown
263
     */
264
    public function getBaseQuery()
265
    {
266
        if ( ! isset($this->_baseQuery)) {
267
            $this->_baseQuery = $this->_createBaseQuery();
268
        }
269
        return $this->_baseQuery->copy();
270
    }
271
 
272
    /**
273
     * Enter description here...
274
     *
275
     */
276
    public function getBaseAlias()
277
    {
278
        return $this->_baseAlias;
279
    }
280
 
281
    /**
282
     * Enter description here...
283
     *
284
     */
285
    private function _createBaseQuery()
286
    {
287
        $this->_baseAlias = "base";
288
        $q = Doctrine_Core::getTable($this->getBaseComponent())
289
            ->createQuery($this->_baseAlias)
290
            ->select($this->_baseAlias . '.*');
291
        return $q;
292
    }
293
 
294
    /**
295
     * Enter description here...
296
     *
297
     * @param Doctrine_Query $query
298
     */
299
    public function setBaseQuery(Doctrine_Query $query)
300
    {
301
        $this->_baseAlias = $query->getRootAlias();
302
        $query->addSelect($this->_baseAlias . ".lft, " . $this->_baseAlias . ".rgt, ". $this->_baseAlias . ".level");
303
        if ($this->getAttribute('rootColumnName')) {
304
            $query->addSelect($this->_baseAlias . "." . $this->getAttribute('rootColumnName'));
305
        }
306
        $this->_baseQuery = $query;
307
    }
308
 
309
    /**
310
     * Enter description here...
311
     *
312
     */
313
    public function resetBaseQuery()
314
    {
315
        $this->_baseQuery = $this->_createBaseQuery();
316
    }
317
}