1 | <?php |
||
2 | /** |
||
3 | * @link https://www.yiiframework.com/ |
||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||
5 | * @license https://www.yiiframework.com/license/ |
||
6 | */ |
||
7 | |||
8 | namespace yii\di; |
||
9 | |||
10 | use Yii; |
||
11 | use yii\base\InvalidConfigException; |
||
12 | |||
13 | /** |
||
14 | * Instance represents a reference to a named object in a dependency injection (DI) container or a service locator. |
||
15 | * |
||
16 | * You may use [[get()]] to obtain the actual object referenced by [[id]]. |
||
17 | * |
||
18 | * Instance is mainly used in two places: |
||
19 | * |
||
20 | * - When configuring a dependency injection container, you use Instance to reference a class name, interface name |
||
21 | * or alias name. The reference can later be resolved into the actual object by the container. |
||
22 | * - In classes which use service locator to obtain dependent objects. |
||
23 | * |
||
24 | * The following example shows how to configure a DI container with Instance: |
||
25 | * |
||
26 | * ```php |
||
27 | * $container = new \yii\di\Container; |
||
28 | * $container->set('cache', [ |
||
29 | * 'class' => 'yii\caching\DbCache', |
||
30 | * 'db' => Instance::of('db') |
||
31 | * ]); |
||
32 | * $container->set('db', [ |
||
33 | * 'class' => 'yii\db\Connection', |
||
34 | * 'dsn' => 'sqlite:path/to/file.db', |
||
35 | * ]); |
||
36 | * ``` |
||
37 | * |
||
38 | * And the following example shows how a class retrieves a component from a service locator: |
||
39 | * |
||
40 | * ```php |
||
41 | * class DbCache extends Cache |
||
42 | * { |
||
43 | * public $db = 'db'; |
||
44 | * |
||
45 | * public function init() |
||
46 | * { |
||
47 | * parent::init(); |
||
48 | * $this->db = Instance::ensure($this->db, 'yii\db\Connection'); |
||
49 | * } |
||
50 | * } |
||
51 | * ``` |
||
52 | * |
||
53 | * @author Qiang Xue <[email protected]> |
||
54 | * @since 2.0 |
||
55 | */ |
||
56 | class Instance |
||
57 | { |
||
58 | /** |
||
59 | * @var string the component ID, class name, interface name or alias name |
||
60 | */ |
||
61 | public $id; |
||
62 | /** |
||
63 | * @var bool if null should be returned instead of throwing an exception |
||
64 | */ |
||
65 | public $optional; |
||
66 | |||
67 | |||
68 | /** |
||
69 | * Constructor. |
||
70 | * @param string $id the component ID |
||
71 | * @param bool $optional if null should be returned instead of throwing an exception |
||
72 | */ |
||
73 | 351 | protected function __construct($id, $optional = false) |
|
74 | { |
||
75 | 351 | $this->id = $id; |
|
76 | 351 | $this->optional = $optional; |
|
77 | } |
||
78 | |||
79 | /** |
||
80 | * Creates a new Instance object. |
||
81 | * @param string $id the component ID |
||
82 | * @param bool $optional if null should be returned instead of throwing an exception |
||
83 | * @return Instance the new Instance object. |
||
84 | */ |
||
85 | 15 | public static function of($id, $optional = false) |
|
86 | { |
||
87 | 15 | return new static($id, $optional); |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * Resolves the specified reference into the actual object and makes sure it is of the specified type. |
||
92 | * |
||
93 | * The reference may be specified as a string or an Instance object. If the former, |
||
94 | * it will be treated as a component ID, a class/interface name or an alias, depending on the container type. |
||
95 | * |
||
96 | * If you do not specify a container, the method will first try `Yii::$app` followed by `Yii::$container`. |
||
97 | * |
||
98 | * For example, |
||
99 | * |
||
100 | * ```php |
||
101 | * use yii\db\Connection; |
||
102 | * |
||
103 | * // returns Yii::$app->db |
||
104 | * $db = Instance::ensure('db', Connection::class); |
||
105 | * // returns an instance of Connection using the given configuration |
||
106 | * $db = Instance::ensure(['dsn' => 'sqlite:path/to/my.db'], Connection::class); |
||
107 | * ``` |
||
108 | * |
||
109 | * @param object|string|array|static $reference an object or a reference to the desired object. |
||
110 | * You may specify a reference in terms of a component ID or an Instance object. |
||
111 | * Starting from version 2.0.2, you may also pass in a configuration array for creating the object. |
||
112 | * If the "class" value is not specified in the configuration array, it will use the value of `$type`. |
||
113 | * @param string|null $type the class/interface name to be checked. If null, type check will not be performed. |
||
114 | * @param ServiceLocator|Container|null $container the container. This will be passed to [[get()]]. |
||
115 | * @return object the object referenced by the Instance, or `$reference` itself if it is an object. |
||
116 | * @throws InvalidConfigException if the reference is invalid |
||
117 | * |
||
118 | * @template T of object |
||
119 | * @psalm-param class-string<T>|null $type |
||
120 | * @phpstan-param class-string<T>|null $type |
||
121 | * @psalm-return ($type is null ? object : T) |
||
122 | * @phpstan-return ($type is null ? object : T) |
||
123 | */ |
||
124 | 360 | public static function ensure($reference, $type = null, $container = null) |
|
125 | { |
||
126 | 360 | if (is_array($reference)) { |
|
127 | 5 | if (!$container instanceof Container) { |
|
128 | 3 | $container = Yii::$container; |
|
129 | } |
||
130 | 5 | if (isset($reference['__class'])) { |
|
131 | $class = $reference['__class']; |
||
132 | unset($reference['__class'], $type['class']); |
||
133 | 5 | } elseif (isset($reference['class'])) { |
|
134 | 5 | $class = $reference['class']; |
|
135 | 5 | unset($reference['class']); |
|
136 | } else { |
||
137 | $class = $type; |
||
138 | } |
||
139 | 5 | $component = $container->get($class, [], $reference); |
|
140 | 5 | if ($type === null || $component instanceof $type) { |
|
141 | 4 | return $component; |
|
142 | } |
||
143 | |||
144 | 1 | throw new InvalidConfigException('Invalid data type: ' . $class . '. ' . $type . ' is expected.'); |
|
145 | 359 | } elseif (empty($reference)) { |
|
146 | 1 | throw new InvalidConfigException('The required component is not specified.'); |
|
147 | } |
||
148 | |||
149 | 358 | if (is_string($reference)) { |
|
150 | 337 | $reference = new static($reference); |
|
151 | 40 | } elseif ($type === null || $reference instanceof $type) { |
|
152 | 38 | return $reference; |
|
153 | } |
||
154 | |||
155 | 339 | if ($reference instanceof self) { |
|
156 | try { |
||
157 | 338 | $component = $reference->get($container); |
|
158 | 3 | } catch (\ReflectionException $e) { |
|
159 | throw new InvalidConfigException('Failed to instantiate component or class "' . $reference->id . '".', 0, $e); |
||
160 | } |
||
161 | 335 | if ($type === null || $component instanceof $type) { |
|
162 | 334 | return $component; |
|
163 | } |
||
164 | |||
165 | 1 | throw new InvalidConfigException('"' . $reference->id . '" refers to a ' . get_class($component) . " component. $type is expected."); |
|
166 | } |
||
167 | |||
168 | 1 | $valueType = is_object($reference) ? get_class($reference) : gettype($reference); |
|
169 | 1 | throw new InvalidConfigException("Invalid data type: $valueType. $type is expected."); |
|
170 | } |
||
171 | |||
172 | /** |
||
173 | * Returns the actual object referenced by this Instance object. |
||
174 | * @param ServiceLocator|Container|null $container the container used to locate the referenced object. |
||
175 | * If null, the method will first try `Yii::$app` then `Yii::$container`. |
||
176 | * @return object the actual object referenced by this Instance object. |
||
177 | */ |
||
178 | 348 | public function get($container = null) |
|
179 | { |
||
180 | try { |
||
181 | 348 | if ($container) { |
|
182 | 15 | return $container->get($this->id); |
|
183 | } |
||
184 | 333 | if (Yii::$app && Yii::$app->has($this->id)) { |
|
185 | 331 | return Yii::$app->get($this->id); |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
186 | } |
||
187 | |||
188 | 4 | return Yii::$container->get($this->id); |
|
189 | 7 | } catch (\Exception $e) { |
|
190 | 7 | if ($this->optional) { |
|
191 | 2 | return null; |
|
192 | } |
||
193 | 5 | throw $e; |
|
194 | } catch (\Throwable $e) { |
||
195 | if ($this->optional) { |
||
196 | return null; |
||
197 | } |
||
198 | throw $e; |
||
199 | } |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * Restores class state after using `var_export()`. |
||
204 | * |
||
205 | * @param array $state |
||
206 | * @return Instance |
||
207 | * @throws InvalidConfigException when $state property does not contain `id` parameter |
||
208 | * @see https://www.php.net/manual/en/function.var-export.php |
||
209 | * @since 2.0.12 |
||
210 | */ |
||
211 | 2 | public static function __set_state($state) |
|
212 | { |
||
213 | 2 | if (!isset($state['id'])) { |
|
214 | 1 | throw new InvalidConfigException('Failed to instantiate class "Instance". Required parameter "id" is missing'); |
|
215 | } |
||
216 | |||
217 | 1 | return new self($state['id']); |
|
218 | } |
||
219 | } |
||
220 |