source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/AbstractQuery.php @ 356

Last change on this file since 356 was 345, checked in by quyenla, 11 years ago

collaborator page

File size: 23.4 KB
Line 
1<?php
2/*
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 *
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the LGPL. For more information, see
17 * <http://www.doctrine-project.org>.
18 */
19
20namespace Doctrine\ORM;
21
22use Doctrine\DBAL\Types\Type,
23    Doctrine\DBAL\Cache\QueryCacheProfile,
24    Doctrine\ORM\Query\QueryException,
25    Doctrine\ORM\Internal\Hydration\CacheHydrator;
26
27/**
28 * Base contract for ORM queries. Base class for Query and NativeQuery.
29 *
30 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
31 * @link    www.doctrine-project.org
32 * @since   2.0
33 * @author  Benjamin Eberlei <kontakt@beberlei.de>
34 * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
35 * @author  Jonathan Wage <jonwage@gmail.com>
36 * @author  Roman Borschel <roman@code-factory.org>
37 * @author  Konsta Vesterinen <kvesteri@cc.hut.fi>
38 */
39abstract class AbstractQuery
40{
41    /* Hydration mode constants */
42    /**
43     * Hydrates an object graph. This is the default behavior.
44     */
45    const HYDRATE_OBJECT = 1;
46    /**
47     * Hydrates an array graph.
48     */
49    const HYDRATE_ARRAY = 2;
50    /**
51     * Hydrates a flat, rectangular result set with scalar values.
52     */
53    const HYDRATE_SCALAR = 3;
54    /**
55     * Hydrates a single scalar value.
56     */
57    const HYDRATE_SINGLE_SCALAR = 4;
58
59    /**
60     * Very simple object hydrator (optimized for performance).
61     */
62    const HYDRATE_SIMPLEOBJECT = 5;
63
64    /**
65     * @var array The parameter map of this query.
66     */
67    protected $_params = array();
68
69    /**
70     * @var array The parameter type map of this query.
71     */
72    protected $_paramTypes = array();
73
74    /**
75     * @var ResultSetMapping The user-specified ResultSetMapping to use.
76     */
77    protected $_resultSetMapping;
78
79    /**
80     * @var \Doctrine\ORM\EntityManager The entity manager used by this query object.
81     */
82    protected $_em;
83
84    /**
85     * @var array The map of query hints.
86     */
87    protected $_hints = array();
88
89    /**
90     * @var integer The hydration mode.
91     */
92    protected $_hydrationMode = self::HYDRATE_OBJECT;
93
94    /**
95     * @param \Doctrine\DBAL\Cache\QueryCacheProfile
96     */
97    protected $_queryCacheProfile;
98
99    /**
100     * @var boolean Boolean value that indicates whether or not expire the result cache.
101     */
102    protected $_expireResultCache = false;
103
104    /**
105     * @param \Doctrine\DBAL\Cache\QueryCacheProfile
106     */
107    protected $_hydrationCacheProfile;
108
109    /**
110     * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
111     *
112     * @param \Doctrine\ORM\EntityManager $entityManager
113     */
114    public function __construct(EntityManager $em)
115    {
116        $this->_em = $em;
117    }
118
119    /**
120     * Retrieves the associated EntityManager of this Query instance.
121     *
122     * @return \Doctrine\ORM\EntityManager
123     */
124    public function getEntityManager()
125    {
126        return $this->_em;
127    }
128
129    /**
130     * Frees the resources used by the query object.
131     *
132     * Resets Parameters, Parameter Types and Query Hints.
133     *
134     * @return void
135     */
136    public function free()
137    {
138        $this->_params = array();
139        $this->_paramTypes = array();
140        $this->_hints = array();
141    }
142
143    /**
144     * Get all defined parameters.
145     *
146     * @return array The defined query parameters.
147     */
148    public function getParameters()
149    {
150        return $this->_params;
151    }
152
153    /**
154     * Get all defined parameter types.
155     *
156     * @return array The defined query parameter types.
157     */
158    public function getParameterTypes()
159    {
160        return $this->_paramTypes;
161    }
162
163    /**
164     * Gets a query parameter.
165     *
166     * @param mixed $key The key (index or name) of the bound parameter.
167     * @return mixed The value of the bound parameter.
168     */
169    public function getParameter($key)
170    {
171        if (isset($this->_params[$key])) {
172            return $this->_params[$key];
173        }
174
175        return null;
176    }
177
178    /**
179     * Gets a query parameter type.
180     *
181     * @param mixed $key The key (index or name) of the bound parameter.
182     * @return mixed The parameter type of the bound parameter.
183     */
184    public function getParameterType($key)
185    {
186        if (isset($this->_paramTypes[$key])) {
187            return $this->_paramTypes[$key];
188        }
189
190        return null;
191    }
192
193    /**
194     * Gets the SQL query that corresponds to this query object.
195     * The returned SQL syntax depends on the connection driver that is used
196     * by this query object at the time of this method call.
197     *
198     * @return string SQL query
199     */
200    abstract public function getSQL();
201
202    /**
203     * Sets a query parameter.
204     *
205     * @param string|integer $key The parameter position or name.
206     * @param mixed $value The parameter value.
207     * @param string $type The parameter type. If specified, the given value will be run through
208     *                     the type conversion of this type. This is usually not needed for
209     *                     strings and numeric types.
210     * @return \Doctrine\ORM\AbstractQuery This query instance.
211     */
212    public function setParameter($key, $value, $type = null)
213    {
214        $key = trim($key, ':');
215
216        $value = $this->processParameterValue($value);
217        if ($type === null) {
218            $type = Query\ParameterTypeInferer::inferType($value);
219        }
220
221        $this->_paramTypes[$key] = $type;
222        $this->_params[$key] = $value;
223
224        return $this;
225    }
226
227    /**
228     * Process an individual parameter value
229     *
230     * @param mixed $value
231     * @return array
232     */
233    private function processParameterValue($value)
234    {
235        switch (true) {
236            case is_array($value):
237                for ($i = 0, $l = count($value); $i < $l; $i++) {
238                    $paramValue = $this->processParameterValue($value[$i]);
239                    $value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
240                }
241
242                return $value;
243
244            case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
245                return $this->convertObjectParameterToScalarValue($value);
246
247            default:
248                return $value;
249        }
250    }
251
252    protected function convertObjectParameterToScalarValue($value)
253    {
254        $class = $this->_em->getClassMetadata(get_class($value));
255
256        if ($class->isIdentifierComposite) {
257            throw new \InvalidArgumentException("Binding an entity with a composite primary key to a query is not supported. You should split the parameter into the explicit fields and bind them seperately.");
258        }
259
260        if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
261            $values = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
262        } else {
263            $values = $class->getIdentifierValues($value);
264        }
265
266        $value = $values[$class->getSingleIdentifierFieldName()];
267        if (!$value) {
268            throw new \InvalidArgumentException("Binding entities to query parameters only allowed for entities that have an identifier.");
269        }
270
271        return $value;
272    }
273
274    /**
275     * Sets a collection of query parameters.
276     *
277     * @param array $params
278     * @param array $types
279     * @return \Doctrine\ORM\AbstractQuery This query instance.
280     */
281    public function setParameters(array $params, array $types = array())
282    {
283        foreach ($params as $key => $value) {
284            $this->setParameter($key, $value, isset($types[$key]) ? $types[$key] : null);
285        }
286
287        return $this;
288    }
289
290    /**
291     * Sets the ResultSetMapping that should be used for hydration.
292     *
293     * @param ResultSetMapping $rsm
294     * @return \Doctrine\ORM\AbstractQuery
295     */
296    public function setResultSetMapping(Query\ResultSetMapping $rsm)
297    {
298        $this->_resultSetMapping = $rsm;
299
300        return $this;
301    }
302
303    /**
304     * Set a cache profile for hydration caching.
305     *
306     * If no result cache driver is set in the QueryCacheProfile, the default
307     * result cache driver is used from the configuration.
308     *
309     * Important: Hydration caching does NOT register entities in the
310     * UnitOfWork when retrieved from the cache. Never use result cached
311     * entities for requests that also flush the EntityManager. If you want
312     * some form of caching with UnitOfWork registration you should use
313     * {@see AbstractQuery::setResultCacheProfile()}.
314     *
315     * @example
316     * $lifetime = 100;
317     * $resultKey = "abc";
318     * $query->setHydrationCacheProfile(new QueryCacheProfile());
319     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
320     *
321     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
322     * @return \Doctrine\ORM\AbstractQuery
323     */
324    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
325    {
326        if ( ! $profile->getResultCacheDriver()) {
327            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
328            $profile = $profile->setResultCacheDriver($resultCacheDriver);
329        }
330
331        $this->_hydrationCacheProfile = $profile;
332
333        return $this;
334    }
335
336    /**
337     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
338     */
339    public function getHydrationCacheProfile()
340    {
341        return $this->_hydrationCacheProfile;
342    }
343
344    /**
345     * Set a cache profile for the result cache.
346     *
347     * If no result cache driver is set in the QueryCacheProfile, the default
348     * result cache driver is used from the configuration.
349     *
350     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
351     * @return \Doctrine\ORM\AbstractQuery
352     */
353    public function setResultCacheProfile(QueryCacheProfile $profile = null)
354    {
355        if ( ! $profile->getResultCacheDriver()) {
356            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
357            $profile = $profile->setResultCacheDriver($resultCacheDriver);
358        }
359
360        $this->_queryCacheProfile = $profile;
361
362        return $this;
363    }
364
365    /**
366     * Defines a cache driver to be used for caching result sets and implictly enables caching.
367     *
368     * @param \Doctrine\Common\Cache\Cache $driver Cache driver
369     * @return \Doctrine\ORM\AbstractQuery
370     */
371    public function setResultCacheDriver($resultCacheDriver = null)
372    {
373        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
374            throw ORMException::invalidResultCacheDriver();
375        }
376
377        $this->_queryCacheProfile = $this->_queryCacheProfile
378            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
379            : new QueryCacheProfile(0, null, $resultCacheDriver);
380
381        return $this;
382    }
383
384    /**
385     * Returns the cache driver used for caching result sets.
386     *
387     * @deprecated
388     * @return \Doctrine\Common\Cache\Cache Cache driver
389     */
390    public function getResultCacheDriver()
391    {
392        if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
393            return $this->_queryCacheProfile->getResultCacheDriver();
394        }
395
396        return $this->_em->getConfiguration()->getResultCacheImpl();
397    }
398
399    /**
400     * Set whether or not to cache the results of this query and if so, for
401     * how long and which ID to use for the cache entry.
402     *
403     * @param boolean $bool
404     * @param integer $lifetime
405     * @param string $resultCacheId
406     * @return \Doctrine\ORM\AbstractQuery This query instance.
407     */
408    public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
409    {
410        if ($bool) {
411            $this->setResultCacheLifetime($lifetime);
412            $this->setResultCacheId($resultCacheId);
413
414            return $this;
415        }
416
417        $this->_queryCacheProfile = null;
418
419        return $this;
420    }
421
422    /**
423     * Defines how long the result cache will be active before expire.
424     *
425     * @param integer $lifetime How long the cache entry is valid.
426     * @return \Doctrine\ORM\AbstractQuery This query instance.
427     */
428    public function setResultCacheLifetime($lifetime)
429    {
430        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
431
432        $this->_queryCacheProfile = $this->_queryCacheProfile
433            ? $this->_queryCacheProfile->setLifetime($lifetime)
434            : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
435
436        return $this;
437    }
438
439    /**
440     * Retrieves the lifetime of resultset cache.
441     *
442     * @deprecated
443     * @return integer
444     */
445    public function getResultCacheLifetime()
446    {
447        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
448    }
449
450    /**
451     * Defines if the result cache is active or not.
452     *
453     * @param boolean $expire Whether or not to force resultset cache expiration.
454     * @return \Doctrine\ORM\AbstractQuery This query instance.
455     */
456    public function expireResultCache($expire = true)
457    {
458        $this->_expireResultCache = $expire;
459
460        return $this;
461    }
462
463    /**
464     * Retrieves if the resultset cache is active or not.
465     *
466     * @return boolean
467     */
468    public function getExpireResultCache()
469    {
470        return $this->_expireResultCache;
471    }
472
473    /**
474     * @return QueryCacheProfile
475     */
476    public function getQueryCacheProfile()
477    {
478        return $this->_queryCacheProfile;
479    }
480
481    /**
482     * Change the default fetch mode of an association for this query.
483     *
484     * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
485     *
486     * @param  string $class
487     * @param  string $assocName
488     * @param  int $fetchMode
489     * @return AbstractQuery
490     */
491    public function setFetchMode($class, $assocName, $fetchMode)
492    {
493        if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
494            $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
495        }
496
497        $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
498
499        return $this;
500    }
501
502    /**
503     * Defines the processing mode to be used during hydration / result set transformation.
504     *
505     * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
506     *                               One of the Query::HYDRATE_* constants.
507     * @return \Doctrine\ORM\AbstractQuery This query instance.
508     */
509    public function setHydrationMode($hydrationMode)
510    {
511        $this->_hydrationMode = $hydrationMode;
512
513        return $this;
514    }
515
516    /**
517     * Gets the hydration mode currently used by the query.
518     *
519     * @return integer
520     */
521    public function getHydrationMode()
522    {
523        return $this->_hydrationMode;
524    }
525
526    /**
527     * Gets the list of results for the query.
528     *
529     * Alias for execute(array(), $hydrationMode = HYDRATE_OBJECT).
530     *
531     * @return array
532     */
533    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
534    {
535        return $this->execute(array(), $hydrationMode);
536    }
537
538    /**
539     * Gets the array of results for the query.
540     *
541     * Alias for execute(array(), HYDRATE_ARRAY).
542     *
543     * @return array
544     */
545    public function getArrayResult()
546    {
547        return $this->execute(array(), self::HYDRATE_ARRAY);
548    }
549
550    /**
551     * Gets the scalar results for the query.
552     *
553     * Alias for execute(array(), HYDRATE_SCALAR).
554     *
555     * @return array
556     */
557    public function getScalarResult()
558    {
559        return $this->execute(array(), self::HYDRATE_SCALAR);
560    }
561
562    /**
563     * Get exactly one result or null.
564     *
565     * @throws NonUniqueResultException
566     * @param int $hydrationMode
567     * @return mixed
568     */
569    public function getOneOrNullResult($hydrationMode = null)
570    {
571        $result = $this->execute(array(), $hydrationMode);
572
573        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
574            return null;
575        }
576
577        if ( ! is_array($result)) {
578            return $result;
579        }
580
581        if (count($result) > 1) {
582            throw new NonUniqueResultException;
583        }
584
585        return array_shift($result);
586    }
587
588    /**
589     * Gets the single result of the query.
590     *
591     * Enforces the presence as well as the uniqueness of the result.
592     *
593     * If the result is not unique, a NonUniqueResultException is thrown.
594     * If there is no result, a NoResultException is thrown.
595     *
596     * @param integer $hydrationMode
597     * @return mixed
598     * @throws NonUniqueResultException If the query result is not unique.
599     * @throws NoResultException If the query returned no result.
600     */
601    public function getSingleResult($hydrationMode = null)
602    {
603        $result = $this->execute(array(), $hydrationMode);
604
605        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
606            throw new NoResultException;
607        }
608
609        if ( ! is_array($result)) {
610            return $result;
611        }
612
613        if (count($result) > 1) {
614            throw new NonUniqueResultException;
615        }
616
617        return array_shift($result);
618    }
619
620    /**
621     * Gets the single scalar result of the query.
622     *
623     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
624     *
625     * @return mixed
626     * @throws QueryException If the query result is not unique.
627     */
628    public function getSingleScalarResult()
629    {
630        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
631    }
632
633    /**
634     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
635     *
636     * @param string $name The name of the hint.
637     * @param mixed $value The value of the hint.
638     * @return \Doctrine\ORM\AbstractQuery
639     */
640    public function setHint($name, $value)
641    {
642        $this->_hints[$name] = $value;
643
644        return $this;
645    }
646
647    /**
648     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
649     *
650     * @param string $name The name of the hint.
651     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
652     */
653    public function getHint($name)
654    {
655        return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
656    }
657
658    /**
659     * Return the key value map of query hints that are currently set.
660     *
661     * @return array
662     */
663    public function getHints()
664    {
665        return $this->_hints;
666    }
667
668    /**
669     * Executes the query and returns an IterableResult that can be used to incrementally
670     * iterate over the result.
671     *
672     * @param array $params The query parameters.
673     * @param integer $hydrationMode The hydration mode to use.
674     * @return \Doctrine\ORM\Internal\Hydration\IterableResult
675     */
676    public function iterate(array $params = array(), $hydrationMode = null)
677    {
678        if ($hydrationMode !== null) {
679            $this->setHydrationMode($hydrationMode);
680        }
681
682        if ($params) {
683            $this->setParameters($params);
684        }
685
686        $stmt = $this->_doExecute();
687
688        return $this->_em->newHydrator($this->_hydrationMode)->iterate(
689            $stmt, $this->_resultSetMapping, $this->_hints
690        );
691    }
692
693    /**
694     * Executes the query.
695     *
696     * @param array $params Any additional query parameters.
697     * @param integer $hydrationMode Processing mode to be used during the hydration process.
698     * @return mixed
699     */
700    public function execute($params = array(), $hydrationMode = null)
701    {
702        if ($hydrationMode !== null) {
703            $this->setHydrationMode($hydrationMode);
704        }
705
706        if ($params) {
707            $this->setParameters($params);
708        }
709
710        $setCacheEntry = function() {};
711
712        if ($this->_hydrationCacheProfile !== null) {
713            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
714
715            $queryCacheProfile = $this->getHydrationCacheProfile();
716            $cache             = $queryCacheProfile->getResultCacheDriver();
717            $result            = $cache->fetch($cacheKey);
718
719            if (isset($result[$realCacheKey])) {
720                return $result[$realCacheKey];
721            }
722
723            if ( ! $result) {
724                $result = array();
725            }
726
727            $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
728                $result[$realCacheKey] = $data;
729                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
730            };
731        }
732
733        $stmt = $this->_doExecute();
734
735        if (is_numeric($stmt)) {
736            $setCacheEntry($stmt);
737
738            return $stmt;
739        }
740
741        $data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
742            $stmt, $this->_resultSetMapping, $this->_hints
743        );
744
745        $setCacheEntry($data);
746
747        return $data;
748    }
749
750    /**
751     * Get the result cache id to use to store the result set cache entry.
752     * Will return the configured id if it exists otherwise a hash will be
753     * automatically generated for you.
754     *
755     * @return array ($key, $hash)
756     */
757    protected function getHydrationCacheId()
758    {
759        $params = $this->getParameters();
760
761        foreach ($params AS $key => $value) {
762            $params[$key] = $this->processParameterValue($value);
763        }
764
765        $sql                    = $this->getSQL();
766        $queryCacheProfile      = $this->getHydrationCacheProfile();
767        $hints                  = $this->getHints();
768        $hints['hydrationMode'] = $this->getHydrationMode();
769        ksort($hints);
770
771        return $queryCacheProfile->generateCacheKeys($sql, $params, $hints);
772    }
773
774    /**
775     * Set the result cache id to use to store the result set cache entry.
776     * If this is not explicitely set by the developer then a hash is automatically
777     * generated for you.
778     *
779     * @param string $id
780     * @return \Doctrine\ORM\AbstractQuery This query instance.
781     */
782    public function setResultCacheId($id)
783    {
784        $this->_queryCacheProfile = $this->_queryCacheProfile
785            ? $this->_queryCacheProfile->setCacheKey($id)
786            : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
787
788        return $this;
789    }
790
791    /**
792     * Get the result cache id to use to store the result set cache entry if set.
793     *
794     * @deprecated
795     * @return string
796     */
797    public function getResultCacheId()
798    {
799        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
800    }
801
802    /**
803     * Executes the query and returns a the resulting Statement object.
804     *
805     * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
806     */
807    abstract protected function _doExecute();
808
809    /**
810     * Cleanup Query resource when clone is called.
811     *
812     * @return void
813     */
814    public function __clone()
815    {
816        $this->_params = array();
817        $this->_paramTypes = array();
818        $this->_hints = array();
819    }
820}
Note: See TracBrowser for help on using the repository browser.