Subiendo proyecto completo sin restricciones de git ignore

This commit is contained in:
Jose Sanchez
2023-08-17 11:44:02 -04:00
parent a0d4f5ba3b
commit 20f1c60600
19921 changed files with 2509159 additions and 45 deletions

View File

@@ -0,0 +1,60 @@
<?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 ?: $this->newAttributeBag();
return array_merge(
($this->data['attributes'] ?? null)?->getAttributes() ?: [],
$this->attributes->getAttributes(),
$this->data,
['attributes' => $this->attributes]
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Illuminate\View;
class AppendableAttributeValue
{
/**
* The attribute value.
*
* @var mixed
*/
public $value;
/**
* Create a new appendable attribute value.
*
* @param mixed $value
* @return void
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Get the string value.
*
* @return string
*/
public function __toString()
{
return (string) $this->value;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,124 @@
<?php
namespace Illuminate\View\Compilers;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Str;
use InvalidArgumentException;
abstract class Compiler
{
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The cache path for the compiled views.
*
* @var string
*/
protected $cachePath;
/**
* The base path that should be removed from paths before hashing.
*
* @var string
*/
protected $basePath;
/**
* Determines if compiled views should be cached.
*
* @var bool
*/
protected $shouldCache;
/**
* The compiled view file extension.
*
* @var string
*/
protected $compiledExtension = 'php';
/**
* Create a new compiler instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $cachePath
* @param string $basePath
* @param bool $shouldCache
* @param string $compiledExtension
* @return void
*
* @throws \InvalidArgumentException
*/
public function __construct(
Filesystem $files,
$cachePath,
$basePath = '',
$shouldCache = true,
$compiledExtension = 'php')
{
if (! $cachePath) {
throw new InvalidArgumentException('Please provide a valid cache path.');
}
$this->files = $files;
$this->cachePath = $cachePath;
$this->basePath = $basePath;
$this->shouldCache = $shouldCache;
$this->compiledExtension = $compiledExtension;
}
/**
* Get the path to the compiled version of a view.
*
* @param string $path
* @return string
*/
public function getCompiledPath($path)
{
return $this->cachePath.'/'.sha1('v2'.Str::after($path, $this->basePath)).'.'.$this->compiledExtension;
}
/**
* Determine if the view at the given path is expired.
*
* @param string $path
* @return bool
*/
public function isExpired($path)
{
if (! $this->shouldCache) {
return true;
}
$compiled = $this->getCompiledPath($path);
// If the compiled file doesn't exist we will indicate that the view is expired
// so that it can be re-compiled. Else, we will verify the last modification
// of the views is less than the modification times of the compiled views.
if (! $this->files->exists($compiled)) {
return true;
}
return $this->files->lastModified($path) >=
$this->files->lastModified($compiled);
}
/**
* Create the compiled file directory if necessary.
*
* @param string $path
* @return void
*/
protected function ensureCompiledDirectoryExists($path)
{
if (! $this->files->exists(dirname($path))) {
$this->files->makeDirectory(dirname($path), 0777, true, true);
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Illuminate\View\Compilers;
interface CompilerInterface
{
/**
* Get the path to the compiled version of a view.
*
* @param string $path
* @return string
*/
public function getCompiledPath($path);
/**
* Determine if the given view is expired.
*
* @param string $path
* @return bool
*/
public function isExpired($path);
/**
* Compile the view at the given path.
*
* @param string $path
* @return void
*/
public function compile($path);
}

View File

@@ -0,0 +1,789 @@
<?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\DynamicComponent;
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 component class namespaces.
*
* @var array
*/
protected $namespaces = [];
/**
* The "bind:" attributes that have been compiled for the current component.
*
* @var array
*/
protected $boundAttributes = [];
/**
* Create a new component tag compiler.
*
* @param array $aliases
* @param array $namespaces
* @param \Illuminate\View\Compilers\BladeCompiler|null $blade
* @return void
*/
public function __construct(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null)
{
$this->aliases = $aliases;
$this->namespaces = $namespaces;
$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+
(?:
(?:
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
@(?:style)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
(\:\\\$)(\w+)
)
|
(?:
[\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+
(?:
(?:
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
@(?:style)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
(\:\\\$)(\w+)
)
|
(?:
[\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##END-COMPONENT-CLASS##";
}, $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 exist 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)) {
$view = Str::startsWith($component, 'mail::')
? "\$__env->getContainer()->make(Illuminate\\View\\Factory::class)->make('{$component}')"
: "'$class'";
$parameters = [
'view' => $view,
'data' => '['.$this->attributesToString($data->all(), $escapeBound = false).']',
];
$class = AnonymousComponent::class;
} else {
$parameters = $data->all();
}
return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).'])
<?php if (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag && $constructor = (new ReflectionClass('.$class.'::class))->getConstructor()): ?>
<?php $attributes = $attributes->except(collect($constructor->getParameters())->map->getName()->all()); ?>
<?php endif; ?>
<?php $component->withAttributes(['.$this->attributesToString($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>';
}
/**
* Get the component class for a given component alias.
*
* @param string $component
* @return string
*
* @throws \InvalidArgumentException
*/
public 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 = $this->findClassByComponent($component)) {
return $class;
}
if (class_exists($class = $this->guessClassName($component))) {
return $class;
}
if (! is_null($guess = $this->guessAnonymousComponentUsingNamespaces($viewFactory, $component)) ||
! is_null($guess = $this->guessAnonymousComponentUsingPaths($viewFactory, $component))) {
return $guess;
}
if (Str::startsWith($component, 'mail::')) {
return $component;
}
throw new InvalidArgumentException(
"Unable to locate a class or view for component [{$component}]."
);
}
/**
* Attempt to find an anonymous component using the registered anonymous component paths.
*
* @param \Illuminate\Contracts\View\Factory $viewFactory
* @param string $component
* @return string|null
*/
protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, string $component)
{
$delimiter = ViewFinderInterface::HINT_PATH_DELIMITER;
foreach ($this->blade->getAnonymousComponentPaths() as $path) {
try {
if (str_contains($component, $delimiter) &&
! str_starts_with($component, $path['prefix'].$delimiter)) {
continue;
}
$formattedComponent = str_starts_with($component, $path['prefix'].$delimiter)
? Str::after($component, $delimiter)
: $component;
if (! is_null($guess = match (true) {
$viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent) => $guess,
$viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent.'.index') => $guess,
default => null,
})) {
return $guess;
}
} catch (InvalidArgumentException $e) {
//
}
}
}
/**
* Attempt to find an anonymous component using the registered anonymous component namespaces.
*
* @param \Illuminate\Contracts\View\Factory $viewFactory
* @param string $component
* @return string|null
*/
protected function guessAnonymousComponentUsingNamespaces(Factory $viewFactory, string $component)
{
return collect($this->blade->getAnonymousComponentNamespaces())
->filter(function ($directory, $prefix) use ($component) {
return Str::startsWith($component, $prefix.'::');
})
->prepend('components', $component)
->reduce(function ($carry, $directory, $prefix) use ($component, $viewFactory) {
if (! is_null($carry)) {
return $carry;
}
$componentName = Str::after($component, $prefix.'::');
if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory))) {
return $view;
}
if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory).'.index')) {
return $view;
}
});
}
/**
* Find the class for the given component using the registered namespaces.
*
* @param string $component
* @return string|null
*/
public function findClassByComponent(string $component)
{
$segments = explode('::', $component);
$prefix = $segments[0];
if (! isset($this->namespaces[$prefix], $segments[1])) {
return;
}
if (class_exists($class = $this->namespaces[$prefix].'\\'.$this->formatClassName($segments[1]))) {
return $class;
}
}
/**
* 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();
$class = $this->formatClassName($component);
return $namespace.'View\\Components\\'.$class;
}
/**
* Format the class name for the given component.
*
* @param string $component
* @return string
*/
public function formatClassName(string $component)
{
$componentPieces = array_map(function ($componentPiece) {
return ucfirst(Str::camel($componentPiece));
}, explode('.', $component));
return implode('\\', $componentPieces);
}
/**
* Guess the view name for the given component.
*
* @param string $name
* @param string $prefix
* @return string
*/
public function guessViewName($name, $prefix = 'components.')
{
if (! Str::endsWith($prefix, '.')) {
$prefix .= '.';
}
$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
*/
public function partitionDataAndAttributes($class, array $attributes)
{
// If the class doesn't exist, we'll assume it is 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##END-COMPONENT-CLASS##', $value);
}
/**
* Compile the slot tags within the given string.
*
* @param string $value
* @return string
*/
public function compileSlots(string $value)
{
$pattern = "/
<
\s*
x[\-\:]slot
(?:\:(?<inlineName>\w+(?:-\w+)*))?
(?:\s+(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))?
(?<attributes>
(?:
\s+
(?:
(?:
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
@(?:style)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)?
)
)
)*
\s*
)
(?<![\/=\-])
>
/x";
$value = preg_replace_callback($pattern, function ($matches) {
$name = $this->stripQuotes($matches['inlineName'] ?: $matches['name']);
if (Str::contains($name, '-') && ! empty($matches['inlineName'])) {
$name = Str::camel($name);
}
if ($matches[2] !== ':') {
$name = "'{$name}'";
}
$this->boundAttributes = [];
$attributes = $this->getAttributesFromAttributeString($matches['attributes']);
return " @slot({$name}, null, [".$this->attributesToString($attributes).']) ';
}, $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->parseShortAttributeSyntax($attributeString);
$attributeString = $this->parseAttributeBag($attributeString);
$attributeString = $this->parseComponentTagClassStatements($attributeString);
$attributeString = $this->parseComponentTagStyleStatements($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_starts_with($attribute, 'bind:')) {
$attribute = Str::after($attribute, 'bind:');
$this->boundAttributes[$attribute] = true;
} else {
$value = "'".$this->compileAttributeEchos($value)."'";
}
if (str_starts_with($attribute, '::')) {
$attribute = substr($attribute, 1);
}
return [$attribute => $value];
})->toArray();
}
/**
* Parses a short attribute syntax like :$foo into a fully-qualified syntax like :foo="$foo".
*
* @param string $value
* @return string
*/
protected function parseShortAttributeSyntax(string $value)
{
$pattern = "/\s\:\\\$(\w+)/x";
return preg_replace_callback($pattern, function (array $matches) {
return " :{$matches[1]}=\"\${$matches[1]}\"";
}, $value);
}
/**
* Parse the attribute bag in a given attribute string into its fully-qualified syntax.
*
* @param string $attributeString
* @return string
*/
protected function parseAttributeBag(string $attributeString)
{
$pattern = "/
(?:^|\s+) # start of the string or whitespace between attributes
\{\{\s*(\\\$attributes(?:[^}]+?(?<!\s))?)\s*\}\} # exact match of attributes variable being echoed
/x";
return preg_replace($pattern, ' :attributes="$1"', $attributeString);
}
/**
* Parse @class statements in a given attribute string into their fully-qualified syntax.
*
* @param string $attributeString
* @return string
*/
protected function parseComponentTagClassStatements(string $attributeString)
{
return preg_replace_callback(
'/@(class)(\( ( (?>[^()]+) | (?2) )* \))/x', function ($match) {
if ($match[1] === 'class') {
$match[2] = str_replace('"', "'", $match[2]);
return ":class=\"\Illuminate\Support\Arr::toCssClasses{$match[2]}\"";
}
return $match[0];
}, $attributeString
);
}
/**
* Parse @style statements in a given attribute string into their fully-qualified syntax.
*
* @param string $attributeString
* @return string
*/
protected function parseComponentTagStyleStatements(string $attributeString)
{
return preg_replace_callback(
'/@(style)(\( ( (?>[^()]+) | (?2) )* \))/x', function ($match) {
if ($match[1] === 'style') {
$match[2] = str_replace('"', "'", $match[2]);
return ":style=\"\Illuminate\Support\Arr::toCssStyles{$match[2]}\"";
}
return $match[0];
}, $attributeString
);
}
/**
* 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 single colon
([\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

@@ -0,0 +1,102 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesAuthorizations
{
/**
* Compile the can statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileCan($expression)
{
return "<?php if (app(\Illuminate\\Contracts\\Auth\\Access\\Gate::class)->check{$expression}): ?>";
}
/**
* Compile the cannot statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileCannot($expression)
{
return "<?php if (app(\Illuminate\\Contracts\\Auth\\Access\\Gate::class)->denies{$expression}): ?>";
}
/**
* Compile the canany statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileCanany($expression)
{
return "<?php if (app(\Illuminate\\Contracts\\Auth\\Access\\Gate::class)->any{$expression}): ?>";
}
/**
* Compile the else-can statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileElsecan($expression)
{
return "<?php elseif (app(\Illuminate\\Contracts\\Auth\\Access\\Gate::class)->check{$expression}): ?>";
}
/**
* Compile the else-cannot statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileElsecannot($expression)
{
return "<?php elseif (app(\Illuminate\\Contracts\\Auth\\Access\\Gate::class)->denies{$expression}): ?>";
}
/**
* Compile the else-canany statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileElsecanany($expression)
{
return "<?php elseif (app(\Illuminate\\Contracts\\Auth\\Access\\Gate::class)->any{$expression}): ?>";
}
/**
* Compile the end-can statements into valid PHP.
*
* @return string
*/
protected function compileEndcan()
{
return '<?php endif; ?>';
}
/**
* Compile the end-cannot statements into valid PHP.
*
* @return string
*/
protected function compileEndcannot()
{
return '<?php endif; ?>';
}
/**
* Compile the end-canany statements into valid PHP.
*
* @return string
*/
protected function compileEndcanany()
{
return '<?php endif; ?>';
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesClasses
{
/**
* Compile the conditional class statement into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileClass($expression)
{
$expression = is_null($expression) ? '([])' : $expression;
return "class=\"<?php echo \Illuminate\Support\Arr::toCssClasses{$expression} ?>\"";
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesComments
{
/**
* Compile Blade comments into an empty string.
*
* @param string $value
* @return string
*/
protected function compileComments($value)
{
$pattern = sprintf('/%s--(.*?)--%s/s', $this->contentTags[0], $this->contentTags[1]);
return preg_replace($pattern, '', $value);
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString;
use Illuminate\Support\Str;
use Illuminate\View\ComponentAttributeBag;
trait CompilesComponents
{
/**
* The component name hash stack.
*
* @var array
*/
protected static $componentHashStack = [];
/**
* Compile the component statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileComponent($expression)
{
[$component, $alias, $data] = str_contains($expression, ',')
? 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 = '.$component.'::resolve('.($data ?: '[]').' + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? (array) $attributes->getIterator() : [])); ?>',
'<?php $component->withName('.$alias.'); ?>',
'<?php if ($component->shouldRender()): ?>',
'<?php $__env->startComponent($component->resolveView(), $component->data()); ?>',
]);
}
/**
* Compile the end-component statements into valid PHP.
*
* @return string
*/
protected function compileEndComponent()
{
return '<?php echo $__env->renderComponent(); ?>';
}
/**
* Compile the end-component statements into valid PHP.
*
* @return string
*/
public function compileEndComponentClass()
{
$hash = array_pop(static::$componentHashStack);
return $this->compileEndComponent()."\n".implode("\n", [
'<?php endif; ?>',
'<?php if (isset($__componentOriginal'.$hash.')): ?>',
'<?php $component = $__componentOriginal'.$hash.'; ?>',
'<?php unset($__componentOriginal'.$hash.'); ?>',
'<?php endif; ?>',
]);
}
/**
* Compile the slot statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileSlot($expression)
{
return "<?php \$__env->slot{$expression}; ?>";
}
/**
* Compile the end-slot statements into valid PHP.
*
* @return string
*/
protected function compileEndSlot()
{
return '<?php $__env->endSlot(); ?>';
}
/**
* Compile the component-first statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileComponentFirst($expression)
{
return "<?php \$__env->startComponentFirst{$expression}; ?>";
}
/**
* Compile the end-component-first statements into valid PHP.
*
* @return string
*/
protected function compileEndComponentFirst()
{
return $this->compileEndComponent();
}
/**
* Compile the prop statement into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileProps($expression)
{
return "<?php \$attributes ??= new \\Illuminate\\View\\ComponentAttributeBag; ?>
<?php foreach(\$attributes->onlyProps{$expression} as \$__key => \$__value) {
\$\$__key = \$\$__key ?? \$__value;
} ?>
<?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); ?>";
}
/**
* Compile the aware statement into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileAware($expression)
{
return "<?php foreach ({$expression} as \$__key => \$__value) {
\$__consumeVariable = is_string(\$__key) ? \$__key : \$__value;
\$\$__consumeVariable = is_string(\$__key) ? \$__env->getConsumableComponentData(\$__key, \$__value) : \$__env->getConsumableComponentData(\$__value);
} ?>";
}
/**
* Sanitize the given component attribute value.
*
* @param mixed $value
* @return mixed
*/
public static function sanitizeComponentAttribute($value)
{
if ($value instanceof CanBeEscapedWhenCastToString) {
return $value->escapeWhenCastingToString();
}
return is_string($value) ||
(is_object($value) && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString'))
? e($value)
: $value;
}
}

View File

@@ -0,0 +1,385 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Support\Str;
trait CompilesConditionals
{
/**
* Identifier for the first case in the switch statement.
*
* @var bool
*/
protected $firstCaseInSwitch = true;
/**
* Compile the if-auth statements into valid PHP.
*
* @param string|null $guard
* @return string
*/
protected function compileAuth($guard = null)
{
$guard = is_null($guard) ? '()' : $guard;
return "<?php if(auth()->guard{$guard}->check()): ?>";
}
/**
* Compile the else-auth statements into valid PHP.
*
* @param string|null $guard
* @return string
*/
protected function compileElseAuth($guard = null)
{
$guard = is_null($guard) ? '()' : $guard;
return "<?php elseif(auth()->guard{$guard}->check()): ?>";
}
/**
* Compile the end-auth statements into valid PHP.
*
* @return string
*/
protected function compileEndAuth()
{
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.
*
* @param string|null $guard
* @return string
*/
protected function compileGuest($guard = null)
{
$guard = is_null($guard) ? '()' : $guard;
return "<?php if(auth()->guard{$guard}->guest()): ?>";
}
/**
* Compile the else-guest statements into valid PHP.
*
* @param string|null $guard
* @return string
*/
protected function compileElseGuest($guard = null)
{
$guard = is_null($guard) ? '()' : $guard;
return "<?php elseif(auth()->guard{$guard}->guest()): ?>";
}
/**
* Compile the end-guest statements into valid PHP.
*
* @return string
*/
protected function compileEndGuest()
{
return '<?php endif; ?>';
}
/**
* Compile the has-section statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileHasSection($expression)
{
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.
*
* @param string $expression
* @return string
*/
protected function compileIf($expression)
{
return "<?php if{$expression}: ?>";
}
/**
* Compile the unless statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileUnless($expression)
{
return "<?php if (! {$expression}): ?>";
}
/**
* Compile the else-if statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileElseif($expression)
{
return "<?php elseif{$expression}: ?>";
}
/**
* Compile the else statements into valid PHP.
*
* @return string
*/
protected function compileElse()
{
return '<?php else: ?>';
}
/**
* Compile the end-if statements into valid PHP.
*
* @return string
*/
protected function compileEndif()
{
return '<?php endif; ?>';
}
/**
* Compile the end-unless statements into valid PHP.
*
* @return string
*/
protected function compileEndunless()
{
return '<?php endif; ?>';
}
/**
* Compile the if-isset statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileIsset($expression)
{
return "<?php if(isset{$expression}): ?>";
}
/**
* Compile the end-isset statements into valid PHP.
*
* @return string
*/
protected function compileEndIsset()
{
return '<?php endif; ?>';
}
/**
* Compile the switch statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileSwitch($expression)
{
$this->firstCaseInSwitch = true;
return "<?php switch{$expression}:";
}
/**
* Compile the case statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileCase($expression)
{
if ($this->firstCaseInSwitch) {
$this->firstCaseInSwitch = false;
return "case {$expression}: ?>";
}
return "<?php case {$expression}: ?>";
}
/**
* Compile the default statements in switch case into valid PHP.
*
* @return string
*/
protected function compileDefault()
{
return '<?php default: ?>';
}
/**
* Compile the end switch statements into valid PHP.
*
* @return string
*/
protected function compileEndSwitch()
{
return '<?php endswitch; ?>';
}
/**
* Compile a once block into valid PHP.
*
* @param string|null $id
* @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; ?>';
}
/**
* Compile a selected block into valid PHP.
*
* @param string $condition
* @return string
*/
protected function compileSelected($condition)
{
return "<?php if{$condition}: echo 'selected'; endif; ?>";
}
/**
* Compile a checked block into valid PHP.
*
* @param string $condition
* @return string
*/
protected function compileChecked($condition)
{
return "<?php if{$condition}: echo 'checked'; endif; ?>";
}
/**
* Compile a disabled block into valid PHP.
*
* @param string $condition
* @return string
*/
protected function compileDisabled($condition)
{
return "<?php if{$condition}: echo 'disabled'; endif; ?>";
}
/**
* Compile a required block into valid PHP.
*
* @param string $condition
* @return string
*/
protected function compileRequired($condition)
{
return "<?php if{$condition}: echo 'required'; endif; ?>";
}
/**
* Compile a readonly block into valid PHP.
*
* @param string $condition
* @return string
*/
protected function compileReadonly($condition)
{
return "<?php if{$condition}: echo 'readonly'; endif; ?>";
}
/**
* Compile the push statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compilePushIf($expression)
{
$parts = explode(',', $this->stripParentheses($expression), 2);
return "<?php if({$parts[0]}): \$__env->startPush({$parts[1]}); ?>";
}
/**
* Compile the end-push statements into valid PHP.
*
* @return string
*/
protected function compileEndPushIf()
{
return '<?php $__env->stopPush(); endif; ?>';
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
use Closure;
use Illuminate\Support\Str;
trait CompilesEchos
{
/**
* Custom rendering callbacks for stringable objects.
*
* @var array
*/
protected $echoHandlers = [];
/**
* Add a handler to be executed before echoing a given class.
*
* @param string|callable $class
* @param callable|null $handler
* @return void
*/
public function stringable($class, $handler = null)
{
if ($class instanceof Closure) {
[$class, $handler] = [$this->firstClosureParameterType($class), $class];
}
$this->echoHandlers[$class] = $handler;
}
/**
* Compile Blade echos into valid PHP.
*
* @param string $value
* @return string
*/
public function compileEchos($value)
{
foreach ($this->getEchoMethods() as $method) {
$value = $this->$method($value);
}
return $value;
}
/**
* Get the echo methods in the proper order for compilation.
*
* @return array
*/
protected function getEchoMethods()
{
return [
'compileRawEchos',
'compileEscapedEchos',
'compileRegularEchos',
];
}
/**
* Compile the "raw" echo statements.
*
* @param string $value
* @return string
*/
protected function compileRawEchos($value)
{
$pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]);
$callback = function ($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
return $matches[1]
? substr($matches[0], 1)
: "<?php echo {$this->wrapInEchoHandler($matches[2])}; ?>{$whitespace}";
};
return preg_replace_callback($pattern, $callback, $value);
}
/**
* Compile the "regular" echo statements.
*
* @param string $value
* @return string
*/
protected function compileRegularEchos($value)
{
$pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]);
$callback = function ($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
$wrapped = sprintf($this->echoFormat, $this->wrapInEchoHandler($matches[2]));
return $matches[1] ? substr($matches[0], 1) : "<?php echo {$wrapped}; ?>{$whitespace}";
};
return preg_replace_callback($pattern, $callback, $value);
}
/**
* Compile the escaped echo statements.
*
* @param string $value
* @return string
*/
protected function compileEscapedEchos($value)
{
$pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]);
$callback = function ($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
return $matches[1]
? $matches[0]
: "<?php echo e({$this->wrapInEchoHandler($matches[2])}); ?>{$whitespace}";
};
return preg_replace_callback($pattern, $callback, $value);
}
/**
* Add an instance of the blade echo handler to the start of the compiled string.
*
* @param string $result
* @return string
*/
protected function addBladeCompilerVariable($result)
{
return "<?php \$__bladeCompiler = app('blade.compiler'); ?>".$result;
}
/**
* Wrap the echoable value in an echo handler if applicable.
*
* @param string $value
* @return string
*/
protected function wrapInEchoHandler($value)
{
$value = Str::of($value)
->trim()
->when(str_ends_with($value, ';'), function ($str) {
return $str->beforeLast(';');
});
return empty($this->echoHandlers) ? $value : '$__bladeCompiler->applyEchoHandler('.$value.')';
}
/**
* Apply the echo handler for the value if it exists.
*
* @param string $value
* @return string
*/
public function applyEchoHandler($value)
{
if (is_object($value) && isset($this->echoHandlers[get_class($value)])) {
return call_user_func($this->echoHandlers[get_class($value)], $value);
}
return $value;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesErrors
{
/**
* Compile the error statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileError($expression)
{
$expression = $this->stripParentheses($expression);
return '<?php $__errorArgs = ['.$expression.'];
$__bag = $errors->getBag($__errorArgs[1] ?? \'default\');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>';
}
/**
* Compile the enderror statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileEnderror($expression)
{
return '<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?>';
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesFragments
{
/**
* The last compiled fragment.
*
* @var string
*/
protected $lastFragment;
/**
* Compile the fragment statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileFragment($expression)
{
$this->lastFragment = trim($expression, "()'\" ");
return "<?php \$__env->startFragment{$expression}; ?>";
}
/**
* Compile the end-fragment statements into valid PHP.
*
* @return string
*/
protected function compileEndfragment()
{
return '<?php echo $__env->stopFragment(); ?>';
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Foundation\Vite;
trait CompilesHelpers
{
/**
* Compile the CSRF statements into valid PHP.
*
* @return string
*/
protected function compileCsrf()
{
return '<?php echo csrf_field(); ?>';
}
/**
* Compile the "dd" statements into valid PHP.
*
* @param string $arguments
* @return string
*/
protected function compileDd($arguments)
{
return "<?php dd{$arguments}; ?>";
}
/**
* Compile the "dump" statements into valid PHP.
*
* @param string $arguments
* @return string
*/
protected function compileDump($arguments)
{
return "<?php dump{$arguments}; ?>";
}
/**
* Compile the method statements into valid PHP.
*
* @param string $method
* @return string
*/
protected function compileMethod($method)
{
return "<?php echo method_field{$method}; ?>";
}
/**
* Compile the "vite" statements into valid PHP.
*
* @param string|null $arguments
* @return string
*/
protected function compileVite($arguments)
{
$arguments ??= '()';
$class = Vite::class;
return "<?php echo app('$class'){$arguments}; ?>";
}
/**
* Compile the "viteReactRefresh" statements into valid PHP.
*
* @return string
*/
protected function compileViteReactRefresh()
{
$class = Vite::class;
return "<?php echo app('$class')->reactRefresh(); ?>";
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesIncludes
{
/**
* Compile the each statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileEach($expression)
{
return "<?php echo \$__env->renderEach{$expression}; ?>";
}
/**
* Compile the include statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileInclude($expression)
{
$expression = $this->stripParentheses($expression);
return "<?php echo \$__env->make({$expression}, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>";
}
/**
* Compile the include-if statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileIncludeIf($expression)
{
$expression = $this->stripParentheses($expression);
return "<?php if (\$__env->exists({$expression})) echo \$__env->make({$expression}, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>";
}
/**
* Compile the include-when statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileIncludeWhen($expression)
{
$expression = $this->stripParentheses($expression);
return "<?php echo \$__env->renderWhen($expression, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path'])); ?>";
}
/**
* Compile the include-unless statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileIncludeUnless($expression)
{
$expression = $this->stripParentheses($expression);
return "<?php echo \$__env->renderUnless($expression, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path'])); ?>";
}
/**
* Compile the include-first statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileIncludeFirst($expression)
{
$expression = $this->stripParentheses($expression);
return "<?php echo \$__env->first({$expression}, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>";
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesInjections
{
/**
* Compile the inject statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileInject($expression)
{
$segments = explode(',', preg_replace("/[\(\)]/", '', $expression));
$variable = trim($segments[0], " '\"");
$service = trim($segments[1]);
return "<?php \${$variable} = app({$service}); ?>";
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Support\Js;
trait CompilesJs
{
/**
* Compile the "@js" directive into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileJs(string $expression)
{
return sprintf(
"<?php echo \%s::from(%s)->toHtml() ?>",
Js::class, $this->stripParentheses($expression)
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesJson
{
/**
* The default JSON encoding options.
*
* @var int
*/
private $encodingOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
/**
* Compile the JSON statement into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileJson($expression)
{
$parts = explode(',', $this->stripParentheses($expression));
$options = isset($parts[1]) ? trim($parts[1]) : $this->encodingOptions;
$depth = isset($parts[2]) ? trim($parts[2]) : 512;
return "<?php echo json_encode($parts[0], $options, $depth) ?>";
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesLayouts
{
/**
* The name of the last section that was started.
*
* @var string
*/
protected $lastSection;
/**
* Compile the extends statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileExtends($expression)
{
$expression = $this->stripParentheses($expression);
$echo = "<?php echo \$__env->make({$expression}, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>";
$this->footer[] = $echo;
return '';
}
/**
* Compile the extends-first statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileExtendsFirst($expression)
{
$expression = $this->stripParentheses($expression);
$echo = "<?php echo \$__env->first({$expression}, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>";
$this->footer[] = $echo;
return '';
}
/**
* Compile the section statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileSection($expression)
{
$this->lastSection = trim($expression, "()'\" ");
return "<?php \$__env->startSection{$expression}; ?>";
}
/**
* Replace the @parent directive to a placeholder.
*
* @return string
*/
protected function compileParent()
{
$escapedLastSection = strtr($this->lastSection, ['\\' => '\\\\', "'" => "\\'"]);
return "<?php echo \Illuminate\View\Factory::parentPlaceholder('{$escapedLastSection}'); ?>";
}
/**
* Compile the yield statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileYield($expression)
{
return "<?php echo \$__env->yieldContent{$expression}; ?>";
}
/**
* Compile the show statements into valid PHP.
*
* @return string
*/
protected function compileShow()
{
return '<?php echo $__env->yieldSection(); ?>';
}
/**
* Compile the append statements into valid PHP.
*
* @return string
*/
protected function compileAppend()
{
return '<?php $__env->appendSection(); ?>';
}
/**
* Compile the overwrite statements into valid PHP.
*
* @return string
*/
protected function compileOverwrite()
{
return '<?php $__env->stopSection(true); ?>';
}
/**
* Compile the stop statements into valid PHP.
*
* @return string
*/
protected function compileStop()
{
return '<?php $__env->stopSection(); ?>';
}
/**
* Compile the end-section statements into valid PHP.
*
* @return string
*/
protected function compileEndsection()
{
return '<?php $__env->stopSection(); ?>';
}
}

View File

@@ -0,0 +1,194 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Contracts\View\ViewCompilationException;
trait CompilesLoops
{
/**
* Counter to keep track of nested forelse statements.
*
* @var int
*/
protected $forElseCounter = 0;
/**
* Compile the for-else statements into valid PHP.
*
* @param string $expression
* @return string
*
* @throws \Illuminate\Contracts\View\ViewCompilationException
*/
protected function compileForelse($expression)
{
$empty = '$__empty_'.++$this->forElseCounter;
preg_match('/\( *(.+) +as +(.+)\)$/is', $expression ?? '', $matches);
if (count($matches) === 0) {
throw new ViewCompilationException('Malformed @forelse statement.');
}
$iteratee = trim($matches[1]);
$iteration = trim($matches[2]);
$initLoop = "\$__currentLoopData = {$iteratee}; \$__env->addLoop(\$__currentLoopData);";
$iterateLoop = '$__env->incrementLoopIndices(); $loop = $__env->getLastLoop();';
return "<?php {$empty} = true; {$initLoop} foreach(\$__currentLoopData as {$iteration}): {$iterateLoop} {$empty} = false; ?>";
}
/**
* Compile the for-else-empty and empty statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileEmpty($expression)
{
if ($expression) {
return "<?php if(empty{$expression}): ?>";
}
$empty = '$__empty_'.$this->forElseCounter--;
return "<?php endforeach; \$__env->popLoop(); \$loop = \$__env->getLastLoop(); if ({$empty}): ?>";
}
/**
* Compile the end-for-else statements into valid PHP.
*
* @return string
*/
protected function compileEndforelse()
{
return '<?php endif; ?>';
}
/**
* Compile the end-empty statements into valid PHP.
*
* @return string
*/
protected function compileEndEmpty()
{
return '<?php endif; ?>';
}
/**
* Compile the for statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileFor($expression)
{
return "<?php for{$expression}: ?>";
}
/**
* Compile the for-each statements into valid PHP.
*
* @param string $expression
* @return string
*
* @throws \Illuminate\Contracts\View\ViewCompilationException
*/
protected function compileForeach($expression)
{
preg_match('/\( *(.+) +as +(.*)\)$/is', $expression ?? '', $matches);
if (count($matches) === 0) {
throw new ViewCompilationException('Malformed @foreach statement.');
}
$iteratee = trim($matches[1]);
$iteration = trim($matches[2]);
$initLoop = "\$__currentLoopData = {$iteratee}; \$__env->addLoop(\$__currentLoopData);";
$iterateLoop = '$__env->incrementLoopIndices(); $loop = $__env->getLastLoop();';
return "<?php {$initLoop} foreach(\$__currentLoopData as {$iteration}): {$iterateLoop} ?>";
}
/**
* Compile the break statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileBreak($expression)
{
if ($expression) {
preg_match('/\(\s*(-?\d+)\s*\)$/', $expression, $matches);
return $matches ? '<?php break '.max(1, $matches[1]).'; ?>' : "<?php if{$expression} break; ?>";
}
return '<?php break; ?>';
}
/**
* Compile the continue statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileContinue($expression)
{
if ($expression) {
preg_match('/\(\s*(-?\d+)\s*\)$/', $expression, $matches);
return $matches ? '<?php continue '.max(1, $matches[1]).'; ?>' : "<?php if{$expression} continue; ?>";
}
return '<?php continue; ?>';
}
/**
* Compile the end-for statements into valid PHP.
*
* @return string
*/
protected function compileEndfor()
{
return '<?php endfor; ?>';
}
/**
* Compile the end-for-each statements into valid PHP.
*
* @return string
*/
protected function compileEndforeach()
{
return '<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>';
}
/**
* Compile the while statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileWhile($expression)
{
return "<?php while{$expression}: ?>";
}
/**
* Compile the end-while statements into valid PHP.
*
* @return string
*/
protected function compileEndwhile()
{
return '<?php endwhile; ?>';
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesRawPhp
{
/**
* Compile the raw PHP statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compilePhp($expression)
{
if ($expression) {
return "<?php {$expression}; ?>";
}
return '@php';
}
/**
* Compile the unset statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileUnset($expression)
{
return "<?php unset{$expression}; ?>";
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
use Illuminate\Support\Str;
trait CompilesStacks
{
/**
* Compile the stack statements into the content.
*
* @param string $expression
* @return string
*/
protected function compileStack($expression)
{
return "<?php echo \$__env->yieldPushContent{$expression}; ?>";
}
/**
* Compile the push statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compilePush($expression)
{
return "<?php \$__env->startPush{$expression}; ?>";
}
/**
* Compile the push-once statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compilePushOnce($expression)
{
$parts = explode(',', $this->stripParentheses($expression), 2);
[$stack, $id] = [$parts[0], $parts[1] ?? ''];
$id = trim($id) ?: "'".(string) Str::uuid()."'";
return '<?php if (! $__env->hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.');
$__env->startPush('.$stack.'); ?>';
}
/**
* Compile the end-push statements into valid PHP.
*
* @return string
*/
protected function compileEndpush()
{
return '<?php $__env->stopPush(); ?>';
}
/**
* Compile the end-push-once statements into valid PHP.
*
* @return string
*/
protected function compileEndpushOnce()
{
return '<?php $__env->stopPush(); endif; ?>';
}
/**
* Compile the prepend statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compilePrepend($expression)
{
return "<?php \$__env->startPrepend{$expression}; ?>";
}
/**
* Compile the prepend-once statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compilePrependOnce($expression)
{
$parts = explode(',', $this->stripParentheses($expression), 2);
[$stack, $id] = [$parts[0], $parts[1] ?? ''];
$id = trim($id) ?: "'".(string) Str::uuid()."'";
return '<?php if (! $__env->hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.');
$__env->startPrepend('.$stack.'); ?>';
}
/**
* Compile the end-prepend statements into valid PHP.
*
* @return string
*/
protected function compileEndprepend()
{
return '<?php $__env->stopPrepend(); ?>';
}
/**
* Compile the end-prepend-once statements into valid PHP.
*
* @return string
*/
protected function compileEndprependOnce()
{
return '<?php $__env->stopPrepend(); endif; ?>';
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesStyles
{
/**
* Compile the conditional style statement into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileStyle($expression)
{
$expression = is_null($expression) ? '([])' : $expression;
return "style=\"<?php echo \Illuminate\Support\Arr::toCssStyles{$expression} ?>\"";
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Illuminate\View\Compilers\Concerns;
trait CompilesTranslations
{
/**
* Compile the lang statements into valid PHP.
*
* @param string|null $expression
* @return string
*/
protected function compileLang($expression)
{
if (is_null($expression)) {
return '<?php $__env->startTranslation(); ?>';
} elseif ($expression[1] === '[') {
return "<?php \$__env->startTranslation{$expression}; ?>";
}
return "<?php echo app('translator')->get{$expression}; ?>";
}
/**
* Compile the end-lang statements into valid PHP.
*
* @return string
*/
protected function compileEndlang()
{
return '<?php echo $__env->renderTranslation(); ?>';
}
/**
* Compile the choice statements into valid PHP.
*
* @param string $expression
* @return string
*/
protected function compileChoice($expression)
{
return "<?php echo app('translator')->choice{$expression}; ?>";
}
}

View File

@@ -0,0 +1,458 @@
<?php
namespace Illuminate\View;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View as ViewContract;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
abstract class Component
{
/**
* 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;
/**
* The view factory instance, if any.
*
* @var \Illuminate\Contracts\View\Factory|null
*/
protected static $factory;
/**
* The component resolver callback.
*
* @var (\Closure(string, array): Component)|null
*/
protected static $componentsResolver;
/**
* The cache of blade view names, keyed by contents.
*
* @var array<string, string>
*/
protected static $bladeViewCache = [];
/**
* 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 cache of constructor parameters, keyed by class.
*
* @var array<class-string, array<int, string>>
*/
protected static $constructorParametersCache = [];
/**
* Get the view / view contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\Support\Htmlable|\Closure|string
*/
abstract public function render();
/**
* Resolve the component instance with the given data.
*
* @param array $data
* @return static
*/
public static function resolve($data)
{
if (static::$componentsResolver) {
return call_user_func(static::$componentsResolver, static::class, $data);
}
$parameters = static::extractConstructorParameters();
$dataKeys = array_keys($data);
if (empty(array_diff($parameters, $dataKeys))) {
return new static(...array_intersect_key($data, array_flip($parameters)));
}
return Container::getInstance()->make(static::class, $data);
}
/**
* Extract the constructor parameters for the component.
*
* @return array
*/
protected static function extractConstructorParameters()
{
if (! isset(static::$constructorParametersCache[static::class])) {
$class = new ReflectionClass(static::class);
$constructor = $class->getConstructor();
static::$constructorParametersCache[static::class] = $constructor
? collect($constructor->getParameters())->map->getName()->all()
: [];
}
return static::$constructorParametersCache[static::class];
}
/**
* Resolve the Blade view or view file that should be used when rendering the component.
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\Support\Htmlable|\Closure|string
*/
public function resolveView()
{
$view = $this->render();
if ($view instanceof ViewContract) {
return $view;
}
if ($view instanceof Htmlable) {
return $view;
}
$resolver = function ($view) {
return $this->extractBladeViewFromString($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 string $contents
* @return string
*/
protected function extractBladeViewFromString($contents)
{
$key = sprintf('%s::%s', static::class, $contents);
if (isset(static::$bladeViewCache[$key])) {
return static::$bladeViewCache[$key];
}
if (strlen($contents) <= PHP_MAXPATHLEN && $this->factory()->exists($contents)) {
return static::$bladeViewCache[$key] = $contents;
}
return static::$bladeViewCache[$key] = $this->createBladeViewFromString($this->factory(), $contents);
}
/**
* 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 (! is_file($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 ?: $this->newAttributeBag();
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_starts_with($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 ?: $this->newAttributeBag();
$this->attributes->setAttributes($attributes);
return $this;
}
/**
* Get a new attribute bag instance.
*
* @param array $attributes
* @return \Illuminate\View\ComponentAttributeBag
*/
protected function newAttributeBag(array $attributes = [])
{
return new ComponentAttributeBag($attributes);
}
/**
* Determine if the component should be rendered.
*
* @return bool
*/
public function shouldRender()
{
return true;
}
/**
* Get the evaluated view contents for the given view.
*
* @param string|null $view
* @param \Illuminate\Contracts\Support\Arrayable|array $data
* @param array $mergeData
* @return \Illuminate\Contracts\View\View
*/
public function view($view, $data = [], $mergeData = [])
{
return $this->factory()->make($view, $data, $mergeData);
}
/**
* Get the view factory instance.
*
* @return \Illuminate\Contracts\View\Factory
*/
protected function factory()
{
if (is_null(static::$factory)) {
static::$factory = Container::getInstance()->make('view');
}
return static::$factory;
}
/**
* Flush the component's cached state.
*
* @return void
*/
public static function flushCache()
{
static::$bladeViewCache = [];
static::$constructorParametersCache = [];
static::$methodCache = [];
static::$propertyCache = [];
}
/**
* Forget the component's factory instance.
*
* @return void
*/
public static function forgetFactory()
{
static::$factory = null;
}
/**
* Forget the component's resolver callback.
*
* @return void
*
* @internal
*/
public static function forgetComponentsResolver()
{
static::$componentsResolver = null;
}
/**
* Set the callback that should be used to resolve components within views.
*
* @param \Closure(string $component, array $data): Component $resolver
* @return void
*
* @internal
*/
public static function resolveComponentsUsing($resolver)
{
static::$componentsResolver = $resolver;
}
}

View File

@@ -0,0 +1,450 @@
<?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\Conditionable;
use Illuminate\Support\Traits\Macroable;
use IteratorAggregate;
use Traversable;
class ComponentAttributeBag implements ArrayAccess, Htmlable, IteratorAggregate
{
use Conditionable, 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);
}
/**
* Determine if a given attribute exists in the attribute array.
*
* @param string $key
* @return bool
*/
public function has($key)
{
return array_key_exists($key, $this->attributes);
}
/**
* Determine if a given attribute is missing from the attribute array.
*
* @param string $key
* @return bool
*/
public function missing($key)
{
return ! $this->has($key, $this->attributes);
}
/**
* Only include the given attribute from the attribute array.
*
* @param mixed $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[] $needles
* @return static
*/
public function whereStartsWith($needles)
{
return $this->filter(function ($value, $key) use ($needles) {
return Str::startsWith($key, $needles);
});
}
/**
* Return a bag of attributes with keys that do not start with the given value / pattern.
*
* @param string|string[] $needles
* @return static
*/
public function whereDoesntStartWith($needles)
{
return $this->filter(function ($value, $key) use ($needles) {
return ! Str::startsWith($key, $needles);
});
}
/**
* Return a bag of attributes that have keys starting with the given value / pattern.
*
* @param string|string[] $needles
* @return static
*/
public function thatStartWith($needles)
{
return $this->whereStartsWith($needles);
}
/**
* Only include the given attribute from the attribute array.
*
* @param mixed|array $keys
* @return static
*/
public function onlyProps($keys)
{
return $this->only($this->extractPropNames($keys));
}
/**
* Exclude the given attribute from the attribute array.
*
* @param mixed|array $keys
* @return static
*/
public function exceptProps($keys)
{
return $this->except($this->extractPropNames($keys));
}
/**
* Extract prop names from given keys.
*
* @param mixed|array $keys
* @return array
*/
protected function extractPropNames($keys)
{
$props = [];
foreach ($keys as $key => $defaultValue) {
$key = is_numeric($key) ? $defaultValue : $key;
$props[] = $key;
$props[] = Str::kebab($key);
}
return $props;
}
/**
* Conditionally merge classes into the attribute bag.
*
* @param mixed|array $classList
* @return static
*/
public function class($classList)
{
$classList = Arr::wrap($classList);
return $this->merge(['class' => Arr::toCssClasses($classList)]);
}
/**
* Conditionally merge styles into the attribute bag.
*
* @param mixed|array $styleList
* @return static
*/
public function style($styleList)
{
$styleList = Arr::wrap($styleList);
return $this->merge(['style' => Arr::toCssStyles($styleList)]);
}
/**
* Merge additional attributes / values into the attribute bag.
*
* @param array $attributeDefaults
* @param bool $escape
* @return static
*/
public function merge(array $attributeDefaults = [], $escape = true)
{
$attributeDefaults = array_map(function ($value) use ($escape) {
return $this->shouldEscapeAttributeValue($escape, $value)
? e($value)
: $value;
}, $attributeDefaults);
[$appendableAttributes, $nonAppendableAttributes] = collect($this->attributes)
->partition(function ($value, $key) use ($attributeDefaults) {
return $key === 'class' || $key === 'style' || (
isset($attributeDefaults[$key]) &&
$attributeDefaults[$key] instanceof AppendableAttributeValue
);
});
$attributes = $appendableAttributes->mapWithKeys(function ($value, $key) use ($attributeDefaults, $escape) {
$defaultsValue = isset($attributeDefaults[$key]) && $attributeDefaults[$key] instanceof AppendableAttributeValue
? $this->resolveAppendableAttributeDefault($attributeDefaults, $key, $escape)
: ($attributeDefaults[$key] ?? '');
if ($key === 'style') {
$value = Str::finish($value, ';');
}
return [$key => implode(' ', array_unique(array_filter([$defaultsValue, $value])))];
})->merge($nonAppendableAttributes)->all();
return new static(array_merge($attributeDefaults, $attributes));
}
/**
* Determine if the specific attribute value should be escaped.
*
* @param bool $escape
* @param mixed $value
* @return bool
*/
protected function shouldEscapeAttributeValue($escape, $value)
{
if (! $escape) {
return false;
}
return ! is_object($value) &&
! is_null($value) &&
! is_bool($value);
}
/**
* Create a new appendable attribute value.
*
* @param mixed $value
* @return \Illuminate\View\AppendableAttributeValue
*/
public function prepends($value)
{
return new AppendableAttributeValue($value);
}
/**
* Resolve an appendable attribute value default value.
*
* @param array $attributeDefaults
* @param string $key
* @param bool $escape
* @return mixed
*/
protected function resolveAppendableAttributeDefault($attributeDefaults, $key, $escape)
{
if ($this->shouldEscapeAttributeValue($escape, $value = $attributeDefaults[$key]->value)) {
$value = e($value);
}
return $value;
}
/**
* Get all of the raw attributes.
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Set the underlying attributes.
*
* @param array $attributes
* @return void
*/
public function setAttributes(array $attributes)
{
if (isset($attributes['attributes']) &&
$attributes['attributes'] instanceof self) {
$parentBag = $attributes['attributes'];
unset($attributes['attributes']);
$attributes = $parentBag->merge($attributes, $escape = false)->getAttributes();
}
$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): bool
{
return isset($this->attributes[$offset]);
}
/**
* Get the value at the given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset): mixed
{
return $this->get($offset);
}
/**
* Set the value at a given offset.
*
* @param string $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value): void
{
$this->attributes[$offset] = $value;
}
/**
* Remove the value at the given offset.
*
* @param string $offset
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->attributes[$offset]);
}
/**
* Get an iterator for the items.
*
* @return \ArrayIterator
*/
public function getIterator(): Traversable
{
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

@@ -0,0 +1,89 @@
<?php
namespace Illuminate\View;
use Illuminate\Contracts\Support\Htmlable;
class ComponentSlot implements Htmlable
{
/**
* The slot attribute bag.
*
* @var \Illuminate\View\ComponentAttributeBag
*/
public $attributes;
/**
* The slot contents.
*
* @var string
*/
protected $contents;
/**
* Create a new slot instance.
*
* @param string $contents
* @param array $attributes
* @return void
*/
public function __construct($contents = '', $attributes = [])
{
$this->contents = $contents;
$this->withAttributes($attributes);
}
/**
* Set the extra attributes that the slot should make available.
*
* @param array $attributes
* @return $this
*/
public function withAttributes(array $attributes)
{
$this->attributes = new ComponentAttributeBag($attributes);
return $this;
}
/**
* Get the slot's HTML string.
*
* @return string
*/
public function toHtml()
{
return $this->contents;
}
/**
* Determine if the slot is empty.
*
* @return bool
*/
public function isEmpty()
{
return $this->contents === '';
}
/**
* Determine if the slot is not empty.
*
* @return bool
*/
public function isNotEmpty()
{
return ! $this->isEmpty();
}
/**
* Get the slot's HTML string.
*
* @return string
*/
public function __toString()
{
return $this->toHtml();
}
}

View File

@@ -0,0 +1,222 @@
<?php
namespace Illuminate\View\Concerns;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\View\ComponentSlot;
trait ManagesComponents
{
/**
* The components being rendered.
*
* @var array
*/
protected $componentStack = [];
/**
* The original data passed to the component.
*
* @var array
*/
protected $componentData = [];
/**
* The component data for the component that is currently being rendered.
*
* @var array
*/
protected $currentComponentData = [];
/**
* The slot contents for the component.
*
* @var array
*/
protected $slots = [];
/**
* The names of the slots being rendered.
*
* @var array
*/
protected $slotStack = [];
/**
* Start a component rendering process.
*
* @param \Illuminate\Contracts\View\View|\Illuminate\Contracts\Support\Htmlable|\Closure|string $view
* @param array $data
* @return void
*/
public function startComponent($view, array $data = [])
{
if (ob_start()) {
$this->componentStack[] = $view;
$this->componentData[$this->currentComponent()] = $data;
$this->slots[$this->currentComponent()] = [];
}
}
/**
* Get the first view that actually exists from the given list, and start a component.
*
* @param array $names
* @param array $data
* @return void
*/
public function startComponentFirst(array $names, array $data = [])
{
$name = Arr::first($names, function ($item) {
return $this->exists($item);
});
$this->startComponent($name, $data);
}
/**
* Render the current component.
*
* @return string
*/
public function renderComponent()
{
$view = array_pop($this->componentStack);
$this->currentComponentData = array_merge(
$previousComponentData = $this->currentComponentData,
$data = $this->componentData()
);
try {
$view = value($view, $data);
if ($view instanceof View) {
return $view->with($data)->render();
} elseif ($view instanceof Htmlable) {
return $view->toHtml();
} else {
return $this->make($view, $data)->render();
}
} finally {
$this->currentComponentData = $previousComponentData;
}
}
/**
* Get the data for the given component.
*
* @return array
*/
protected function componentData()
{
$defaultSlot = new HtmlString(trim(ob_get_clean()));
$slots = array_merge([
'__default' => $defaultSlot,
], $this->slots[count($this->componentStack)]);
return array_merge(
$this->componentData[count($this->componentStack)],
['slot' => $defaultSlot],
$this->slots[count($this->componentStack)],
['__laravel_slots' => $slots]
);
}
/**
* Get an item from the component data that exists above the current component.
*
* @param string $key
* @param mixed $default
* @return mixed|null
*/
public function getConsumableComponentData($key, $default = null)
{
if (array_key_exists($key, $this->currentComponentData)) {
return $this->currentComponentData[$key];
}
$currentComponent = count($this->componentStack);
if ($currentComponent === 0) {
return value($default);
}
for ($i = $currentComponent - 1; $i >= 0; $i--) {
$data = $this->componentData[$i] ?? [];
if (array_key_exists($key, $data)) {
return $data[$key];
}
}
return value($default);
}
/**
* Start the slot rendering process.
*
* @param string $name
* @param string|null $content
* @param array $attributes
* @return void
*/
public function slot($name, $content = null, $attributes = [])
{
if (func_num_args() === 2 || $content !== null) {
$this->slots[$this->currentComponent()][$name] = $content;
} elseif (ob_start()) {
$this->slots[$this->currentComponent()][$name] = '';
$this->slotStack[$this->currentComponent()][] = [$name, $attributes];
}
}
/**
* Save the slot content for rendering.
*
* @return void
*/
public function endSlot()
{
last($this->componentStack);
$currentSlot = array_pop(
$this->slotStack[$this->currentComponent()]
);
[$currentName, $currentAttributes] = $currentSlot;
$this->slots[$this->currentComponent()][$currentName] = new ComponentSlot(
trim(ob_get_clean()), $currentAttributes
);
}
/**
* Get the index for the current component.
*
* @return int
*/
protected function currentComponent()
{
return count($this->componentStack) - 1;
}
/**
* Flush all of the component state.
*
* @return void
*/
protected function flushComponents()
{
$this->componentStack = [];
$this->componentData = [];
$this->currentComponentData = [];
}
}

View File

@@ -0,0 +1,190 @@
<?php
namespace Illuminate\View\Concerns;
use Closure;
use Illuminate\Contracts\View\View as ViewContract;
use Illuminate\Support\Str;
trait ManagesEvents
{
/**
* Register a view creator event.
*
* @param array|string $views
* @param \Closure|string $callback
* @return array
*/
public function creator($views, $callback)
{
$creators = [];
foreach ((array) $views as $view) {
$creators[] = $this->addViewEvent($view, $callback, 'creating: ');
}
return $creators;
}
/**
* Register multiple view composers via an array.
*
* @param array $composers
* @return array
*/
public function composers(array $composers)
{
$registered = [];
foreach ($composers as $callback => $views) {
$registered = array_merge($registered, $this->composer($views, $callback));
}
return $registered;
}
/**
* Register a view composer event.
*
* @param array|string $views
* @param \Closure|string $callback
* @return array
*/
public function composer($views, $callback)
{
$composers = [];
foreach ((array) $views as $view) {
$composers[] = $this->addViewEvent($view, $callback);
}
return $composers;
}
/**
* Add an event for a given view.
*
* @param string $view
* @param \Closure|string $callback
* @param string $prefix
* @return \Closure|null
*/
protected function addViewEvent($view, $callback, $prefix = 'composing: ')
{
$view = $this->normalizeName($view);
if ($callback instanceof Closure) {
$this->addEventListener($prefix.$view, $callback);
return $callback;
} elseif (is_string($callback)) {
return $this->addClassEvent($view, $callback, $prefix);
}
}
/**
* Register a class based view composer.
*
* @param string $view
* @param string $class
* @param string $prefix
* @return \Closure
*/
protected function addClassEvent($view, $class, $prefix)
{
$name = $prefix.$view;
// When registering a class based view "composer", we will simply resolve the
// classes from the application IoC container then call the compose method
// on the instance. This allows for convenient, testable view composers.
$callback = $this->buildClassEventCallback(
$class, $prefix
);
$this->addEventListener($name, $callback);
return $callback;
}
/**
* Build a class based container callback Closure.
*
* @param string $class
* @param string $prefix
* @return \Closure
*/
protected function buildClassEventCallback($class, $prefix)
{
[$class, $method] = $this->parseClassEvent($class, $prefix);
// Once we have the class and method name, we can build the Closure to resolve
// the instance out of the IoC container and call the method on it with the
// given arguments that are passed to the Closure as the composer's data.
return function () use ($class, $method) {
return $this->container->make($class)->{$method}(...func_get_args());
};
}
/**
* Parse a class based composer name.
*
* @param string $class
* @param string $prefix
* @return array
*/
protected function parseClassEvent($class, $prefix)
{
return Str::parseCallback($class, $this->classEventMethodForPrefix($prefix));
}
/**
* Determine the class event method based on the given prefix.
*
* @param string $prefix
* @return string
*/
protected function classEventMethodForPrefix($prefix)
{
return str_contains($prefix, 'composing') ? 'compose' : 'create';
}
/**
* Add a listener to the event dispatcher.
*
* @param string $name
* @param \Closure $callback
* @return void
*/
protected function addEventListener($name, $callback)
{
if (str_contains($name, '*')) {
$callback = function ($name, array $data) use ($callback) {
return $callback($data[0]);
};
}
$this->events->listen($name, $callback);
}
/**
* Call the composer for a given view.
*
* @param \Illuminate\Contracts\View\View $view
* @return void
*/
public function callComposer(ViewContract $view)
{
$this->events->dispatch('composing: '.$view->name(), [$view]);
}
/**
* Call the creator for a given view.
*
* @param \Illuminate\Contracts\View\View $view
* @return void
*/
public function callCreator(ViewContract $view)
{
$this->events->dispatch('creating: '.$view->name(), [$view]);
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Illuminate\View\Concerns;
use InvalidArgumentException;
trait ManagesFragments
{
/**
* All of the captured, rendered fragments.
*
* @var array
*/
protected $fragments = [];
/**
* The stack of in-progress fragment renders.
*
* @var array
*/
protected $fragmentStack = [];
/**
* Start injecting content into a fragment.
*
* @param string $fragment
* @return void
*/
public function startFragment($fragment)
{
if (ob_start()) {
$this->fragmentStack[] = $fragment;
}
}
/**
* Stop injecting content into a fragment.
*
* @return string
*
* @throws \InvalidArgumentException
*/
public function stopFragment()
{
if (empty($this->fragmentStack)) {
throw new InvalidArgumentException('Cannot end a fragment without first starting one.');
}
$last = array_pop($this->fragmentStack);
$this->fragments[$last] = ob_get_clean();
return $this->fragments[$last];
}
/**
* Get the contents of a fragment.
*
* @param string $name
* @param string|null $default
* @return mixed
*/
public function getFragment($name, $default = null)
{
return $this->getFragments()[$name] ?? $default;
}
/**
* Get the entire array of rendered fragments.
*
* @return array
*/
public function getFragments()
{
return $this->fragments;
}
/**
* Flush all of the fragments.
*
* @return void
*/
public function flushFragments()
{
$this->fragments = [];
$this->fragmentStack = [];
}
}

View File

@@ -0,0 +1,255 @@
<?php
namespace Illuminate\View\Concerns;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use InvalidArgumentException;
trait ManagesLayouts
{
/**
* All of the finished, captured sections.
*
* @var array
*/
protected $sections = [];
/**
* The stack of in-progress sections.
*
* @var array
*/
protected $sectionStack = [];
/**
* The parent placeholder for the request.
*
* @var mixed
*/
protected static $parentPlaceholder = [];
/**
* The parent placeholder salt for the request.
*
* @var string
*/
protected static $parentPlaceholderSalt;
/**
* Start injecting content into a section.
*
* @param string $section
* @param string|null $content
* @return void
*/
public function startSection($section, $content = null)
{
if ($content === null) {
if (ob_start()) {
$this->sectionStack[] = $section;
}
} else {
$this->extendSection($section, $content instanceof View ? $content : e($content));
}
}
/**
* Inject inline content into a section.
*
* @param string $section
* @param string $content
* @return void
*/
public function inject($section, $content)
{
$this->startSection($section, $content);
}
/**
* Stop injecting content into a section and return its contents.
*
* @return string
*/
public function yieldSection()
{
if (empty($this->sectionStack)) {
return '';
}
return $this->yieldContent($this->stopSection());
}
/**
* Stop injecting content into a section.
*
* @param bool $overwrite
* @return string
*
* @throws \InvalidArgumentException
*/
public function stopSection($overwrite = false)
{
if (empty($this->sectionStack)) {
throw new InvalidArgumentException('Cannot end a section without first starting one.');
}
$last = array_pop($this->sectionStack);
if ($overwrite) {
$this->sections[$last] = ob_get_clean();
} else {
$this->extendSection($last, ob_get_clean());
}
return $last;
}
/**
* Stop injecting content into a section and append it.
*
* @return string
*
* @throws \InvalidArgumentException
*/
public function appendSection()
{
if (empty($this->sectionStack)) {
throw new InvalidArgumentException('Cannot end a section without first starting one.');
}
$last = array_pop($this->sectionStack);
if (isset($this->sections[$last])) {
$this->sections[$last] .= ob_get_clean();
} else {
$this->sections[$last] = ob_get_clean();
}
return $last;
}
/**
* Append content to a given section.
*
* @param string $section
* @param string $content
* @return void
*/
protected function extendSection($section, $content)
{
if (isset($this->sections[$section])) {
$content = str_replace(static::parentPlaceholder($section), $content, $this->sections[$section]);
}
$this->sections[$section] = $content;
}
/**
* Get the string contents of a section.
*
* @param string $section
* @param string $default
* @return string
*/
public function yieldContent($section, $default = '')
{
$sectionContent = $default instanceof View ? $default : e($default);
if (isset($this->sections[$section])) {
$sectionContent = $this->sections[$section];
}
$sectionContent = str_replace('@@parent', '--parent--holder--', $sectionContent);
return str_replace(
'--parent--holder--', '@parent', str_replace(static::parentPlaceholder($section), '', $sectionContent)
);
}
/**
* Get the parent placeholder for the current request.
*
* @param string $section
* @return string
*/
public static function parentPlaceholder($section = '')
{
if (! isset(static::$parentPlaceholder[$section])) {
$salt = static::parentPlaceholderSalt();
static::$parentPlaceholder[$section] = '##parent-placeholder-'.sha1($salt.$section).'##';
}
return static::$parentPlaceholder[$section];
}
/**
* Get the parent placeholder salt.
*
* @return string
*/
protected static function parentPlaceholderSalt()
{
if (! static::$parentPlaceholderSalt) {
return static::$parentPlaceholderSalt = Str::random(40);
}
return static::$parentPlaceholderSalt;
}
/**
* Check if section exists.
*
* @param string $name
* @return bool
*/
public function hasSection($name)
{
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.
*
* @param string $name
* @param string|null $default
* @return mixed
*/
public function getSection($name, $default = null)
{
return $this->getSections()[$name] ?? $default;
}
/**
* Get the entire array of sections.
*
* @return array
*/
public function getSections()
{
return $this->sections;
}
/**
* Flush all of the sections.
*
* @return void
*/
public function flushSections()
{
$this->sections = [];
$this->sectionStack = [];
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Illuminate\View\Concerns;
use Illuminate\Support\Arr;
use Illuminate\Support\LazyCollection;
trait ManagesLoops
{
/**
* The stack of in-progress loops.
*
* @var array
*/
protected $loopsStack = [];
/**
* Add new loop to the stack.
*
* @param \Countable|array $data
* @return void
*/
public function addLoop($data)
{
$length = is_countable($data) && ! $data instanceof LazyCollection
? count($data)
: null;
$parent = Arr::last($this->loopsStack);
$this->loopsStack[] = [
'iteration' => 0,
'index' => 0,
'remaining' => $length ?? null,
'count' => $length,
'first' => true,
'last' => isset($length) ? $length == 1 : null,
'odd' => false,
'even' => true,
'depth' => count($this->loopsStack) + 1,
'parent' => $parent ? (object) $parent : null,
];
}
/**
* Increment the top loop's indices.
*
* @return void
*/
public function incrementLoopIndices()
{
$loop = $this->loopsStack[$index = count($this->loopsStack) - 1];
$this->loopsStack[$index] = array_merge($this->loopsStack[$index], [
'iteration' => $loop['iteration'] + 1,
'index' => $loop['iteration'],
'first' => $loop['iteration'] == 0,
'odd' => ! $loop['odd'],
'even' => ! $loop['even'],
'remaining' => isset($loop['count']) ? $loop['remaining'] - 1 : null,
'last' => isset($loop['count']) ? $loop['iteration'] == $loop['count'] - 1 : null,
]);
}
/**
* Pop a loop from the top of the loop stack.
*
* @return void
*/
public function popLoop()
{
array_pop($this->loopsStack);
}
/**
* Get an instance of the last loop in the stack.
*
* @return \stdClass|null
*/
public function getLastLoop()
{
if ($last = Arr::last($this->loopsStack)) {
return (object) $last;
}
}
/**
* Get the entire loop stack.
*
* @return array
*/
public function getLoopStack()
{
return $this->loopsStack;
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Illuminate\View\Concerns;
use InvalidArgumentException;
trait ManagesStacks
{
/**
* All of the finished, captured push sections.
*
* @var array
*/
protected $pushes = [];
/**
* All of the finished, captured prepend sections.
*
* @var array
*/
protected $prepends = [];
/**
* The stack of in-progress push sections.
*
* @var array
*/
protected $pushStack = [];
/**
* Start injecting content into a push section.
*
* @param string $section
* @param string $content
* @return void
*/
public function startPush($section, $content = '')
{
if ($content === '') {
if (ob_start()) {
$this->pushStack[] = $section;
}
} else {
$this->extendPush($section, $content);
}
}
/**
* Stop injecting content into a push section.
*
* @return string
*
* @throws \InvalidArgumentException
*/
public function stopPush()
{
if (empty($this->pushStack)) {
throw new InvalidArgumentException('Cannot end a push stack without first starting one.');
}
return tap(array_pop($this->pushStack), function ($last) {
$this->extendPush($last, ob_get_clean());
});
}
/**
* Append content to a given push section.
*
* @param string $section
* @param string $content
* @return void
*/
protected function extendPush($section, $content)
{
if (! isset($this->pushes[$section])) {
$this->pushes[$section] = [];
}
if (! isset($this->pushes[$section][$this->renderCount])) {
$this->pushes[$section][$this->renderCount] = $content;
} else {
$this->pushes[$section][$this->renderCount] .= $content;
}
}
/**
* Start prepending content into a push section.
*
* @param string $section
* @param string $content
* @return void
*/
public function startPrepend($section, $content = '')
{
if ($content === '') {
if (ob_start()) {
$this->pushStack[] = $section;
}
} else {
$this->extendPrepend($section, $content);
}
}
/**
* Stop prepending content into a push section.
*
* @return string
*
* @throws \InvalidArgumentException
*/
public function stopPrepend()
{
if (empty($this->pushStack)) {
throw new InvalidArgumentException('Cannot end a prepend operation without first starting one.');
}
return tap(array_pop($this->pushStack), function ($last) {
$this->extendPrepend($last, ob_get_clean());
});
}
/**
* Prepend content to a given stack.
*
* @param string $section
* @param string $content
* @return void
*/
protected function extendPrepend($section, $content)
{
if (! isset($this->prepends[$section])) {
$this->prepends[$section] = [];
}
if (! isset($this->prepends[$section][$this->renderCount])) {
$this->prepends[$section][$this->renderCount] = $content;
} else {
$this->prepends[$section][$this->renderCount] = $content.$this->prepends[$section][$this->renderCount];
}
}
/**
* Get the string contents of a push section.
*
* @param string $section
* @param string $default
* @return string
*/
public function yieldPushContent($section, $default = '')
{
if (! isset($this->pushes[$section]) && ! isset($this->prepends[$section])) {
return $default;
}
$output = '';
if (isset($this->prepends[$section])) {
$output .= implode(array_reverse($this->prepends[$section]));
}
if (isset($this->pushes[$section])) {
$output .= implode($this->pushes[$section]);
}
return $output;
}
/**
* Flush all of the stacks.
*
* @return void
*/
public function flushStacks()
{
$this->pushes = [];
$this->prepends = [];
$this->pushStack = [];
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Illuminate\View\Concerns;
trait ManagesTranslations
{
/**
* The translation replacements for the translation being rendered.
*
* @var array
*/
protected $translationReplacements = [];
/**
* Start a translation block.
*
* @param array $replacements
* @return void
*/
public function startTranslation($replacements = [])
{
ob_start();
$this->translationReplacements = $replacements;
}
/**
* Render the current translation.
*
* @return string
*/
public function renderTranslation()
{
return $this->container->make('translator')->get(
trim(ob_get_clean()), $this->translationReplacements
);
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace Illuminate\View;
use Illuminate\Container\Container;
use Illuminate\Support\Str;
use Illuminate\View\Compilers\ComponentTagCompiler;
class DynamicComponent extends Component
{
/**
* The name of the component.
*
* @var string
*/
public $component;
/**
* The component tag compiler instance.
*
* @var \Illuminate\View\Compilers\BladeTagCompiler
*/
protected static $compiler;
/**
* The cached component classes.
*
* @var array
*/
protected static $componentClasses = [];
/**
* Create a new component instance.
*
* @param string $component
* @return void
*/
public function __construct(string $component)
{
$this->component = $component;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|string
*/
public function render()
{
$template = <<<'EOF'
<?php extract(collect($attributes->getAttributes())->mapWithKeys(function ($value, $key) { return [Illuminate\Support\Str::camel(str_replace([':', '.'], ' ', $key)) => $value]; })->all(), EXTR_SKIP); ?>
{{ props }}
<x-{{ component }} {{ bindings }} {{ attributes }}>
{{ slots }}
{{ defaultSlot }}
</x-{{ component }}>
EOF;
return function ($data) use ($template) {
$bindings = $this->bindings($class = $this->classForComponent());
return str_replace(
[
'{{ component }}',
'{{ props }}',
'{{ bindings }}',
'{{ attributes }}',
'{{ slots }}',
'{{ defaultSlot }}',
],
[
$this->component,
$this->compileProps($bindings),
$this->compileBindings($bindings),
class_exists($class) ? '{{ $attributes }}' : '',
$this->compileSlots($data['__laravel_slots']),
'{{ $slot ?? "" }}',
],
$template
);
};
}
/**
* Compile the @props directive for the component.
*
* @param array $bindings
* @return string
*/
protected function compileProps(array $bindings)
{
if (empty($bindings)) {
return '';
}
return '@props('.'[\''.implode('\',\'', collect($bindings)->map(function ($dataKey) {
return Str::camel($dataKey);
})->all()).'\']'.')';
}
/**
* Compile the bindings for the component.
*
* @param array $bindings
* @return string
*/
protected function compileBindings(array $bindings)
{
return collect($bindings)->map(function ($key) {
return ':'.$key.'="$'.Str::camel(str_replace([':', '.'], ' ', $key)).'"';
})->implode(' ');
}
/**
* Compile the slots for the component.
*
* @param array $slots
* @return string
*/
protected function compileSlots(array $slots)
{
return collect($slots)->map(function ($slot, $name) {
return $name === '__default' ? null : '<x-slot name="'.$name.'" '.((string) $slot->attributes).'>{{ $'.$name.' }}</x-slot>';
})->filter()->implode(PHP_EOL);
}
/**
* Get the class for the current component.
*
* @return string
*/
protected function classForComponent()
{
if (isset(static::$componentClasses[$this->component])) {
return static::$componentClasses[$this->component];
}
return static::$componentClasses[$this->component] =
$this->compiler()->componentClass($this->component);
}
/**
* Get the names of the variables that should be bound to the component.
*
* @param string $class
* @return array
*/
protected function bindings(string $class)
{
[$data, $attributes] = $this->compiler()->partitionDataAndAttributes($class, $this->attributes->getAttributes());
return array_keys($data->all());
}
/**
* Get an instance of the Blade tag compiler.
*
* @return \Illuminate\View\Compilers\ComponentTagCompiler
*/
protected function compiler()
{
if (! static::$compiler) {
static::$compiler = new ComponentTagCompiler(
Container::getInstance()->make('blade.compiler')->getClassComponentAliases(),
Container::getInstance()->make('blade.compiler')->getClassComponentNamespaces(),
Container::getInstance()->make('blade.compiler')
);
}
return static::$compiler;
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace Illuminate\View\Engines;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\CompilerInterface;
use Illuminate\View\ViewException;
use Throwable;
class CompilerEngine extends PhpEngine
{
/**
* The Blade compiler instance.
*
* @var \Illuminate\View\Compilers\CompilerInterface
*/
protected $compiler;
/**
* A stack of the last compiled templates.
*
* @var array
*/
protected $lastCompiled = [];
/**
* The view paths that were compiled or are not expired, keyed by the path.
*
* @var array<string, true>
*/
protected $compiledOrNotExpired = [];
/**
* Create a new compiler engine instance.
*
* @param \Illuminate\View\Compilers\CompilerInterface $compiler
* @param \Illuminate\Filesystem\Filesystem|null $files
* @return void
*/
public function __construct(CompilerInterface $compiler, Filesystem $files = null)
{
parent::__construct($files ?: new Filesystem);
$this->compiler = $compiler;
}
/**
* Get the evaluated contents of the view.
*
* @param string $path
* @param array $data
* @return string
*/
public function get($path, array $data = [])
{
$this->lastCompiled[] = $path;
// If this given view has expired, which means it has simply been edited since
// it was last compiled, we will re-compile the views so we can evaluate a
// fresh copy of the view. We'll pass the compiler the path of the view.
if (! isset($this->compiledOrNotExpired[$path]) && $this->compiler->isExpired($path)) {
$this->compiler->compile($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.
try {
$results = $this->evaluatePath($this->compiler->getCompiledPath($path), $data);
} catch (ViewException $e) {
if (! str($e->getMessage())->contains(['No such file or directory', 'File does not exist at path'])) {
throw $e;
}
if (! isset($this->compiledOrNotExpired[$path])) {
throw $e;
}
$this->compiler->compile($path);
$results = $this->evaluatePath($this->compiler->getCompiledPath($path), $data);
}
$this->compiledOrNotExpired[$path] = true;
array_pop($this->lastCompiled);
return $results;
}
/**
* Handle a view exception.
*
* @param \Throwable $e
* @param int $obLevel
* @return void
*
* @throws \Throwable
*/
protected function handleViewException(Throwable $e, $obLevel)
{
$e = new ViewException($this->getMessage($e), 0, 1, $e->getFile(), $e->getLine(), $e);
parent::handleViewException($e, $obLevel);
}
/**
* Get the exception message for an exception.
*
* @param \Throwable $e
* @return string
*/
protected function getMessage(Throwable $e)
{
return $e->getMessage().' (View: '.realpath(last($this->lastCompiled)).')';
}
/**
* Get the compiler implementation.
*
* @return \Illuminate\View\Compilers\CompilerInterface
*/
public function getCompiler()
{
return $this->compiler;
}
/**
* Clear the cache of views that were compiled or not expired.
*
* @return void
*/
public function forgetCompiledOrNotExpired()
{
$this->compiledOrNotExpired = [];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Illuminate\View\Engines;
abstract class Engine
{
/**
* The view that was last to be rendered.
*
* @var string
*/
protected $lastRendered;
/**
* Get the last view that was rendered.
*
* @return string
*/
public function getLastRendered()
{
return $this->lastRendered;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Illuminate\View\Engines;
use Closure;
use InvalidArgumentException;
class EngineResolver
{
/**
* The array of engine resolvers.
*
* @var array
*/
protected $resolvers = [];
/**
* The resolved engine instances.
*
* @var array
*/
protected $resolved = [];
/**
* Register a new engine resolver.
*
* The engine string typically corresponds to a file extension.
*
* @param string $engine
* @param \Closure $resolver
* @return void
*/
public function register($engine, Closure $resolver)
{
$this->forget($engine);
$this->resolvers[$engine] = $resolver;
}
/**
* Resolve an engine instance by name.
*
* @param string $engine
* @return \Illuminate\Contracts\View\Engine
*
* @throws \InvalidArgumentException
*/
public function resolve($engine)
{
if (isset($this->resolved[$engine])) {
return $this->resolved[$engine];
}
if (isset($this->resolvers[$engine])) {
return $this->resolved[$engine] = call_user_func($this->resolvers[$engine]);
}
throw new InvalidArgumentException("Engine [{$engine}] not found.");
}
/**
* Remove a resolved engine.
*
* @param string $engine
* @return void
*/
public function forget($engine)
{
unset($this->resolved[$engine]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Illuminate\View\Engines;
use Illuminate\Contracts\View\Engine;
use Illuminate\Filesystem\Filesystem;
class FileEngine implements Engine
{
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* Create a new file engine instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @return void
*/
public function __construct(Filesystem $files)
{
$this->files = $files;
}
/**
* Get the evaluated contents of the view.
*
* @param string $path
* @param array $data
* @return string
*/
public function get($path, array $data = [])
{
return $this->files->get($path);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Illuminate\View\Engines;
use Illuminate\Contracts\View\Engine;
use Illuminate\Filesystem\Filesystem;
use Throwable;
class PhpEngine implements Engine
{
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* Create a new file engine instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @return void
*/
public function __construct(Filesystem $files)
{
$this->files = $files;
}
/**
* Get the evaluated contents of the view.
*
* @param string $path
* @param array $data
* @return string
*/
public function get($path, array $data = [])
{
return $this->evaluatePath($path, $data);
}
/**
* Get the evaluated contents of the view at the given path.
*
* @param string $path
* @param array $data
* @return string
*/
protected function evaluatePath($path, $data)
{
$obLevel = ob_get_level();
ob_start();
// We'll evaluate the contents of the view inside a try/catch block so we can
// flush out any stray output that might get out before an error occurs or
// an exception is thrown. This prevents any partial views from leaking.
try {
$this->files->getRequire($path, $data);
} catch (Throwable $e) {
$this->handleViewException($e, $obLevel);
}
return ltrim(ob_get_clean());
}
/**
* Handle a view exception.
*
* @param \Throwable $e
* @param int $obLevel
* @return void
*
* @throws \Throwable
*/
protected function handleViewException(Throwable $e, $obLevel)
{
while (ob_get_level() > $obLevel) {
ob_end_clean();
}
throw $e;
}
}

View File

@@ -0,0 +1,614 @@
<?php
namespace Illuminate\View;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\View\Factory as FactoryContract;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\Macroable;
use Illuminate\View\Engines\EngineResolver;
use InvalidArgumentException;
class Factory implements FactoryContract
{
use Macroable,
Concerns\ManagesComponents,
Concerns\ManagesEvents,
Concerns\ManagesFragments,
Concerns\ManagesLayouts,
Concerns\ManagesLoops,
Concerns\ManagesStacks,
Concerns\ManagesTranslations;
/**
* The engine implementation.
*
* @var \Illuminate\View\Engines\EngineResolver
*/
protected $engines;
/**
* The view finder implementation.
*
* @var \Illuminate\View\ViewFinderInterface
*/
protected $finder;
/**
* The event dispatcher instance.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $events;
/**
* The IoC container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* Data that should be available to all templates.
*
* @var array
*/
protected $shared = [];
/**
* The extension to engine bindings.
*
* @var array
*/
protected $extensions = [
'blade.php' => 'blade',
'php' => 'php',
'css' => 'file',
'html' => 'file',
];
/**
* The view composer events.
*
* @var array
*/
protected $composers = [];
/**
* The number of active rendering operations.
*
* @var int
*/
protected $renderCount = 0;
/**
* The "once" block IDs that have been rendered.
*
* @var array
*/
protected $renderedOnce = [];
/**
* Create a new view factory instance.
*
* @param \Illuminate\View\Engines\EngineResolver $engines
* @param \Illuminate\View\ViewFinderInterface $finder
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events)
{
$this->finder = $finder;
$this->events = $events;
$this->engines = $engines;
$this->share('__env', $this);
}
/**
* Get the evaluated view contents for the given view.
*
* @param string $path
* @param \Illuminate\Contracts\Support\Arrayable|array $data
* @param array $mergeData
* @return \Illuminate\Contracts\View\View
*/
public function file($path, $data = [], $mergeData = [])
{
$data = array_merge($mergeData, $this->parseData($data));
return tap($this->viewInstance($path, $path, $data), function ($view) {
$this->callCreator($view);
});
}
/**
* Get the evaluated view contents for the given view.
*
* @param string $view
* @param \Illuminate\Contracts\Support\Arrayable|array $data
* @param array $mergeData
* @return \Illuminate\Contracts\View\View
*/
public function make($view, $data = [], $mergeData = [])
{
$path = $this->finder->find(
$view = $this->normalizeName($view)
);
// Next, we will create the view instance and call the view creator for the view
// which can set any data, etc. Then we will return the view instance back to
// the caller for rendering or performing other view manipulations on this.
$data = array_merge($mergeData, $this->parseData($data));
return tap($this->viewInstance($view, $path, $data), function ($view) {
$this->callCreator($view);
});
}
/**
* Get the first view that actually exists from the given list.
*
* @param array $views
* @param \Illuminate\Contracts\Support\Arrayable|array $data
* @param array $mergeData
* @return \Illuminate\Contracts\View\View
*
* @throws \InvalidArgumentException
*/
public function first(array $views, $data = [], $mergeData = [])
{
$view = Arr::first($views, function ($view) {
return $this->exists($view);
});
if (! $view) {
throw new InvalidArgumentException('None of the views in the given array exist.');
}
return $this->make($view, $data, $mergeData);
}
/**
* Get the rendered content of the view based on a given condition.
*
* @param bool $condition
* @param string $view
* @param \Illuminate\Contracts\Support\Arrayable|array $data
* @param array $mergeData
* @return string
*/
public function renderWhen($condition, $view, $data = [], $mergeData = [])
{
if (! $condition) {
return '';
}
return $this->make($view, $this->parseData($data), $mergeData)->render();
}
/**
* Get the rendered content of the view based on the negation of a given condition.
*
* @param bool $condition
* @param string $view
* @param \Illuminate\Contracts\Support\Arrayable|array $data
* @param array $mergeData
* @return string
*/
public function renderUnless($condition, $view, $data = [], $mergeData = [])
{
return $this->renderWhen(! $condition, $view, $data, $mergeData);
}
/**
* Get the rendered contents of a partial from a loop.
*
* @param string $view
* @param array $data
* @param string $iterator
* @param string $empty
* @return string
*/
public function renderEach($view, $data, $iterator, $empty = 'raw|')
{
$result = '';
// If is actually data in the array, we will loop through the data and append
// an instance of the partial view to the final result HTML passing in the
// iterated value of this data array, allowing the views to access them.
if (count($data) > 0) {
foreach ($data as $key => $value) {
$result .= $this->make(
$view, ['key' => $key, $iterator => $value]
)->render();
}
}
// If there is no data in the array, we will render the contents of the empty
// view. Alternatively, the "empty view" could be a raw string that begins
// with "raw|" for convenience and to let this know that it is a string.
else {
$result = str_starts_with($empty, 'raw|')
? substr($empty, 4)
: $this->make($empty)->render();
}
return $result;
}
/**
* Normalize a view name.
*
* @param string $name
* @return string
*/
protected function normalizeName($name)
{
return ViewName::normalize($name);
}
/**
* Parse the given data into a raw array.
*
* @param mixed $data
* @return array
*/
protected function parseData($data)
{
return $data instanceof Arrayable ? $data->toArray() : $data;
}
/**
* Create a new view instance from the given arguments.
*
* @param string $view
* @param string $path
* @param \Illuminate\Contracts\Support\Arrayable|array $data
* @return \Illuminate\Contracts\View\View
*/
protected function viewInstance($view, $path, $data)
{
return new View($this, $this->getEngineFromPath($path), $view, $path, $data);
}
/**
* Determine if a given view exists.
*
* @param string $view
* @return bool
*/
public function exists($view)
{
try {
$this->finder->find($view);
} catch (InvalidArgumentException $e) {
return false;
}
return true;
}
/**
* Get the appropriate view engine for the given path.
*
* @param string $path
* @return \Illuminate\Contracts\View\Engine
*
* @throws \InvalidArgumentException
*/
public function getEngineFromPath($path)
{
if (! $extension = $this->getExtension($path)) {
throw new InvalidArgumentException("Unrecognized extension in file: {$path}.");
}
$engine = $this->extensions[$extension];
return $this->engines->resolve($engine);
}
/**
* Get the extension used by the view file.
*
* @param string $path
* @return string|null
*/
protected function getExtension($path)
{
$extensions = array_keys($this->extensions);
return Arr::first($extensions, function ($value) use ($path) {
return str_ends_with($path, '.'.$value);
});
}
/**
* Add a piece of shared data to the environment.
*
* @param array|string $key
* @param mixed|null $value
* @return mixed
*/
public function share($key, $value = null)
{
$keys = is_array($key) ? $key : [$key => $value];
foreach ($keys as $key => $value) {
$this->shared[$key] = $value;
}
return $value;
}
/**
* Increment the rendering counter.
*
* @return void
*/
public function incrementRender()
{
$this->renderCount++;
}
/**
* Decrement the rendering counter.
*
* @return void
*/
public function decrementRender()
{
$this->renderCount--;
}
/**
* Check if there are no active render operations.
*
* @return bool
*/
public function doneRendering()
{
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.
*
* @param string $location
* @return void
*/
public function addLocation($location)
{
$this->finder->addLocation($location);
}
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string|array $hints
* @return $this
*/
public function addNamespace($namespace, $hints)
{
$this->finder->addNamespace($namespace, $hints);
return $this;
}
/**
* Prepend a new namespace to the loader.
*
* @param string $namespace
* @param string|array $hints
* @return $this
*/
public function prependNamespace($namespace, $hints)
{
$this->finder->prependNamespace($namespace, $hints);
return $this;
}
/**
* Replace the namespace hints for the given namespace.
*
* @param string $namespace
* @param string|array $hints
* @return $this
*/
public function replaceNamespace($namespace, $hints)
{
$this->finder->replaceNamespace($namespace, $hints);
return $this;
}
/**
* Register a valid view extension and its engine.
*
* @param string $extension
* @param string $engine
* @param \Closure|null $resolver
* @return void
*/
public function addExtension($extension, $engine, $resolver = null)
{
$this->finder->addExtension($extension);
if (isset($resolver)) {
$this->engines->register($engine, $resolver);
}
unset($this->extensions[$extension]);
$this->extensions = array_merge([$extension => $engine], $this->extensions);
}
/**
* Flush all of the factory state like sections and stacks.
*
* @return void
*/
public function flushState()
{
$this->renderCount = 0;
$this->renderedOnce = [];
$this->flushSections();
$this->flushStacks();
$this->flushComponents();
$this->flushFragments();
}
/**
* Flush all of the section contents if done rendering.
*
* @return void
*/
public function flushStateIfDoneRendering()
{
if ($this->doneRendering()) {
$this->flushState();
}
}
/**
* Get the extension to engine bindings.
*
* @return array
*/
public function getExtensions()
{
return $this->extensions;
}
/**
* Get the engine resolver instance.
*
* @return \Illuminate\View\Engines\EngineResolver
*/
public function getEngineResolver()
{
return $this->engines;
}
/**
* Get the view finder instance.
*
* @return \Illuminate\View\ViewFinderInterface
*/
public function getFinder()
{
return $this->finder;
}
/**
* Set the view finder instance.
*
* @param \Illuminate\View\ViewFinderInterface $finder
* @return void
*/
public function setFinder(ViewFinderInterface $finder)
{
$this->finder = $finder;
}
/**
* Flush the cache of views located by the finder.
*
* @return void
*/
public function flushFinderCache()
{
$this->getFinder()->flush();
}
/**
* Get the event dispatcher instance.
*
* @return \Illuminate\Contracts\Events\Dispatcher
*/
public function getDispatcher()
{
return $this->events;
}
/**
* Set the event dispatcher instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function setDispatcher(Dispatcher $events)
{
$this->events = $events;
}
/**
* Get the IoC container instance.
*
* @return \Illuminate\Contracts\Container\Container
*/
public function getContainer()
{
return $this->container;
}
/**
* Set the IoC container instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @return void
*/
public function setContainer(Container $container)
{
$this->container = $container;
}
/**
* Get an item from the shared data.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function shared($key, $default = null)
{
return Arr::get($this->shared, $key, $default);
}
/**
* Get all of the shared data for the environment.
*
* @return array
*/
public function getShared()
{
return $this->shared;
}
}

View File

@@ -0,0 +1,330 @@
<?php
namespace Illuminate\View;
use Illuminate\Filesystem\Filesystem;
use InvalidArgumentException;
class FileViewFinder implements ViewFinderInterface
{
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The array of active view paths.
*
* @var array
*/
protected $paths;
/**
* The array of views that have been located.
*
* @var array
*/
protected $views = [];
/**
* The namespace to file path hints.
*
* @var array
*/
protected $hints = [];
/**
* Register a view extension with the finder.
*
* @var string[]
*/
protected $extensions = ['blade.php', 'php', 'css', 'html'];
/**
* Create a new file view loader instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param array $paths
* @param array|null $extensions
* @return void
*/
public function __construct(Filesystem $files, array $paths, array $extensions = null)
{
$this->files = $files;
$this->paths = array_map([$this, 'resolvePath'], $paths);
if (isset($extensions)) {
$this->extensions = $extensions;
}
}
/**
* Get the fully qualified location of the view.
*
* @param string $name
* @return string
*/
public function find($name)
{
if (isset($this->views[$name])) {
return $this->views[$name];
}
if ($this->hasHintInformation($name = trim($name))) {
return $this->views[$name] = $this->findNamespacedView($name);
}
return $this->views[$name] = $this->findInPaths($name, $this->paths);
}
/**
* Get the path to a template with a named path.
*
* @param string $name
* @return string
*/
protected function findNamespacedView($name)
{
[$namespace, $view] = $this->parseNamespaceSegments($name);
return $this->findInPaths($view, $this->hints[$namespace]);
}
/**
* Get the segments of a template with a named path.
*
* @param string $name
* @return array
*
* @throws \InvalidArgumentException
*/
protected function parseNamespaceSegments($name)
{
$segments = explode(static::HINT_PATH_DELIMITER, $name);
if (count($segments) !== 2) {
throw new InvalidArgumentException("View [{$name}] has an invalid name.");
}
if (! isset($this->hints[$segments[0]])) {
throw new InvalidArgumentException("No hint path defined for [{$segments[0]}].");
}
return $segments;
}
/**
* Find the given view in the list of paths.
*
* @param string $name
* @param array $paths
* @return string
*
* @throws \InvalidArgumentException
*/
protected function findInPaths($name, $paths)
{
foreach ((array) $paths as $path) {
foreach ($this->getPossibleViewFiles($name) as $file) {
if ($this->files->exists($viewPath = $path.'/'.$file)) {
return $viewPath;
}
}
}
throw new InvalidArgumentException("View [{$name}] not found.");
}
/**
* Get an array of possible view files.
*
* @param string $name
* @return array
*/
protected function getPossibleViewFiles($name)
{
return array_map(fn ($extension) => str_replace('.', '/', $name).'.'.$extension, $this->extensions);
}
/**
* Add a location to the finder.
*
* @param string $location
* @return void
*/
public function addLocation($location)
{
$this->paths[] = $this->resolvePath($location);
}
/**
* Prepend a location to the finder.
*
* @param string $location
* @return void
*/
public function prependLocation($location)
{
array_unshift($this->paths, $this->resolvePath($location));
}
/**
* Resolve the path.
*
* @param string $path
* @return string
*/
protected function resolvePath($path)
{
return realpath($path) ?: $path;
}
/**
* Add a namespace hint to the finder.
*
* @param string $namespace
* @param string|array $hints
* @return void
*/
public function addNamespace($namespace, $hints)
{
$hints = (array) $hints;
if (isset($this->hints[$namespace])) {
$hints = array_merge($this->hints[$namespace], $hints);
}
$this->hints[$namespace] = $hints;
}
/**
* Prepend a namespace hint to the finder.
*
* @param string $namespace
* @param string|array $hints
* @return void
*/
public function prependNamespace($namespace, $hints)
{
$hints = (array) $hints;
if (isset($this->hints[$namespace])) {
$hints = array_merge($hints, $this->hints[$namespace]);
}
$this->hints[$namespace] = $hints;
}
/**
* Replace the namespace hints for the given namespace.
*
* @param string $namespace
* @param string|array $hints
* @return void
*/
public function replaceNamespace($namespace, $hints)
{
$this->hints[$namespace] = (array) $hints;
}
/**
* Register an extension with the view finder.
*
* @param string $extension
* @return void
*/
public function addExtension($extension)
{
if (($index = array_search($extension, $this->extensions)) !== false) {
unset($this->extensions[$index]);
}
array_unshift($this->extensions, $extension);
}
/**
* Returns whether or not the view name has any hint information.
*
* @param string $name
* @return bool
*/
public function hasHintInformation($name)
{
return strpos($name, static::HINT_PATH_DELIMITER) > 0;
}
/**
* Flush the cache of located views.
*
* @return void
*/
public function flush()
{
$this->views = [];
}
/**
* Get the filesystem instance.
*
* @return \Illuminate\Filesystem\Filesystem
*/
public function getFilesystem()
{
return $this->files;
}
/**
* Set the active view paths.
*
* @param array $paths
* @return $this
*/
public function setPaths($paths)
{
$this->paths = $paths;
return $this;
}
/**
* Get the active view paths.
*
* @return array
*/
public function getPaths()
{
return $this->paths;
}
/**
* Get the views that have been located.
*
* @return array
*/
public function getViews()
{
return $this->views;
}
/**
* Get the namespace to file path hints.
*
* @return array
*/
public function getHints()
{
return $this->hints;
}
/**
* Get registered extensions.
*
* @return array
*/
public function getExtensions()
{
return $this->extensions;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Illuminate\View;
use ArrayIterator;
use Closure;
use Illuminate\Contracts\Support\DeferringDisplayableValue;
use Illuminate\Support\Enumerable;
use IteratorAggregate;
use Traversable;
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(): Traversable
{
$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

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,51 @@
<?php
namespace Illuminate\View\Middleware;
use Closure;
use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\Support\ViewErrorBag;
class ShareErrorsFromSession
{
/**
* The view factory implementation.
*
* @var \Illuminate\Contracts\View\Factory
*/
protected $view;
/**
* Create a new error binder instance.
*
* @param \Illuminate\Contracts\View\Factory $view
* @return void
*/
public function __construct(ViewFactory $view)
{
$this->view = $view;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// If the current session has an "errors" variable bound to it, we will share
// its value with all view instances so the views can easily access errors
// without having to bind. An empty bag is set when there aren't errors.
$this->view->share(
'errors', $request->session()->get('errors') ?: new ViewErrorBag
);
// Putting the errors in the view for every view allows the developer to just
// assume that some errors are always available, which is convenient since
// they don't have to continually run checks for the presence of errors.
return $next($request);
}
}

View File

@@ -0,0 +1,494 @@
<?php
namespace Illuminate\View;
use ArrayAccess;
use BadMethodCallException;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\Support\MessageProvider;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Contracts\View\Engine;
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
{
use Macroable {
__call as macroCall;
}
/**
* The view factory instance.
*
* @var \Illuminate\View\Factory
*/
protected $factory;
/**
* The engine implementation.
*
* @var \Illuminate\Contracts\View\Engine
*/
protected $engine;
/**
* The name of the view.
*
* @var string
*/
protected $view;
/**
* The array of view data.
*
* @var array
*/
protected $data;
/**
* The path to the view file.
*
* @var string
*/
protected $path;
/**
* Create a new view instance.
*
* @param \Illuminate\View\Factory $factory
* @param \Illuminate\Contracts\View\Engine $engine
* @param string $view
* @param string $path
* @param mixed $data
* @return void
*/
public function __construct(Factory $factory, Engine $engine, $view, $path, $data = [])
{
$this->view = $view;
$this->path = $path;
$this->engine = $engine;
$this->factory = $factory;
$this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data;
}
/**
* Get the evaluated contents of a given fragment.
*
* @param string $fragment
* @return string
*/
public function fragment($fragment)
{
return $this->render(function () use ($fragment) {
return $this->factory->getFragment($fragment);
});
}
/**
* Get the evaluated contents for a given array of fragments.
*
* @param array $fragments
* @return string
*/
public function fragments(array $fragments)
{
return collect($fragments)->map(fn ($f) => $this->fragment($f))->implode('');
}
/**
* Get the evaluated contents of a given fragment if the given condition is true.
*
* @param bool $boolean
* @param string $fragment
* @return string
*/
public function fragmentIf($boolean, $fragment)
{
if (value($boolean)) {
return $this->fragment($fragment);
}
return $this->render();
}
/**
* Get the evaluated contents for a given array of fragments if the given condition is true.
*
* @param bool $boolean
* @param array $fragments
* @return string
*/
public function fragmentsIf($boolean, array $fragments)
{
if (value($boolean)) {
return $this->fragments($fragments);
}
return $this->render();
}
/**
* Get the string contents of the view.
*
* @param callable|null $callback
* @return string
*
* @throws \Throwable
*/
public function render(callable $callback = null)
{
try {
$contents = $this->renderContents();
$response = isset($callback) ? $callback($this, $contents) : null;
// Once we have the contents of the view, we will flush the sections if we are
// done rendering all views so that there is nothing left hanging over when
// another view gets rendered in the future by the application developer.
$this->factory->flushStateIfDoneRendering();
return ! is_null($response) ? $response : $contents;
} catch (Throwable $e) {
$this->factory->flushState();
throw $e;
}
}
/**
* Get the contents of the view instance.
*
* @return string
*/
protected function renderContents()
{
// We will keep track of the number of views being rendered so we can flush
// the section after the complete rendering operation is done. This will
// clear out the sections for any separate views that may be rendered.
$this->factory->incrementRender();
$this->factory->callComposer($this);
$contents = $this->getContents();
// Once we've finished rendering the view, we'll decrement the render count
// so that each section gets flushed out next time a view is created and
// no old sections are staying around in the memory of an environment.
$this->factory->decrementRender();
return $contents;
}
/**
* Get the evaluated contents of the view.
*
* @return string
*/
protected function getContents()
{
return $this->engine->get($this->path, $this->gatherData());
}
/**
* Get the data bound to the view instance.
*
* @return array
*/
public function gatherData()
{
$data = array_merge($this->factory->getShared(), $this->data);
foreach ($data as $key => $value) {
if ($value instanceof Renderable) {
$data[$key] = $value->render();
}
}
return $data;
}
/**
* Get the sections of the rendered view.
*
* @return array
*
* @throws \Throwable
*/
public function renderSections()
{
return $this->render(function () {
return $this->factory->getSections();
});
}
/**
* Add a piece of data to the view.
*
* @param string|array $key
* @param mixed $value
* @return $this
*/
public function with($key, $value = null)
{
if (is_array($key)) {
$this->data = array_merge($this->data, $key);
} else {
$this->data[$key] = $value;
}
return $this;
}
/**
* Add a view instance to the view data.
*
* @param string $key
* @param string $view
* @param array $data
* @return $this
*/
public function nest($key, $view, array $data = [])
{
return $this->with($key, $this->factory->make($view, $data));
}
/**
* Add validation errors to the view.
*
* @param \Illuminate\Contracts\Support\MessageProvider|array $provider
* @param string $bag
* @return $this
*/
public function withErrors($provider, $bag = 'default')
{
return $this->with('errors', (new ViewErrorBag)->put(
$bag, $this->formatErrors($provider)
));
}
/**
* Parse the given errors into an appropriate value.
*
* @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);
}
/**
* Get the name of the view.
*
* @return string
*/
public function name()
{
return $this->getName();
}
/**
* Get the name of the view.
*
* @return string
*/
public function getName()
{
return $this->view;
}
/**
* Get the array of view data.
*
* @return array
*/
public function getData()
{
return $this->data;
}
/**
* Get the path to the view file.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Set the path to the view.
*
* @param string $path
* @return void
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Get the view factory instance.
*
* @return \Illuminate\View\Factory
*/
public function getFactory()
{
return $this->factory;
}
/**
* Get the view's rendering engine.
*
* @return \Illuminate\Contracts\View\Engine
*/
public function getEngine()
{
return $this->engine;
}
/**
* Determine if a piece of data is bound.
*
* @param string $key
* @return bool
*/
public function offsetExists($key): bool
{
return array_key_exists($key, $this->data);
}
/**
* Get a piece of bound data to the view.
*
* @param string $key
* @return mixed
*/
public function offsetGet($key): mixed
{
return $this->data[$key];
}
/**
* Set a piece of data on the view.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function offsetSet($key, $value): void
{
$this->with($key, $value);
}
/**
* Unset a piece of data from the view.
*
* @param string $key
* @return void
*/
public function offsetUnset($key): void
{
unset($this->data[$key]);
}
/**
* Get a piece of data from the view.
*
* @param string $key
* @return mixed
*/
public function &__get($key)
{
return $this->data[$key];
}
/**
* Set a piece of data on the view.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function __set($key, $value)
{
$this->with($key, $value);
}
/**
* Check if a piece of data is bound to the view.
*
* @param string $key
* @return bool
*/
public function __isset($key)
{
return isset($this->data[$key]);
}
/**
* Remove a piece of bound data from the view.
*
* @param string $key
* @return void
*/
public function __unset($key)
{
unset($this->data[$key]);
}
/**
* Dynamically bind parameters to the view.
*
* @param string $method
* @param array $parameters
* @return \Illuminate\View\View
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if (! str_starts_with($method, 'with')) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}
return $this->with(Str::camel(substr($method, 4)), $parameters[0]);
}
/**
* Get content as a string of HTML.
*
* @return string
*/
public function toHtml()
{
return $this->render();
}
/**
* Get the string contents of the view.
*
* @return string
*
* @throws \Throwable
*/
public function __toString()
{
return $this->render();
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Illuminate\View;
use ErrorException;
use Illuminate\Container\Container;
use Illuminate\Support\Reflector;
class ViewException extends ErrorException
{
/**
* Report the exception.
*
* @return bool|null
*/
public function report()
{
$exception = $this->getPrevious();
if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
return Container::getInstance()->call($reportCallable);
}
return false;
}
/**
* Render the exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|null
*/
public function render($request)
{
$exception = $this->getPrevious();
if ($exception && method_exists($exception, 'render')) {
return $exception->render($request);
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Illuminate\View;
interface ViewFinderInterface
{
/**
* Hint path delimiter value.
*
* @var string
*/
const HINT_PATH_DELIMITER = '::';
/**
* Get the fully qualified location of the view.
*
* @param string $view
* @return string
*/
public function find($view);
/**
* Add a location to the finder.
*
* @param string $location
* @return void
*/
public function addLocation($location);
/**
* Add a namespace hint to the finder.
*
* @param string $namespace
* @param string|array $hints
* @return void
*/
public function addNamespace($namespace, $hints);
/**
* Prepend a namespace hint to the finder.
*
* @param string $namespace
* @param string|array $hints
* @return void
*/
public function prependNamespace($namespace, $hints);
/**
* Replace the namespace hints for the given namespace.
*
* @param string $namespace
* @param string|array $hints
* @return void
*/
public function replaceNamespace($namespace, $hints);
/**
* Add a valid view extension to the finder.
*
* @param string $extension
* @return void
*/
public function addExtension($extension);
/**
* Flush the cache of located views.
*
* @return void
*/
public function flush();
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Illuminate\View;
class ViewName
{
/**
* Normalize the given view name.
*
* @param string $name
* @return string
*/
public static function normalize($name)
{
$delimiter = ViewFinderInterface::HINT_PATH_DELIMITER;
if (! str_contains($name, $delimiter)) {
return str_replace('/', '.', $name);
}
[$namespace, $name] = explode($delimiter, $name);
return $namespace.$delimiter.str_replace('/', '.', $name);
}
}

View File

@@ -0,0 +1,173 @@
<?php
namespace Illuminate\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\Engines\CompilerEngine;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Engines\FileEngine;
use Illuminate\View\Engines\PhpEngine;
class ViewServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerFactory();
$this->registerViewFinder();
$this->registerBladeCompiler();
$this->registerEngineResolver();
$this->app->terminating(static function () {
Component::flushCache();
});
}
/**
* Register the view environment.
*
* @return void
*/
public function registerFactory()
{
$this->app->singleton('view', function ($app) {
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$factory = $this->createFactory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$factory->setContainer($app);
$factory->share('app', $app);
$app->terminating(static function () {
Component::forgetFactory();
});
return $factory;
});
}
/**
* Create a new Factory Instance.
*
* @param \Illuminate\View\Engines\EngineResolver $resolver
* @param \Illuminate\View\ViewFinderInterface $finder
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return \Illuminate\View\Factory
*/
protected function createFactory($resolver, $finder, $events)
{
return new Factory($resolver, $finder, $events);
}
/**
* Register the view finder implementation.
*
* @return void
*/
public function registerViewFinder()
{
$this->app->bind('view.finder', function ($app) {
return new FileViewFinder($app['files'], $app['config']['view.paths']);
});
}
/**
* Register the Blade compiler implementation.
*
* @return void
*/
public function registerBladeCompiler()
{
$this->app->singleton('blade.compiler', function ($app) {
return tap(new BladeCompiler(
$app['files'],
$app['config']['view.compiled'],
$app['config']->get('view.relative_hash', false) ? $app->basePath() : '',
$app['config']->get('view.cache', true),
$app['config']->get('view.compiled_extension', 'php'),
), function ($blade) {
$blade->component('dynamic-component', DynamicComponent::class);
});
});
}
/**
* Register the engine resolver instance.
*
* @return void
*/
public function registerEngineResolver()
{
$this->app->singleton('view.engine.resolver', function () {
$resolver = new EngineResolver;
// Next, we will register the various view engines with the resolver so that the
// environment will resolve the engines needed for various views based on the
// extension of view file. We call a method for each of the view's engines.
foreach (['file', 'php', 'blade'] as $engine) {
$this->{'register'.ucfirst($engine).'Engine'}($resolver);
}
return $resolver;
});
}
/**
* Register the file engine implementation.
*
* @param \Illuminate\View\Engines\EngineResolver $resolver
* @return void
*/
public function registerFileEngine($resolver)
{
$resolver->register('file', function () {
return new FileEngine($this->app['files']);
});
}
/**
* Register the PHP engine implementation.
*
* @param \Illuminate\View\Engines\EngineResolver $resolver
* @return void
*/
public function registerPhpEngine($resolver)
{
$resolver->register('php', function () {
return new PhpEngine($this->app['files']);
});
}
/**
* Register the Blade engine implementation.
*
* @param \Illuminate\View\Engines\EngineResolver $resolver
* @return void
*/
public function registerBladeEngine($resolver)
{
$resolver->register('blade', function () {
$compiler = new CompilerEngine($this->app['blade.compiler'], $this->app['files']);
$this->app->terminating(static function () use ($compiler) {
$compiler->forgetCompiledOrNotExpired();
});
return $compiler;
});
}
}

View File

@@ -0,0 +1,41 @@
{
"name": "illuminate/view",
"description": "The Illuminate View package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.0.2",
"ext-tokenizer": "*",
"illuminate/collections": "^9.0",
"illuminate/container": "^9.0",
"illuminate/contracts": "^9.0",
"illuminate/events": "^9.0",
"illuminate/filesystem": "^9.0",
"illuminate/macroable": "^9.0",
"illuminate/support": "^9.0"
},
"autoload": {
"psr-4": {
"Illuminate\\View\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "9.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}