Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\View;
4
 
5
use Closure;
6
use Illuminate\Container\Container;
7
use Illuminate\Contracts\Support\Htmlable;
8
use Illuminate\Contracts\View\View as ViewContract;
9
use ReflectionClass;
10
use ReflectionMethod;
11
use ReflectionProperty;
12
 
13
abstract class Component
14
{
15
    /**
16
     * The properties / methods that should not be exposed to the component.
17
     *
18
     * @var array
19
     */
20
    protected $except = [];
21
 
22
    /**
23
     * The component alias name.
24
     *
25
     * @var string
26
     */
27
    public $componentName;
28
 
29
    /**
30
     * The component attributes.
31
     *
32
     * @var \Illuminate\View\ComponentAttributeBag
33
     */
34
    public $attributes;
35
 
36
    /**
37
     * The view factory instance, if any.
38
     *
39
     * @var \Illuminate\Contracts\View\Factory|null
40
     */
41
    protected static $factory;
42
 
43
    /**
44
     * The component resolver callback.
45
     *
46
     * @var (\Closure(string, array): Component)|null
47
     */
48
    protected static $componentsResolver;
49
 
50
    /**
51
     * The cache of blade view names, keyed by contents.
52
     *
53
     * @var array<string, string>
54
     */
55
    protected static $bladeViewCache = [];
56
 
57
    /**
58
     * The cache of public property names, keyed by class.
59
     *
60
     * @var array
61
     */
62
    protected static $propertyCache = [];
63
 
64
    /**
65
     * The cache of public method names, keyed by class.
66
     *
67
     * @var array
68
     */
69
    protected static $methodCache = [];
70
 
71
    /**
72
     * The cache of constructor parameters, keyed by class.
73
     *
74
     * @var array<class-string, array<int, string>>
75
     */
76
    protected static $constructorParametersCache = [];
77
 
78
    /**
79
     * Get the view / view contents that represent the component.
80
     *
81
     * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\Support\Htmlable|\Closure|string
82
     */
83
    abstract public function render();
84
 
85
    /**
86
     * Resolve the component instance with the given data.
87
     *
88
     * @param  array  $data
89
     * @return static
90
     */
91
    public static function resolve($data)
92
    {
93
        if (static::$componentsResolver) {
94
            return call_user_func(static::$componentsResolver, static::class, $data);
95
        }
96
 
97
        $parameters = static::extractConstructorParameters();
98
 
99
        $dataKeys = array_keys($data);
100
 
101
        if (empty(array_diff($parameters, $dataKeys))) {
102
            return new static(...array_intersect_key($data, array_flip($parameters)));
103
        }
104
 
105
        return Container::getInstance()->make(static::class, $data);
106
    }
107
 
108
    /**
109
     * Extract the constructor parameters for the component.
110
     *
111
     * @return array
112
     */
113
    protected static function extractConstructorParameters()
114
    {
115
        if (! isset(static::$constructorParametersCache[static::class])) {
116
            $class = new ReflectionClass(static::class);
117
 
118
            $constructor = $class->getConstructor();
119
 
120
            static::$constructorParametersCache[static::class] = $constructor
121
                ? collect($constructor->getParameters())->map->getName()->all()
122
                : [];
123
        }
124
 
125
        return static::$constructorParametersCache[static::class];
126
    }
127
 
128
    /**
129
     * Resolve the Blade view or view file that should be used when rendering the component.
130
     *
131
     * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\Support\Htmlable|\Closure|string
132
     */
133
    public function resolveView()
134
    {
135
        $view = $this->render();
136
 
137
        if ($view instanceof ViewContract) {
138
            return $view;
139
        }
140
 
141
        if ($view instanceof Htmlable) {
142
            return $view;
143
        }
144
 
145
        $resolver = function ($view) {
146
            return $this->extractBladeViewFromString($view);
147
        };
148
 
149
        return $view instanceof Closure ? function (array $data = []) use ($view, $resolver) {
150
            return $resolver($view($data));
151
        }
152
        : $resolver($view);
153
    }
154
 
155
    /**
156
     * Create a Blade view with the raw component string content.
157
     *
158
     * @param  string  $contents
159
     * @return string
160
     */
161
    protected function extractBladeViewFromString($contents)
162
    {
163
        $key = sprintf('%s::%s', static::class, $contents);
164
 
165
        if (isset(static::$bladeViewCache[$key])) {
166
            return static::$bladeViewCache[$key];
167
        }
168
 
169
        if (strlen($contents) <= PHP_MAXPATHLEN && $this->factory()->exists($contents)) {
170
            return static::$bladeViewCache[$key] = $contents;
171
        }
172
 
173
        return static::$bladeViewCache[$key] = $this->createBladeViewFromString($this->factory(), $contents);
174
    }
175
 
176
    /**
177
     * Create a Blade view with the raw component string content.
178
     *
179
     * @param  \Illuminate\Contracts\View\Factory  $factory
180
     * @param  string  $contents
181
     * @return string
182
     */
183
    protected function createBladeViewFromString($factory, $contents)
184
    {
185
        $factory->addNamespace(
186
            '__components',
187
            $directory = Container::getInstance()['config']->get('view.compiled')
188
        );
189
 
190
        if (! is_file($viewFile = $directory.'/'.sha1($contents).'.blade.php')) {
191
            if (! is_dir($directory)) {
192
                mkdir($directory, 0755, true);
193
            }
194
 
195
            file_put_contents($viewFile, $contents);
196
        }
197
 
198
        return '__components::'.basename($viewFile, '.blade.php');
199
    }
200
 
201
    /**
202
     * Get the data that should be supplied to the view.
203
     *
204
     * @author Freek Van der Herten
205
     * @author Brent Roose
206
     *
207
     * @return array
208
     */
209
    public function data()
210
    {
211
        $this->attributes = $this->attributes ?: $this->newAttributeBag();
212
 
213
        return array_merge($this->extractPublicProperties(), $this->extractPublicMethods());
214
    }
215
 
216
    /**
217
     * Extract the public properties for the component.
218
     *
219
     * @return array
220
     */
221
    protected function extractPublicProperties()
222
    {
223
        $class = get_class($this);
224
 
225
        if (! isset(static::$propertyCache[$class])) {
226
            $reflection = new ReflectionClass($this);
227
 
228
            static::$propertyCache[$class] = collect($reflection->getProperties(ReflectionProperty::IS_PUBLIC))
229
                ->reject(function (ReflectionProperty $property) {
230
                    return $property->isStatic();
231
                })
232
                ->reject(function (ReflectionProperty $property) {
233
                    return $this->shouldIgnore($property->getName());
234
                })
235
                ->map(function (ReflectionProperty $property) {
236
                    return $property->getName();
237
                })->all();
238
        }
239
 
240
        $values = [];
241
 
242
        foreach (static::$propertyCache[$class] as $property) {
243
            $values[$property] = $this->{$property};
244
        }
245
 
246
        return $values;
247
    }
248
 
249
    /**
250
     * Extract the public methods for the component.
251
     *
252
     * @return array
253
     */
254
    protected function extractPublicMethods()
255
    {
256
        $class = get_class($this);
257
 
258
        if (! isset(static::$methodCache[$class])) {
259
            $reflection = new ReflectionClass($this);
260
 
261
            static::$methodCache[$class] = collect($reflection->getMethods(ReflectionMethod::IS_PUBLIC))
262
                ->reject(function (ReflectionMethod $method) {
263
                    return $this->shouldIgnore($method->getName());
264
                })
265
                ->map(function (ReflectionMethod $method) {
266
                    return $method->getName();
267
                });
268
        }
269
 
270
        $values = [];
271
 
272
        foreach (static::$methodCache[$class] as $method) {
273
            $values[$method] = $this->createVariableFromMethod(new ReflectionMethod($this, $method));
274
        }
275
 
276
        return $values;
277
    }
278
 
279
    /**
280
     * Create a callable variable from the given method.
281
     *
282
     * @param  \ReflectionMethod  $method
283
     * @return mixed
284
     */
285
    protected function createVariableFromMethod(ReflectionMethod $method)
286
    {
287
        return $method->getNumberOfParameters() === 0
288
                        ? $this->createInvokableVariable($method->getName())
289
                        : Closure::fromCallable([$this, $method->getName()]);
290
    }
291
 
292
    /**
293
     * Create an invokable, toStringable variable for the given component method.
294
     *
295
     * @param  string  $method
296
     * @return \Illuminate\View\InvokableComponentVariable
297
     */
298
    protected function createInvokableVariable(string $method)
299
    {
300
        return new InvokableComponentVariable(function () use ($method) {
301
            return $this->{$method}();
302
        });
303
    }
304
 
305
    /**
306
     * Determine if the given property / method should be ignored.
307
     *
308
     * @param  string  $name
309
     * @return bool
310
     */
311
    protected function shouldIgnore($name)
312
    {
313
        return str_starts_with($name, '__') ||
314
               in_array($name, $this->ignoredMethods());
315
    }
316
 
317
    /**
318
     * Get the methods that should be ignored.
319
     *
320
     * @return array
321
     */
322
    protected function ignoredMethods()
323
    {
324
        return array_merge([
325
            'data',
326
            'render',
327
            'resolveView',
328
            'shouldRender',
329
            'view',
330
            'withName',
331
            'withAttributes',
332
        ], $this->except);
333
    }
334
 
335
    /**
336
     * Set the component alias name.
337
     *
338
     * @param  string  $name
339
     * @return $this
340
     */
341
    public function withName($name)
342
    {
343
        $this->componentName = $name;
344
 
345
        return $this;
346
    }
347
 
348
    /**
349
     * Set the extra attributes that the component should make available.
350
     *
351
     * @param  array  $attributes
352
     * @return $this
353
     */
354
    public function withAttributes(array $attributes)
355
    {
356
        $this->attributes = $this->attributes ?: $this->newAttributeBag();
357
 
358
        $this->attributes->setAttributes($attributes);
359
 
360
        return $this;
361
    }
362
 
363
    /**
364
     * Get a new attribute bag instance.
365
     *
366
     * @param  array  $attributes
367
     * @return \Illuminate\View\ComponentAttributeBag
368
     */
369
    protected function newAttributeBag(array $attributes = [])
370
    {
371
        return new ComponentAttributeBag($attributes);
372
    }
373
 
374
    /**
375
     * Determine if the component should be rendered.
376
     *
377
     * @return bool
378
     */
379
    public function shouldRender()
380
    {
381
        return true;
382
    }
383
 
384
    /**
385
     * Get the evaluated view contents for the given view.
386
     *
387
     * @param  string|null  $view
388
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $data
389
     * @param  array  $mergeData
390
     * @return \Illuminate\Contracts\View\View
391
     */
392
    public function view($view, $data = [], $mergeData = [])
393
    {
394
        return $this->factory()->make($view, $data, $mergeData);
395
    }
396
 
397
    /**
398
     * Get the view factory instance.
399
     *
400
     * @return \Illuminate\Contracts\View\Factory
401
     */
402
    protected function factory()
403
    {
404
        if (is_null(static::$factory)) {
405
            static::$factory = Container::getInstance()->make('view');
406
        }
407
 
408
        return static::$factory;
409
    }
410
 
411
    /**
412
     * Flush the component's cached state.
413
     *
414
     * @return void
415
     */
416
    public static function flushCache()
417
    {
418
        static::$bladeViewCache = [];
419
        static::$constructorParametersCache = [];
420
        static::$methodCache = [];
421
        static::$propertyCache = [];
422
    }
423
 
424
    /**
425
     * Forget the component's factory instance.
426
     *
427
     * @return void
428
     */
429
    public static function forgetFactory()
430
    {
431
        static::$factory = null;
432
    }
433
 
434
    /**
435
     * Forget the component's resolver callback.
436
     *
437
     * @return void
438
     *
439
     * @internal
440
     */
441
    public static function forgetComponentsResolver()
442
    {
443
        static::$componentsResolver = null;
444
    }
445
 
446
    /**
447
     * Set the callback that should be used to resolve components within views.
448
     *
449
     * @param  \Closure(string $component, array $data): Component  $resolver
450
     * @return void
451
     *
452
     * @internal
453
     */
454
    public static function resolveComponentsUsing($resolver)
455
    {
456
        static::$componentsResolver = $resolver;
457
    }
458
}