~tcuthbert/wordpress/openstack-objectstorage

« back to all changes in this revision

Viewing changes to vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php

  • Committer: Jacek Nykis
  • Date: 2015-02-11 15:35:31 UTC
  • Revision ID: jacek.nykis@canonical.com-20150211153531-hmy6zi0ov2qfkl0b
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 MIT license. For more information, see
 
17
 * <http://www.doctrine-project.org>.
 
18
 */
 
19
 
 
20
namespace Doctrine\Instantiator;
 
21
 
 
22
use Closure;
 
23
use Doctrine\Instantiator\Exception\InvalidArgumentException;
 
24
use Doctrine\Instantiator\Exception\UnexpectedValueException;
 
25
use Exception;
 
26
use ReflectionClass;
 
27
 
 
28
/**
 
29
 * {@inheritDoc}
 
30
 *
 
31
 * @author Marco Pivetta <ocramius@gmail.com>
 
32
 */
 
33
final class Instantiator implements InstantiatorInterface
 
34
{
 
35
    /**
 
36
     * Markers used internally by PHP to define whether {@see \unserialize} should invoke
 
37
     * the method {@see \Serializable::unserialize()} when dealing with classes implementing
 
38
     * the {@see \Serializable} interface.
 
39
     */
 
40
    const SERIALIZATION_FORMAT_USE_UNSERIALIZER   = 'C';
 
41
    const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
 
42
 
 
43
    /**
 
44
     * @var \Closure[] of {@see \Closure} instances used to instantiate specific classes
 
45
     */
 
46
    private static $cachedInstantiators = array();
 
47
 
 
48
    /**
 
49
     * @var object[] of objects that can directly be cloned
 
50
     */
 
51
    private static $cachedCloneables = array();
 
52
 
 
53
    /**
 
54
     * {@inheritDoc}
 
55
     */
 
56
    public function instantiate($className)
 
57
    {
 
58
        if (isset(self::$cachedCloneables[$className])) {
 
59
            return clone self::$cachedCloneables[$className];
 
60
        }
 
61
 
 
62
        if (isset(self::$cachedInstantiators[$className])) {
 
63
            $factory = self::$cachedInstantiators[$className];
 
64
 
 
65
            return $factory();
 
66
        }
 
67
 
 
68
        $factory    = self::$cachedInstantiators[$className] = $this->buildFactory($className);
 
69
        $instance   = $factory();
 
70
        $reflection = new ReflectionClass($instance);
 
71
 
 
72
        if ($this->isSafeToClone($reflection)) {
 
73
            self::$cachedCloneables[$className] = clone $instance;
 
74
        }
 
75
 
 
76
        return $instance;
 
77
    }
 
78
 
 
79
    /**
 
80
     * @internal
 
81
     * @private
 
82
     *
 
83
     * Builds a {@see \Closure} capable of instantiating the given $className without
 
84
     * invoking its constructor.
 
85
     * This method is only exposed as public because of PHP 5.3 compatibility. Do not
 
86
     * use this method in your own code
 
87
     *
 
88
     * @param string $className
 
89
     *
 
90
     * @return Closure
 
91
     */
 
92
    public function buildFactory($className)
 
93
    {
 
94
        $reflectionClass = $this->getReflectionClass($className);
 
95
 
 
96
        if ($this->isInstantiableViaReflection($reflectionClass)) {
 
97
            return function () use ($reflectionClass) {
 
98
                return $reflectionClass->newInstanceWithoutConstructor();
 
99
            };
 
100
        }
 
101
 
 
102
        $serializedString = sprintf(
 
103
            '%s:%d:"%s":0:{}',
 
104
            $this->getSerializationFormat($reflectionClass),
 
105
            strlen($className),
 
106
            $className
 
107
        );
 
108
 
 
109
        $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
 
110
 
 
111
        return function () use ($serializedString) {
 
112
            return unserialize($serializedString);
 
113
        };
 
114
    }
 
115
 
 
116
    /**
 
117
     * @param string $className
 
118
     *
 
119
     * @return ReflectionClass
 
120
     *
 
121
     * @throws InvalidArgumentException
 
122
     */
 
123
    private function getReflectionClass($className)
 
124
    {
 
125
        if (! class_exists($className)) {
 
126
            throw InvalidArgumentException::fromNonExistingClass($className);
 
127
        }
 
128
 
 
129
        $reflection = new ReflectionClass($className);
 
130
 
 
131
        if ($reflection->isAbstract()) {
 
132
            throw InvalidArgumentException::fromAbstractClass($reflection);
 
133
        }
 
134
 
 
135
        return $reflection;
 
136
    }
 
137
 
 
138
    /**
 
139
     * @param ReflectionClass $reflectionClass
 
140
     * @param string          $serializedString
 
141
     *
 
142
     * @throws UnexpectedValueException
 
143
     *
 
144
     * @return void
 
145
     */
 
146
    private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString)
 
147
    {
 
148
        set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) {
 
149
            $error = UnexpectedValueException::fromUncleanUnSerialization(
 
150
                $reflectionClass,
 
151
                $message,
 
152
                $code,
 
153
                $file,
 
154
                $line
 
155
            );
 
156
        });
 
157
 
 
158
        try {
 
159
            unserialize($serializedString);
 
160
        } catch (Exception $exception) {
 
161
            restore_error_handler();
 
162
 
 
163
            throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
 
164
        }
 
165
 
 
166
        restore_error_handler();
 
167
 
 
168
        if ($error) {
 
169
            throw $error;
 
170
        }
 
171
    }
 
172
 
 
173
    /**
 
174
     * @param ReflectionClass $reflectionClass
 
175
     *
 
176
     * @return bool
 
177
     */
 
178
    private function isInstantiableViaReflection(ReflectionClass $reflectionClass)
 
179
    {
 
180
        if (\PHP_VERSION_ID >= 50600) {
 
181
            return ! ($reflectionClass->isInternal() && $reflectionClass->isFinal());
 
182
        }
 
183
 
 
184
        return \PHP_VERSION_ID >= 50400 && ! $this->hasInternalAncestors($reflectionClass);
 
185
    }
 
186
 
 
187
    /**
 
188
     * Verifies whether the given class is to be considered internal
 
189
     *
 
190
     * @param ReflectionClass $reflectionClass
 
191
     *
 
192
     * @return bool
 
193
     */
 
194
    private function hasInternalAncestors(ReflectionClass $reflectionClass)
 
195
    {
 
196
        do {
 
197
            if ($reflectionClass->isInternal()) {
 
198
                return true;
 
199
            }
 
200
        } while ($reflectionClass = $reflectionClass->getParentClass());
 
201
 
 
202
        return false;
 
203
    }
 
204
 
 
205
    /**
 
206
     * Verifies if the given PHP version implements the `Serializable` interface serialization
 
207
     * with an incompatible serialization format. If that's the case, use serialization marker
 
208
     * "C" instead of "O".
 
209
     *
 
210
     * @link http://news.php.net/php.internals/74654
 
211
     *
 
212
     * @param ReflectionClass $reflectionClass
 
213
     *
 
214
     * @return string the serialization format marker, either self::SERIALIZATION_FORMAT_USE_UNSERIALIZER
 
215
     *                or self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER
 
216
     */
 
217
    private function getSerializationFormat(ReflectionClass $reflectionClass)
 
218
    {
 
219
        if ($this->isPhpVersionWithBrokenSerializationFormat()
 
220
            && $reflectionClass->implementsInterface('Serializable')
 
221
        ) {
 
222
            return self::SERIALIZATION_FORMAT_USE_UNSERIALIZER;
 
223
        }
 
224
 
 
225
        return self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER;
 
226
    }
 
227
 
 
228
    /**
 
229
     * Checks whether the current PHP runtime uses an incompatible serialization format
 
230
     *
 
231
     * @return bool
 
232
     */
 
233
    private function isPhpVersionWithBrokenSerializationFormat()
 
234
    {
 
235
        return PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513;
 
236
    }
 
237
 
 
238
    /**
 
239
     * Checks if a class is cloneable
 
240
     *
 
241
     * @param ReflectionClass $reflection
 
242
     *
 
243
     * @return bool
 
244
     */
 
245
    private function isSafeToClone(ReflectionClass $reflection)
 
246
    {
 
247
        if (method_exists($reflection, 'isCloneable') && ! $reflection->isCloneable()) {
 
248
            return false;
 
249
        }
 
250
 
 
251
        // not cloneable if it implements `__clone`, as we want to avoid calling it
 
252
        return ! $reflection->hasMethod('__clone');
 
253
    }
 
254
}