upgraded dependencies

This commit is contained in:
RafficMohammed
2023-01-08 01:59:16 +05:30
parent 51056e3aad
commit f9ae387337
6895 changed files with 133617 additions and 178680 deletions

View File

@@ -0,0 +1,55 @@
<?php
namespace Illuminate\View;
class AnonymousComponent extends Component
{
/**
* The component view.
*
* @var string
*/
protected $view;
/**
* The component data.
*
* @var array
*/
protected $data = [];
/**
* Create a new anonymous component instance.
*
* @param string $view
* @param array $data
* @return void
*/
public function __construct($view, $data)
{
$this->view = $view;
$this->data = $data;
}
/**
* Get the view / view contents that represent the component.
*
* @return string
*/
public function render()
{
return $this->view;
}
/**
* Get the data that should be supplied to the view.
*
* @return array
*/
public function data()
{
$this->attributes = $this->attributes ?: new ComponentAttributeBag;
return $this->data + ['attributes' => $this->attributes];
}
}

View File

@@ -45,6 +45,13 @@ class BladeCompiler extends Compiler implements CompilerInterface
*/
protected $conditions = [];
/**
* All of the registered precompilers.
*
* @var array
*/
protected $precompilers = [];
/**
* The file currently being compiled.
*
@@ -58,7 +65,7 @@ class BladeCompiler extends Compiler implements CompilerInterface
* @var array
*/
protected $compilers = [
'Comments',
// 'Comments',
'Extensions',
'Statements',
'Echos',
@@ -106,6 +113,20 @@ class BladeCompiler extends Compiler implements CompilerInterface
*/
protected $rawBlocks = [];
/**
* The array of class component aliases and their class names.
*
* @var array
*/
protected $classComponentAliases = [];
/**
* Indicates if component tags should be compiled.
*
* @var bool
*/
protected $compilesComponentTags = true;
/**
* Compile the view at the given path.
*
@@ -194,7 +215,16 @@ class BladeCompiler extends Compiler implements CompilerInterface
{
[$this->footer, $result] = [[], ''];
$value = $this->storeUncompiledBlocks($value);
// First we will compile the Blade component tags. This is a precompile style
// step which compiles the component Blade tags into @component directives
// that may be used by Blade. Then we should call any other precompilers.
$value = $this->compileComponentTags(
$this->compileComments($this->storeUncompiledBlocks($value))
);
foreach ($this->precompilers as $precompiler) {
$value = call_user_func($precompiler, $value);
}
// Here we will loop through all of the tokens returned by the Zend lexer and
// parse each one into the corresponding valid PHP. We will then have this
@@ -275,6 +305,23 @@ class BladeCompiler extends Compiler implements CompilerInterface
);
}
/**
* Compile the component tags.
*
* @param string $value
* @return string
*/
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
return $value;
}
return (new ComponentTagCompiler(
$this->classComponentAliases, $this
))->compile($value);
}
/**
* Replace the raw placeholders with the original code stored in the raw blocks.
*
@@ -311,8 +358,8 @@ class BladeCompiler extends Compiler implements CompilerInterface
*/
protected function addFooters($result)
{
return ltrim($result, PHP_EOL)
.PHP_EOL.implode(PHP_EOL, array_reverse($this->footer));
return ltrim($result, "\n")
."\n".implode("\n", array_reverse($this->footer));
}
/**
@@ -481,6 +528,63 @@ class BladeCompiler extends Compiler implements CompilerInterface
return call_user_func($this->conditions[$name], ...$parameters);
}
/**
* Register a class-based component alias directive.
*
* @param string $class
* @param string|null $alias
* @param string $prefix
* @return void
*/
public function component($class, $alias = null, $prefix = '')
{
if (! is_null($alias) && Str::contains($alias, '\\')) {
[$class, $alias] = [$alias, $class];
}
if (is_null($alias)) {
$alias = Str::contains($class, '\\View\\Components\\')
? collect(explode('\\', Str::after($class, '\\View\\Components\\')))->map(function ($segment) {
return Str::kebab($segment);
})->implode(':')
: Str::kebab(class_basename($class));
}
if (! empty($prefix)) {
$alias = $prefix.'-'.$alias;
}
$this->classComponentAliases[$alias] = $class;
}
/**
* Register an array of class-based components.
*
* @param array $components
* @param string $prefix
* @return void
*/
public function components(array $components, $prefix = '')
{
foreach ($components as $key => $value) {
if (is_numeric($key)) {
static::component($value, null, $prefix);
} else {
static::component($key, $value, $prefix);
}
}
}
/**
* Get the registered class component aliases.
*
* @return array
*/
public function getClassComponentAliases()
{
return $this->classComponentAliases;
}
/**
* Register a component alias directive.
*
@@ -488,7 +592,7 @@ class BladeCompiler extends Compiler implements CompilerInterface
* @param string|null $alias
* @return void
*/
public function component($path, $alias = null)
public function aliasComponent($path, $alias = null)
{
$alias = $alias ?: Arr::last(explode('.', $path));
@@ -511,6 +615,18 @@ class BladeCompiler extends Compiler implements CompilerInterface
* @return void
*/
public function include($path, $alias = null)
{
return $this->aliasInclude($path, $alias);
}
/**
* Register an include alias directive.
*
* @param string $path
* @param string|null $alias
* @return void
*/
public function aliasInclude($path, $alias = null)
{
$alias = $alias ?: Arr::last(explode('.', $path));
@@ -549,6 +665,17 @@ class BladeCompiler extends Compiler implements CompilerInterface
return $this->customDirectives;
}
/**
* Register a new precompiler.
*
* @param callable $precompiler
* @return void
*/
public function precompiler(callable $precompiler)
{
$this->precompilers[] = $precompiler;
}
/**
* Set the echo format to be used by the compiler.
*
@@ -579,4 +706,14 @@ class BladeCompiler extends Compiler implements CompilerInterface
{
$this->setEchoFormat('e(%s, false)');
}
/**
* Indicate that component tags should not be compiled.
*
* @return void
*/
public function withoutComponentTags()
{
$this->compilesComponentTags = false;
}
}

View File

@@ -0,0 +1,488 @@
<?php
namespace Illuminate\View\Compilers;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Str;
use Illuminate\View\AnonymousComponent;
use Illuminate\View\ViewFinderInterface;
use InvalidArgumentException;
use ReflectionClass;
/**
* @author Spatie bvba <info@spatie.be>
* @author Taylor Otwell <taylor@laravel.com>
*/
class ComponentTagCompiler
{
/**
* The Blade compiler instance.
*
* @var \Illuminate\View\Compilers\BladeCompiler
*/
protected $blade;
/**
* The component class aliases.
*
* @var array
*/
protected $aliases = [];
/**
* The "bind:" attributes that have been compiled for the current component.
*
* @var array
*/
protected $boundAttributes = [];
/**
* Create new component tag compiler.
*
* @param array $aliases
* @param \Illuminate\View\Compilers\BladeCompiler|null
* @return void
*/
public function __construct(array $aliases = [], ?BladeCompiler $blade = null)
{
$this->aliases = $aliases;
$this->blade = $blade ?: new BladeCompiler(new Filesystem, sys_get_temp_dir());
}
/**
* Compile the component and slot tags within the given string.
*
* @param string $value
* @return string
*/
public function compile(string $value)
{
$value = $this->compileSlots($value);
return $this->compileTags($value);
}
/**
* Compile the tags within the given string.
*
* @param string $value
* @return string
*
* @throws \InvalidArgumentException
*/
public function compileTags(string $value)
{
$value = $this->compileSelfClosingTags($value);
$value = $this->compileOpeningTags($value);
$value = $this->compileClosingTags($value);
return $value;
}
/**
* Compile the opening tags within the given string.
*
* @param string $value
* @return string
*
* @throws \InvalidArgumentException
*/
protected function compileOpeningTags(string $value)
{
$pattern = "/
<
\s*
x[-\:]([\w\-\:\.]*)
(?<attributes>
(?:
\s+
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)
?)*
\s*
)
(?<![\/=\-])
>
/x";
return preg_replace_callback($pattern, function (array $matches) {
$this->boundAttributes = [];
$attributes = $this->getAttributesFromAttributeString($matches['attributes']);
return $this->componentString($matches[1], $attributes);
}, $value);
}
/**
* Compile the self-closing tags within the given string.
*
* @param string $value
* @return string
*
* @throws \InvalidArgumentException
*/
protected function compileSelfClosingTags(string $value)
{
$pattern = "/
<
\s*
x[-\:]([\w\-\:\.]*)
\s*
(?<attributes>
(?:
\s+
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)?
)*
\s*
)
\/>
/x";
return preg_replace_callback($pattern, function (array $matches) {
$this->boundAttributes = [];
$attributes = $this->getAttributesFromAttributeString($matches['attributes']);
return $this->componentString($matches[1], $attributes)."\n@endcomponentClass ";
}, $value);
}
/**
* Compile the Blade component string for the given component and attributes.
*
* @param string $component
* @param array $attributes
* @return string
*
* @throws \InvalidArgumentException
*/
protected function componentString(string $component, array $attributes)
{
$class = $this->componentClass($component);
[$data, $attributes] = $this->partitionDataAndAttributes($class, $attributes);
$data = $data->mapWithKeys(function ($value, $key) {
return [Str::camel($key) => $value];
});
// If the component doesn't exists as a class we'll assume it's a class-less
// component and pass the component as a view parameter to the data so it
// can be accessed within the component and we can render out the view.
if (! class_exists($class)) {
$parameters = [
'view' => "'$class'",
'data' => '['.$this->attributesToString($data->all(), $escapeBound = false).']',
];
$class = AnonymousComponent::class;
} else {
$parameters = $data->all();
}
return " @component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).'])
<?php $component->withAttributes(['.$this->attributesToString($attributes->all()).']); ?>';
}
/**
* Get the component class for a given component alias.
*
* @param string $component
* @return string
*
* @throws \InvalidArgumentException
*/
protected function componentClass(string $component)
{
$viewFactory = Container::getInstance()->make(Factory::class);
if (isset($this->aliases[$component])) {
if (class_exists($alias = $this->aliases[$component])) {
return $alias;
}
if ($viewFactory->exists($alias)) {
return $alias;
}
throw new InvalidArgumentException(
"Unable to locate class or view [{$alias}] for component [{$component}]."
);
}
if (class_exists($class = $this->guessClassName($component))) {
return $class;
}
if ($viewFactory->exists($view = $this->guessViewName($component))) {
return $view;
}
throw new InvalidArgumentException(
"Unable to locate a class or view for component [{$component}]."
);
}
/**
* Guess the class name for the given component.
*
* @param string $component
* @return string
*/
public function guessClassName(string $component)
{
$namespace = Container::getInstance()
->make(Application::class)
->getNamespace();
$componentPieces = array_map(function ($componentPiece) {
return ucfirst(Str::camel($componentPiece));
}, explode('.', $component));
return $namespace.'View\\Components\\'.implode('\\', $componentPieces);
}
/**
* Guess the view name for the given component.
*
* @param string $name
* @return string
*/
public function guessViewName($name)
{
$prefix = 'components.';
$delimiter = ViewFinderInterface::HINT_PATH_DELIMITER;
if (Str::contains($name, $delimiter)) {
return Str::replaceFirst($delimiter, $delimiter.$prefix, $name);
}
return $prefix.$name;
}
/**
* Partition the data and extra attributes from the given array of attributes.
*
* @param string $class
* @param array $attributes
* @return array
*/
protected function partitionDataAndAttributes($class, array $attributes)
{
// If the class doesn't exists, we'll assume it's a class-less component and
// return all of the attributes as both data and attributes since we have
// now way to partition them. The user can exclude attributes manually.
if (! class_exists($class)) {
return [collect($attributes), collect($attributes)];
}
$constructor = (new ReflectionClass($class))->getConstructor();
$parameterNames = $constructor
? collect($constructor->getParameters())->map->getName()->all()
: [];
return collect($attributes)->partition(function ($value, $key) use ($parameterNames) {
return in_array(Str::camel($key), $parameterNames);
})->all();
}
/**
* Compile the closing tags within the given string.
*
* @param string $value
* @return string
*/
protected function compileClosingTags(string $value)
{
return preg_replace("/<\/\s*x[-\:][\w\-\:\.]*\s*>/", ' @endcomponentClass ', $value);
}
/**
* Compile the slot tags within the given string.
*
* @param string $value
* @return string
*/
public function compileSlots(string $value)
{
$value = preg_replace_callback('/<\s*x[\-\:]slot\s+(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/', function ($matches) {
$name = $this->stripQuotes($matches['name']);
if ($matches[1] !== ':') {
$name = "'{$name}'";
}
return " @slot({$name}) ";
}, $value);
return preg_replace('/<\/\s*x[\-\:]slot[^>]*>/', ' @endslot', $value);
}
/**
* Get an array of attributes from the given attribute string.
*
* @param string $attributeString
* @return array
*/
protected function getAttributesFromAttributeString(string $attributeString)
{
$attributeString = $this->parseBindAttributes($attributeString);
$pattern = '/
(?<attribute>[\w\-:.@]+)
(
=
(?<value>
(
\"[^\"]+\"
|
\\\'[^\\\']+\\\'
|
[^\s>]+
)
)
)?
/x';
if (! preg_match_all($pattern, $attributeString, $matches, PREG_SET_ORDER)) {
return [];
}
return collect($matches)->mapWithKeys(function ($match) {
$attribute = $match['attribute'];
$value = $match['value'] ?? null;
if (is_null($value)) {
$value = 'true';
$attribute = Str::start($attribute, 'bind:');
}
$value = $this->stripQuotes($value);
if (Str::startsWith($attribute, 'bind:')) {
$attribute = Str::after($attribute, 'bind:');
$this->boundAttributes[$attribute] = true;
} else {
$value = "'".$this->compileAttributeEchos($value)."'";
}
return [$attribute => $value];
})->toArray();
}
/**
* Parse the "bind" attributes in a given attribute string into their fully-qualified syntax.
*
* @param string $attributeString
* @return string
*/
protected function parseBindAttributes(string $attributeString)
{
$pattern = "/
(?:^|\s+) # start of the string or whitespace between attributes
: # attribute needs to start with a semicolon
([\w\-:.@]+) # match the actual attribute name
= # only match attributes that have a value
/xm";
return preg_replace($pattern, ' bind:$1=', $attributeString);
}
/**
* Compile any Blade echo statements that are present in the attribute string.
*
* These echo statements need to be converted to string concatenation statements.
*
* @param string $attributeString
* @return string
*/
protected function compileAttributeEchos(string $attributeString)
{
$value = $this->blade->compileEchos($attributeString);
$value = $this->escapeSingleQuotesOutsideOfPhpBlocks($value);
$value = str_replace('<?php echo ', '\'.', $value);
$value = str_replace('; ?>', '.\'', $value);
return $value;
}
/**
* Escape the single quotes in the given string that are outside of PHP blocks.
*
* @param string $value
* @return string
*/
protected function escapeSingleQuotesOutsideOfPhpBlocks(string $value)
{
return collect(token_get_all($value))->map(function ($token) {
if (! is_array($token)) {
return $token;
}
return $token[0] === T_INLINE_HTML
? str_replace("'", "\\'", $token[1])
: $token[1];
})->implode('');
}
/**
* Convert an array of attributes to a string.
*
* @param array $attributes
* @param bool $escapeBound
* @return string
*/
protected function attributesToString(array $attributes, $escapeBound = true)
{
return collect($attributes)
->map(function (string $value, string $attribute) use ($escapeBound) {
return $escapeBound && isset($this->boundAttributes[$attribute]) && $value !== 'true' && ! is_numeric($value)
? "'{$attribute}' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute({$value})"
: "'{$attribute}' => {$value}";
})
->implode(',');
}
/**
* Strip any quotes from the given string.
*
* @param string $value
* @return string
*/
public function stripQuotes(string $value)
{
return Str::startsWith($value, ['"', '\''])
? substr($value, 1, -1)
: $value;
}
}

View File

@@ -2,8 +2,17 @@
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Support\Str;
trait CompilesComponents
{
/**
* The component name hash stack.
*
* @var array
*/
protected static $componentHashStack = [];
/**
* Compile the component statements into valid PHP.
*
@@ -12,9 +21,54 @@ trait CompilesComponents
*/
protected function compileComponent($expression)
{
[$component, $alias, $data] = strpos($expression, ',') !== false
? array_map('trim', explode(',', trim($expression, '()'), 3)) + ['', '', '']
: [trim($expression, '()'), '', ''];
$component = trim($component, '\'"');
$hash = static::newComponentHash($component);
if (Str::contains($component, ['::class', '\\'])) {
return static::compileClassComponentOpening($component, $alias, $data, $hash);
}
return "<?php \$__env->startComponent{$expression}; ?>";
}
/**
* Get a new component hash for a component name.
*
* @param string $component
* @return string
*/
public static function newComponentHash(string $component)
{
static::$componentHashStack[] = $hash = sha1($component);
return $hash;
}
/**
* Compile a class component opening.
*
* @param string $component
* @param string $alias
* @param string $data
* @param string $hash
* @return string
*/
public static function compileClassComponentOpening(string $component, string $alias, string $data, string $hash)
{
return implode("\n", [
'<?php if (isset($component)) { $__componentOriginal'.$hash.' = $component; } ?>',
'<?php $component = $__env->getContainer()->make('.Str::finish($component, '::class').', '.($data ?: '[]').'); ?>',
'<?php $component->withName('.$alias.'); ?>',
'<?php if ($component->shouldRender()): ?>',
'<?php $__env->startComponent($component->resolveView(), $component->data()); ?>',
]);
}
/**
* Compile the end-component statements into valid PHP.
*
@@ -22,7 +76,27 @@ trait CompilesComponents
*/
protected function compileEndComponent()
{
return '<?php echo $__env->renderComponent(); ?>';
$hash = array_pop(static::$componentHashStack);
return implode("\n", [
'<?php if (isset($__componentOriginal'.$hash.')): ?>',
'<?php $component = $__componentOriginal'.$hash.'; ?>',
'<?php unset($__componentOriginal'.$hash.'); ?>',
'<?php endif; ?>',
'<?php echo $__env->renderComponent(); ?>',
]);
}
/**
* Compile the end-component statements into valid PHP.
*
* @return string
*/
public function compileEndComponentClass()
{
return static::compileEndComponent()."\n".implode("\n", [
'<?php endif; ?>',
]);
}
/**
@@ -66,4 +140,37 @@ trait CompilesComponents
{
return $this->compileEndComponent();
}
/**
* Compile the prop statement into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileProps($expression)
{
return "<?php \$attributes = \$attributes->exceptProps{$expression}; ?>
<?php foreach (array_filter({$expression}, 'is_string', ARRAY_FILTER_USE_KEY) as \$__key => \$__value) {
\$\$__key = \$\$__key ?? \$__value;
} ?>
<?php \$__defined_vars = get_defined_vars(); ?>
<?php foreach (\$attributes as \$__key => \$__value) {
if (array_key_exists(\$__key, \$__defined_vars)) unset(\$\$__key);
} ?>
<?php unset(\$__defined_vars); ?>";
}
/**
* Sanitize the given component attribute value.
*
* @param mixed $value
* @return mixed
*/
public static function sanitizeComponentAttribute($value)
{
return is_string($value) ||
(is_object($value) && method_exists($value, '__toString'))
? e($value)
: $value;
}
}

View File

@@ -2,6 +2,8 @@
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Support\Str;
trait CompilesConditionals
{
/**
@@ -47,6 +49,47 @@ trait CompilesConditionals
return '<?php endif; ?>';
}
/**
* Compile the env statements into valid PHP.
*
* @param string $environments
* @return string
*/
protected function compileEnv($environments)
{
return "<?php if(app()->environment{$environments}): ?>";
}
/**
* Compile the end-env statements into valid PHP.
*
* @return string
*/
protected function compileEndEnv()
{
return '<?php endif; ?>';
}
/**
* Compile the production statements into valid PHP.
*
* @return string
*/
protected function compileProduction()
{
return "<?php if(app()->environment('production')): ?>";
}
/**
* Compile the end-production statements into valid PHP.
*
* @return string
*/
protected function compileEndProduction()
{
return '<?php endif; ?>';
}
/**
* Compile the if-guest statements into valid PHP.
*
@@ -94,6 +137,17 @@ trait CompilesConditionals
return "<?php if (! empty(trim(\$__env->yieldContent{$expression}))): ?>";
}
/**
* Compile the section-missing statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileSectionMissing($expression)
{
return "<?php if (empty(trim(\$__env->yieldContent{$expression}))): ?>";
}
/**
* Compile the if statements into valid PHP.
*
@@ -227,4 +281,26 @@ trait CompilesConditionals
{
return '<?php endswitch; ?>';
}
/**
* Compile an once block into valid PHP.
*
* @return string
*/
protected function compileOnce($id = null)
{
$id = $id ? $this->stripParentheses($id) : "'".(string) Str::uuid()."'";
return '<?php if (! $__env->hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); ?>';
}
/**
* Compile an end-once block into valid PHP.
*
* @return string
*/
public function compileEndOnce()
{
return '<?php endif; ?>';
}
}

View File

@@ -10,7 +10,7 @@ trait CompilesEchos
* @param string $value
* @return string
*/
protected function compileEchos($value)
public function compileEchos($value)
{
foreach ($this->getEchoMethods() as $method) {
$value = $this->$method($value);

View File

@@ -7,7 +7,7 @@ trait CompilesTranslations
/**
* Compile the lang statements into valid PHP.
*
* @param string $expression
* @param string|null $expression
* @return string
*/
protected function compileLang($expression)

View File

@@ -0,0 +1,279 @@
<?php
namespace Illuminate\View;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
abstract class Component
{
/**
* The cache of public property names, keyed by class.
*
* @var array
*/
protected static $propertyCache = [];
/**
* The cache of public method names, keyed by class.
*
* @var array
*/
protected static $methodCache = [];
/**
* The properties / methods that should not be exposed to the component.
*
* @var array
*/
protected $except = [];
/**
* The component alias name.
*
* @var string
*/
public $componentName;
/**
* The component attributes.
*
* @var \Illuminate\View\ComponentAttributeBag
*/
public $attributes;
/**
* Get the view / view contents that represent the component.
*
* @return \Illuminate\View\View|\Closure|string
*/
abstract public function render();
/**
* Resolve the Blade view or view file that should be used when rendering the component.
*
* @return \Illuminate\View\View|\Closure|string
*/
public function resolveView()
{
$view = $this->render();
if ($view instanceof View) {
return $view;
}
$resolver = function ($view) {
$factory = Container::getInstance()->make('view');
return $factory->exists($view)
? $view
: $this->createBladeViewFromString($factory, $view);
};
return $view instanceof Closure ? function (array $data = []) use ($view, $resolver) {
return $resolver($view($data));
}
: $resolver($view);
}
/**
* Create a Blade view with the raw component string content.
*
* @param \Illuminate\Contracts\View\Factory $factory
* @param string $contents
* @return string
*/
protected function createBladeViewFromString($factory, $contents)
{
$factory->addNamespace(
'__components',
$directory = Container::getInstance()['config']->get('view.compiled')
);
if (! file_exists($viewFile = $directory.'/'.sha1($contents).'.blade.php')) {
if (! is_dir($directory)) {
mkdir($directory, 0755, true);
}
file_put_contents($viewFile, $contents);
}
return '__components::'.basename($viewFile, '.blade.php');
}
/**
* Get the data that should be supplied to the view.
*
* @author Freek Van der Herten
* @author Brent Roose
*
* @return array
*/
public function data()
{
$this->attributes = $this->attributes ?: new ComponentAttributeBag;
return array_merge($this->extractPublicProperties(), $this->extractPublicMethods());
}
/**
* Extract the public properties for the component.
*
* @return array
*/
protected function extractPublicProperties()
{
$class = get_class($this);
if (! isset(static::$propertyCache[$class])) {
$reflection = new ReflectionClass($this);
static::$propertyCache[$class] = collect($reflection->getProperties(ReflectionProperty::IS_PUBLIC))
->reject(function (ReflectionProperty $property) {
return $property->isStatic();
})
->reject(function (ReflectionProperty $property) {
return $this->shouldIgnore($property->getName());
})
->map(function (ReflectionProperty $property) {
return $property->getName();
})->all();
}
$values = [];
foreach (static::$propertyCache[$class] as $property) {
$values[$property] = $this->{$property};
}
return $values;
}
/**
* Extract the public methods for the component.
*
* @return array
*/
protected function extractPublicMethods()
{
$class = get_class($this);
if (! isset(static::$methodCache[$class])) {
$reflection = new ReflectionClass($this);
static::$methodCache[$class] = collect($reflection->getMethods(ReflectionMethod::IS_PUBLIC))
->reject(function (ReflectionMethod $method) {
return $this->shouldIgnore($method->getName());
})
->map(function (ReflectionMethod $method) {
return $method->getName();
});
}
$values = [];
foreach (static::$methodCache[$class] as $method) {
$values[$method] = $this->createVariableFromMethod(new ReflectionMethod($this, $method));
}
return $values;
}
/**
* Create a callable variable from the given method.
*
* @param \ReflectionMethod $method
* @return mixed
*/
protected function createVariableFromMethod(ReflectionMethod $method)
{
return $method->getNumberOfParameters() === 0
? $this->createInvokableVariable($method->getName())
: Closure::fromCallable([$this, $method->getName()]);
}
/**
* Create an invokable, toStringable variable for the given component method.
*
* @param string $method
* @return \Illuminate\View\InvokableComponentVariable
*/
protected function createInvokableVariable(string $method)
{
return new InvokableComponentVariable(function () use ($method) {
return $this->{$method}();
});
}
/**
* Determine if the given property / method should be ignored.
*
* @param string $name
* @return bool
*/
protected function shouldIgnore($name)
{
return Str::startsWith($name, '__') ||
in_array($name, $this->ignoredMethods());
}
/**
* Get the methods that should be ignored.
*
* @return array
*/
protected function ignoredMethods()
{
return array_merge([
'data',
'render',
'resolveView',
'shouldRender',
'view',
'withName',
'withAttributes',
], $this->except);
}
/**
* Set the component alias name.
*
* @param string $name
* @return $this
*/
public function withName($name)
{
$this->componentName = $name;
return $this;
}
/**
* Set the extra attributes that the component should make available.
*
* @param array $attributes
* @return $this
*/
public function withAttributes(array $attributes)
{
$this->attributes = $this->attributes ?: new ComponentAttributeBag;
$this->attributes->setAttributes($attributes);
return $this;
}
/**
* Determine if the component should be rendered.
*
* @return bool
*/
public function shouldRender()
{
return true;
}
}

View File

@@ -0,0 +1,308 @@
<?php
namespace Illuminate\View;
use ArrayAccess;
use ArrayIterator;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use IteratorAggregate;
class ComponentAttributeBag implements ArrayAccess, Htmlable, IteratorAggregate
{
use Macroable;
/**
* The raw array of attributes.
*
* @var array
*/
protected $attributes = [];
/**
* Create a new component attribute bag instance.
*
* @param array $attributes
* @return void
*/
public function __construct(array $attributes = [])
{
$this->attributes = $attributes;
}
/**
* Get the first attribute's value.
*
* @param mixed $default
* @return mixed
*/
public function first($default = null)
{
return $this->getIterator()->current() ?? value($default);
}
/**
* Get a given attribute from the attribute array.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
return $this->attributes[$key] ?? value($default);
}
/**
* Only include the given attribute from the attribute array.
*
* @param mixed|array $keys
* @return static
*/
public function only($keys)
{
if (is_null($keys)) {
$values = $this->attributes;
} else {
$keys = Arr::wrap($keys);
$values = Arr::only($this->attributes, $keys);
}
return new static($values);
}
/**
* Exclude the given attribute from the attribute array.
*
* @param mixed|array $keys
* @return static
*/
public function except($keys)
{
if (is_null($keys)) {
$values = $this->attributes;
} else {
$keys = Arr::wrap($keys);
$values = Arr::except($this->attributes, $keys);
}
return new static($values);
}
/**
* Filter the attributes, returning a bag of attributes that pass the filter.
*
* @param callable $callback
* @return static
*/
public function filter($callback)
{
return new static(collect($this->attributes)->filter($callback)->all());
}
/**
* Return a bag of attributes that have keys starting with the given value / pattern.
*
* @param string $string
* @return static
*/
public function whereStartsWith($string)
{
return $this->filter(function ($value, $key) use ($string) {
return Str::startsWith($key, $string);
});
}
/**
* Return a bag of attributes with keys that do not start with the given value / pattern.
*
* @param string $string
* @return static
*/
public function whereDoesntStartWith($string)
{
return $this->filter(function ($value, $key) use ($string) {
return ! Str::startsWith($key, $string);
});
}
/**
* Return a bag of attributes that have keys starting with the given value / pattern.
*
* @param string $string
* @return static
*/
public function thatStartWith($string)
{
return $this->whereStartsWith($string);
}
/**
* Exclude the given attribute from the attribute array.
*
* @param mixed|array $keys
* @return static
*/
public function exceptProps($keys)
{
$props = [];
foreach ($keys as $key => $defaultValue) {
$key = is_numeric($key) ? $defaultValue : $key;
$props[] = $key;
$props[] = Str::kebab($key);
}
return $this->except($props);
}
/**
* Merge additional attributes / values into the attribute bag.
*
* @param array $attributeDefaults
* @return static
*/
public function merge(array $attributeDefaults = [])
{
$attributes = [];
$attributeDefaults = array_map(function ($value) {
if (is_null($value) || is_bool($value)) {
return $value;
}
return e($value);
}, $attributeDefaults);
foreach ($this->attributes as $key => $value) {
if ($key !== 'class') {
$attributes[$key] = $value;
continue;
}
$attributes[$key] = implode(' ', array_unique(
array_filter([$attributeDefaults[$key] ?? '', $value])
));
}
return new static(array_merge($attributeDefaults, $attributes));
}
/**
* Set the underlying attributes.
*
* @param array $attributes
* @return void
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
}
/**
* Get content as a string of HTML.
*
* @return string
*/
public function toHtml()
{
return (string) $this;
}
/**
* Merge additional attributes / values into the attribute bag.
*
* @param array $attributeDefaults
* @return \Illuminate\Support\HtmlString
*/
public function __invoke(array $attributeDefaults = [])
{
return new HtmlString((string) $this->merge($attributeDefaults));
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->attributes[$offset]);
}
/**
* Get the value at the given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Set the value at a given offset.
*
* @param string $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
{
$this->attributes[$offset] = $value;
}
/**
* Remove the value at the given offset.
*
* @param string $offset
* @return void
*/
public function offsetUnset($offset)
{
unset($this->attributes[$offset]);
}
/**
* Get an iterator for the items.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->attributes);
}
/**
* Implode the attributes into a single HTML ready string.
*
* @return string
*/
public function __toString()
{
$string = '';
foreach ($this->attributes as $key => $value) {
if ($value === false || is_null($value)) {
continue;
}
if ($value === true) {
$value = $key;
}
$string .= ' '.$key.'="'.str_replace('"', '\\"', trim($value)).'"';
}
return trim($string);
}
}

View File

@@ -2,8 +2,10 @@
namespace Illuminate\View\Concerns;
use Closure;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\View\View;
use InvalidArgumentException;
trait ManagesComponents
@@ -39,14 +41,14 @@ trait ManagesComponents
/**
* Start a component rendering process.
*
* @param string $name
* @param \Illuminate\View\View|\Closure|string $view
* @param array $data
* @return void
*/
public function startComponent($name, array $data = [])
public function startComponent($view, array $data = [])
{
if (ob_start()) {
$this->componentStack[] = $name;
$this->componentStack[] = $view;
$this->componentData[$this->currentComponent()] = $data;
@@ -77,18 +79,27 @@ trait ManagesComponents
*/
public function renderComponent()
{
$name = array_pop($this->componentStack);
$view = array_pop($this->componentStack);
return $this->make($name, $this->componentData($name))->render();
$data = $this->componentData();
if ($view instanceof Closure) {
$view = $view($data);
}
if ($view instanceof View) {
return $view->with($data)->render();
} else {
return $this->make($view, $data)->render();
}
}
/**
* Get the data for the given component.
*
* @param string $name
* @return array
*/
protected function componentData($name)
protected function componentData()
{
return array_merge(
$this->componentData[count($this->componentStack)],

View File

@@ -209,6 +209,17 @@ trait ManagesLayouts
return array_key_exists($name, $this->sections);
}
/**
* Check if section does not exist.
*
* @param string $name
* @return bool
*/
public function sectionMissing($name)
{
return ! $this->hasSection($name);
}
/**
* Get the contents of a section.
*

View File

@@ -2,9 +2,9 @@
namespace Illuminate\View\Engines;
use Exception;
use Illuminate\View\Compilers\CompilerInterface;
use Illuminate\View\ViewException;
use Throwable;
class CompilerEngine extends PhpEngine
{
@@ -51,12 +51,10 @@ class CompilerEngine extends PhpEngine
$this->compiler->compile($path);
}
$compiled = $this->compiler->getCompiledPath($path);
// Once we have the path to the compiled file, we will evaluate the paths with
// typical PHP just like any other templates. We also keep a stack of views
// which have been rendered for right exception messages to be generated.
$results = $this->evaluatePath($compiled, $data);
$results = $this->evaluatePath($this->compiler->getCompiledPath($path), $data);
array_pop($this->lastCompiled);
@@ -66,13 +64,13 @@ class CompilerEngine extends PhpEngine
/**
* Handle a view exception.
*
* @param \Exception $e
* @param \Throwable $e
* @param int $obLevel
* @return void
*
* @throws \Exception
* @throws \Throwable
*/
protected function handleViewException(Exception $e, $obLevel)
protected function handleViewException(Throwable $e, $obLevel)
{
$e = new ViewException($this->getMessage($e), 0, 1, $e->getFile(), $e->getLine(), $e);
@@ -82,10 +80,10 @@ class CompilerEngine extends PhpEngine
/**
* Get the exception message for an exception.
*
* @param \Exception $e
* @param \Throwable $e
* @return string
*/
protected function getMessage(Exception $e)
protected function getMessage(Throwable $e)
{
return $e->getMessage().' (View: '.realpath(last($this->lastCompiled)).')';
}

View File

@@ -2,9 +2,7 @@
namespace Illuminate\View\Engines;
use Exception;
use Illuminate\Contracts\View\Engine;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Throwable;
class PhpEngine implements Engine
@@ -41,10 +39,8 @@ class PhpEngine implements Engine
// an exception is thrown. This prevents any partial views from leaking.
try {
include $__path;
} catch (Exception $e) {
$this->handleViewException($e, $obLevel);
} catch (Throwable $e) {
$this->handleViewException(new FatalThrowableError($e), $obLevel);
$this->handleViewException($e, $obLevel);
}
return ltrim(ob_get_clean());
@@ -53,13 +49,13 @@ class PhpEngine implements Engine
/**
* Handle a view exception.
*
* @param \Exception $e
* @param \Throwable $e
* @param int $obLevel
* @return void
*
* @throws \Exception
* @throws \Throwable
*/
protected function handleViewException(Exception $e, $obLevel)
protected function handleViewException(Throwable $e, $obLevel)
{
while (ob_get_level() > $obLevel) {
ob_end_clean();

View File

@@ -83,6 +83,13 @@ class Factory implements FactoryContract
*/
protected $renderCount = 0;
/**
* The "once" block IDs that have been rendered.
*
* @var array
*/
protected $renderedOnce = [];
/**
* Create a new view factory instance.
*
@@ -281,7 +288,7 @@ class Factory implements FactoryContract
public function getEngineFromPath($path)
{
if (! $extension = $this->getExtension($path)) {
throw new InvalidArgumentException("Unrecognized extension in file: {$path}");
throw new InvalidArgumentException("Unrecognized extension in file: {$path}.");
}
$engine = $this->extensions[$extension];
@@ -293,7 +300,7 @@ class Factory implements FactoryContract
* Get the extension used by the view file.
*
* @param string $path
* @return string
* @return string|null
*/
protected function getExtension($path)
{
@@ -352,6 +359,28 @@ class Factory implements FactoryContract
return $this->renderCount == 0;
}
/**
* Determine if the given once token has been rendered.
*
* @param string $id
* @return bool
*/
public function hasRenderedOnce(string $id)
{
return isset($this->renderedOnce[$id]);
}
/**
* Mark the given once token as having been rendered.
*
* @param string $id
* @return void
*/
public function markAsRenderedOnce(string $id)
{
$this->renderedOnce[$id] = true;
}
/**
* Add a location to the array of view locations.
*
@@ -434,6 +463,7 @@ class Factory implements FactoryContract
public function flushState()
{
$this->renderCount = 0;
$this->renderedOnce = [];
$this->flushSections();
$this->flushStacks();

View File

@@ -0,0 +1,95 @@
<?php
namespace Illuminate\View;
use ArrayIterator;
use Closure;
use Illuminate\Contracts\Support\DeferringDisplayableValue;
use Illuminate\Support\Enumerable;
use IteratorAggregate;
class InvokableComponentVariable implements DeferringDisplayableValue, IteratorAggregate
{
/**
* The callable instance to resolve the variable value.
*
* @var \Closure
*/
protected $callable;
/**
* Create a new variable instance.
*
* @param \Closure $callable
* @return void
*/
public function __construct(Closure $callable)
{
$this->callable = $callable;
}
/**
* Resolve the displayable value that the class is deferring.
*
* @return \Illuminate\Contracts\Support\Htmlable|string
*/
public function resolveDisplayableValue()
{
return $this->__invoke();
}
/**
* Get an interator instance for the variable.
*
* @return \ArrayIterator
*/
public function getIterator()
{
$result = $this->__invoke();
return new ArrayIterator($result instanceof Enumerable ? $result->all() : $result);
}
/**
* Dynamically proxy attribute access to the variable.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->__invoke()->{$key};
}
/**
* Dynamically proxy method access to the variable.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->__invoke()->{$method}(...$parameters);
}
/**
* Resolve the variable.
*
* @return mixed
*/
public function __invoke()
{
return call_user_func($this->callable);
}
/**
* Resolve the variable as a string.
*
* @return mixed
*/
public function __toString()
{
return (string) $this->__invoke();
}
}

View File

@@ -4,7 +4,6 @@ namespace Illuminate\View;
use ArrayAccess;
use BadMethodCallException;
use Exception;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\Support\MessageProvider;
@@ -14,6 +13,7 @@ use Illuminate\Contracts\View\View as ViewContract;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\ViewErrorBag;
use Throwable;
class View implements ArrayAccess, Htmlable, ViewContract
@@ -98,10 +98,6 @@ class View implements ArrayAccess, Htmlable, ViewContract
$this->factory->flushStateIfDoneRendering();
return ! is_null($response) ? $response : $contents;
} catch (Exception $e) {
$this->factory->flushState();
throw $e;
} catch (Throwable $e) {
$this->factory->flushState();
@@ -210,25 +206,27 @@ class View implements ArrayAccess, Htmlable, ViewContract
* Add validation errors to the view.
*
* @param \Illuminate\Contracts\Support\MessageProvider|array $provider
* @param string $bag
* @return $this
*/
public function withErrors($provider)
public function withErrors($provider, $bag = 'default')
{
$this->with('errors', $this->formatErrors($provider));
return $this;
return $this->with('errors', (new ViewErrorBag)->put(
$bag, $this->formatErrors($provider)
));
}
/**
* Format the given message provider into a MessageBag.
* Parse the given errors into an appropriate value.
*
* @param \Illuminate\Contracts\Support\MessageProvider|array $provider
* @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider
* @return \Illuminate\Support\MessageBag
*/
protected function formatErrors($provider)
{
return $provider instanceof MessageProvider
? $provider->getMessageBag() : new MessageBag((array) $provider);
? $provider->getMessageBag()
: new MessageBag((array) $provider);
}
/**

View File

@@ -5,7 +5,7 @@ namespace Illuminate\View;
class ViewName
{
/**
* Normalize the given event name.
* Normalize the given view name.
*
* @param string $name
* @return string

View File

@@ -87,10 +87,8 @@ class ViewServiceProvider extends ServiceProvider
*/
public function registerBladeCompiler()
{
$this->app->singleton('blade.compiler', function () {
return new BladeCompiler(
$this->app['files'], $this->app['config']['view.compiled']
);
$this->app->singleton('blade.compiler', function ($app) {
return new BladeCompiler($app['files'], $app['config']['view.compiled']);
});
}

View File

@@ -16,12 +16,11 @@
"require": {
"php": "^7.2.5|^8.0",
"ext-json": "*",
"illuminate/container": "^6.0",
"illuminate/contracts": "^6.0",
"illuminate/events": "^6.0",
"illuminate/filesystem": "^6.0",
"illuminate/support": "^6.0",
"symfony/debug": "^4.3.4"
"illuminate/container": "^7.0",
"illuminate/contracts": "^7.0",
"illuminate/events": "^7.0",
"illuminate/filesystem": "^7.0",
"illuminate/support": "^7.0"
},
"autoload": {
"psr-4": {
@@ -30,7 +29,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
"dev-master": "7.x-dev"
}
},
"config": {