From 6ed899ccf20f5b52e5ef120b41dff9b6fbb8a1b0 Mon Sep 17 00:00:00 2001 From: Yu Shing Date: Wed, 15 Oct 2025 09:50:02 +0800 Subject: [PATCH 01/21] feat: copy source code from laravel --- src/view/README.md | 4 + src/view/composer.json | 50 + src/view/src/AnonymousComponent.php | 60 + src/view/src/AppendableAttributeValue.php | 36 + src/view/src/Compilers/BladeCompiler.php | 1068 +++++++++++++++++ src/view/src/Compilers/Compiler.php | 135 +++ src/view/src/Compilers/CompilerInterface.php | 30 + .../src/Compilers/ComponentTagCompiler.php | 810 +++++++++++++ .../Concerns/CompilesAuthorizations.php | 102 ++ .../Compilers/Concerns/CompilesClasses.php | 19 + .../Compilers/Concerns/CompilesComments.php | 19 + .../Compilers/Concerns/CompilesComponents.php | 222 ++++ .../Concerns/CompilesConditionals.php | 420 +++++++ .../src/Compilers/Concerns/CompilesEchos.php | 171 +++ .../src/Compilers/Concerns/CompilesErrors.php | 37 + .../Compilers/Concerns/CompilesFragments.php | 36 + .../Compilers/Concerns/CompilesHelpers.php | 78 ++ .../Compilers/Concerns/CompilesIncludes.php | 82 ++ .../Compilers/Concerns/CompilesInjections.php | 23 + .../src/Compilers/Concerns/CompilesJs.php | 22 + .../src/Compilers/Concerns/CompilesJson.php | 30 + .../Compilers/Concerns/CompilesLayouts.php | 133 ++ .../src/Compilers/Concerns/CompilesLoops.php | 194 +++ .../src/Compilers/Concerns/CompilesRawPhp.php | 32 + .../Compilers/Concerns/CompilesSessions.php | 37 + .../src/Compilers/Concerns/CompilesStacks.php | 117 ++ .../src/Compilers/Concerns/CompilesStyles.php | 19 + .../Concerns/CompilesTranslations.php | 44 + .../Concerns/CompilesUseStatements.php | 22 + src/view/src/Component.php | 491 ++++++++ src/view/src/ComponentAttributeBag.php | 524 ++++++++ src/view/src/ComponentSlot.php | 110 ++ src/view/src/Concerns/ManagesComponents.php | 221 ++++ src/view/src/Concerns/ManagesEvents.php | 194 +++ src/view/src/Concerns/ManagesFragments.php | 88 ++ src/view/src/Concerns/ManagesLayouts.php | 255 ++++ src/view/src/Concerns/ManagesLoops.php | 96 ++ src/view/src/Concerns/ManagesStacks.php | 179 +++ src/view/src/Concerns/ManagesTranslations.php | 38 + src/view/src/DynamicComponent.php | 174 +++ src/view/src/Engines/CompilerEngine.php | 150 +++ src/view/src/Engines/Engine.php | 23 + src/view/src/Engines/EngineResolver.php | 71 ++ src/view/src/Engines/FileEngine.php | 39 + src/view/src/Engines/PhpEngine.php | 83 ++ src/view/src/Factory.php | 645 ++++++++++ src/view/src/FileViewFinder.php | 332 +++++ src/view/src/InvokableComponentVariable.php | 97 ++ src/view/src/LICENSE.md | 21 + .../src/Middleware/ShareErrorsFromSession.php | 51 + src/view/src/View.php | 508 ++++++++ src/view/src/ViewException.php | 41 + src/view/src/ViewFinderInterface.php | 71 ++ src/view/src/ViewName.php | 25 + src/view/src/ViewServiceProvider.php | 179 +++ src/view/src/composer.json | 41 + 56 files changed, 8799 insertions(+) create mode 100644 src/view/README.md create mode 100644 src/view/composer.json create mode 100644 src/view/src/AnonymousComponent.php create mode 100644 src/view/src/AppendableAttributeValue.php create mode 100644 src/view/src/Compilers/BladeCompiler.php create mode 100755 src/view/src/Compilers/Compiler.php create mode 100755 src/view/src/Compilers/CompilerInterface.php create mode 100644 src/view/src/Compilers/ComponentTagCompiler.php create mode 100644 src/view/src/Compilers/Concerns/CompilesAuthorizations.php create mode 100644 src/view/src/Compilers/Concerns/CompilesClasses.php create mode 100644 src/view/src/Compilers/Concerns/CompilesComments.php create mode 100644 src/view/src/Compilers/Concerns/CompilesComponents.php create mode 100644 src/view/src/Compilers/Concerns/CompilesConditionals.php create mode 100644 src/view/src/Compilers/Concerns/CompilesEchos.php create mode 100644 src/view/src/Compilers/Concerns/CompilesErrors.php create mode 100644 src/view/src/Compilers/Concerns/CompilesFragments.php create mode 100644 src/view/src/Compilers/Concerns/CompilesHelpers.php create mode 100644 src/view/src/Compilers/Concerns/CompilesIncludes.php create mode 100644 src/view/src/Compilers/Concerns/CompilesInjections.php create mode 100644 src/view/src/Compilers/Concerns/CompilesJs.php create mode 100644 src/view/src/Compilers/Concerns/CompilesJson.php create mode 100644 src/view/src/Compilers/Concerns/CompilesLayouts.php create mode 100644 src/view/src/Compilers/Concerns/CompilesLoops.php create mode 100644 src/view/src/Compilers/Concerns/CompilesRawPhp.php create mode 100644 src/view/src/Compilers/Concerns/CompilesSessions.php create mode 100644 src/view/src/Compilers/Concerns/CompilesStacks.php create mode 100644 src/view/src/Compilers/Concerns/CompilesStyles.php create mode 100644 src/view/src/Compilers/Concerns/CompilesTranslations.php create mode 100644 src/view/src/Compilers/Concerns/CompilesUseStatements.php create mode 100644 src/view/src/Component.php create mode 100644 src/view/src/ComponentAttributeBag.php create mode 100644 src/view/src/ComponentSlot.php create mode 100644 src/view/src/Concerns/ManagesComponents.php create mode 100644 src/view/src/Concerns/ManagesEvents.php create mode 100644 src/view/src/Concerns/ManagesFragments.php create mode 100644 src/view/src/Concerns/ManagesLayouts.php create mode 100644 src/view/src/Concerns/ManagesLoops.php create mode 100644 src/view/src/Concerns/ManagesStacks.php create mode 100644 src/view/src/Concerns/ManagesTranslations.php create mode 100644 src/view/src/DynamicComponent.php create mode 100755 src/view/src/Engines/CompilerEngine.php create mode 100755 src/view/src/Engines/Engine.php create mode 100755 src/view/src/Engines/EngineResolver.php create mode 100644 src/view/src/Engines/FileEngine.php create mode 100755 src/view/src/Engines/PhpEngine.php create mode 100755 src/view/src/Factory.php create mode 100755 src/view/src/FileViewFinder.php create mode 100644 src/view/src/InvokableComponentVariable.php create mode 100644 src/view/src/LICENSE.md create mode 100644 src/view/src/Middleware/ShareErrorsFromSession.php create mode 100755 src/view/src/View.php create mode 100644 src/view/src/ViewException.php create mode 100755 src/view/src/ViewFinderInterface.php create mode 100644 src/view/src/ViewName.php create mode 100755 src/view/src/ViewServiceProvider.php create mode 100644 src/view/src/composer.json diff --git a/src/view/README.md b/src/view/README.md new file mode 100644 index 000000000..d67ed0829 --- /dev/null +++ b/src/view/README.md @@ -0,0 +1,4 @@ +View for Hypervel +=== + +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/hypervel/view) diff --git a/src/view/composer.json b/src/view/composer.json new file mode 100644 index 000000000..7b0d5c149 --- /dev/null +++ b/src/view/composer.json @@ -0,0 +1,50 @@ +{ + "name": "hypervel/view", + "type": "library", + "description": "The view package for Hypervel.", + "license": "MIT", + "keywords": [ + "php", + "hyperf", + "swoole", + "view", + "hypervel" + ], + "authors": [ + { + "name": "Albert Chen", + "email": "albert@hypervel.org" + } + ], + "support": { + "issues": "https://github.com/hypervel/components/issues", + "source": "https://github.com/hypervel/components" + }, + "autoload": { + "psr-4": { + "Hypervel\\View\\": "src/" + } + }, + "require": { + "php": "^8.2", + "ext-tokenizer": "*", + "hyperf/contract": "~3.1.0", + "hyperf/http-server": "~3.1.0", + "hyperf/pool": "~3.1.0", + "hypervel/collections": "^0.3", + "hypervel/container": "^0.3", + "hypervel/contracts": "^0.3", + "hypervel/events": "^0.3", + "hypervel/filesystem": "^0.3", + "hypervel/macroable": "^0.3", + "hypervel/support": "^0.3" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-main": "0.3-dev" + } + } +} diff --git a/src/view/src/AnonymousComponent.php b/src/view/src/AnonymousComponent.php new file mode 100644 index 000000000..eba643656 --- /dev/null +++ b/src/view/src/AnonymousComponent.php @@ -0,0 +1,60 @@ +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] + ); + } +} diff --git a/src/view/src/AppendableAttributeValue.php b/src/view/src/AppendableAttributeValue.php new file mode 100644 index 000000000..10981bf97 --- /dev/null +++ b/src/view/src/AppendableAttributeValue.php @@ -0,0 +1,36 @@ +value = $value; + } + + /** + * Get the string value. + * + * @return string + */ + public function __toString() + { + return (string) $this->value; + } +} diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php new file mode 100644 index 000000000..ca46c5a41 --- /dev/null +++ b/src/view/src/Compilers/BladeCompiler.php @@ -0,0 +1,1068 @@ +setPath($path); + } + + if (! is_null($this->cachePath)) { + $contents = $this->compileString($this->files->get($this->getPath())); + + if (! empty($this->getPath())) { + $contents = $this->appendFilePath($contents); + } + + $this->ensureCompiledDirectoryExists( + $compiledPath = $this->getCompiledPath($this->getPath()) + ); + + $this->files->put($compiledPath, $contents); + } + } + + /** + * Append the file path to the compiled string. + * + * @param string $contents + * @return string + */ + protected function appendFilePath($contents) + { + $tokens = $this->getOpenAndClosingPhpTokens($contents); + + if ($tokens->isNotEmpty() && $tokens->last() !== T_CLOSE_TAG) { + $contents .= ' ?>'; + } + + return $contents."getPath()} ENDPATH**/ ?>"; + } + + /** + * Get the open and closing PHP tag tokens from the given string. + * + * @param string $contents + * @return \Illuminate\Support\Collection + */ + protected function getOpenAndClosingPhpTokens($contents) + { + return (new Collection(token_get_all($contents))) + ->pluck(0) + ->filter(function ($token) { + return in_array($token, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG]); + }); + } + + /** + * Get the path currently being compiled. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set the path currently being compiled. + * + * @param string $path + * @return void + */ + public function setPath($path) + { + $this->path = $path; + } + + /** + * Compile the given Blade template contents. + * + * @param string $value + * @return string + */ + public function compileString($value) + { + [$this->footer, $result] = [[], '']; + + foreach ($this->prepareStringsForCompilationUsing as $callback) { + $value = $callback($value); + } + + $value = $this->storeUncompiledBlocks($value); + + // First we will compile the Blade component tags. This is a precompile style + // step which compiles the component Blade tags into @component directives + // that may be used by Blade. Then we should call any other precompilers. + $value = $this->compileComponentTags( + $this->compileComments($value) + ); + + foreach ($this->precompilers as $precompiler) { + $value = $precompiler($value); + } + + // Here we will loop through all of the tokens returned by the Zend lexer and + // parse each one into the corresponding valid PHP. We will then have this + // template as the correctly rendered PHP that can be rendered natively. + foreach (token_get_all($value) as $token) { + $result .= is_array($token) ? $this->parseToken($token) : $token; + } + + if (! empty($this->rawBlocks)) { + $result = $this->restoreRawContent($result); + } + + // If there are any footer lines that need to get added to a template we will + // add them here at the end of the template. This gets used mainly for the + // template inheritance via the extends keyword that should be appended. + if (count($this->footer) > 0) { + $result = $this->addFooters($result); + } + + if (! empty($this->echoHandlers)) { + $result = $this->addBladeCompilerVariable($result); + } + + return str_replace( + ['##BEGIN-COMPONENT-CLASS##', '##END-COMPONENT-CLASS##'], + '', + $result); + } + + /** + * Evaluate and render a Blade string to HTML. + * + * @param string $string + * @param array $data + * @param bool $deleteCachedView + * @return string + */ + public static function render($string, $data = [], $deleteCachedView = false) + { + $component = new class($string) extends Component + { + protected $template; + + public function __construct($template) + { + $this->template = $template; + } + + public function render() + { + return $this->template; + } + }; + + $view = Container::getInstance() + ->make(ViewFactory::class) + ->make($component->resolveView(), $data); + + return tap($view->render(), function () use ($view, $deleteCachedView) { + if ($deleteCachedView) { + @unlink($view->getPath()); + } + }); + } + + /** + * Render a component instance to HTML. + * + * @param \Illuminate\View\Component $component + * @return string + */ + public static function renderComponent(Component $component) + { + $data = $component->data(); + + $view = value($component->resolveView(), $data); + + if ($view instanceof View) { + return $view->with($data)->render(); + } elseif ($view instanceof Htmlable) { + return $view->toHtml(); + } else { + return Container::getInstance() + ->make(ViewFactory::class) + ->make($view, $data) + ->render(); + } + } + + /** + * Store the blocks that do not receive compilation. + * + * @param string $value + * @return string + */ + protected function storeUncompiledBlocks($value) + { + if (str_contains($value, '@verbatim')) { + $value = $this->storeVerbatimBlocks($value); + } + + if (str_contains($value, '@php')) { + $value = $this->storePhpBlocks($value); + } + + return $value; + } + + /** + * Store the verbatim blocks and replace them with a temporary placeholder. + * + * @param string $value + * @return string + */ + protected function storeVerbatimBlocks($value) + { + return preg_replace_callback('/(?storeRawBlock($matches[2]); + }, $value); + } + + /** + * Store the PHP blocks and replace them with a temporary placeholder. + * + * @param string $value + * @return string + */ + protected function storePhpBlocks($value) + { + return preg_replace_callback('/(?storeRawBlock(""); + }, $value); + } + + /** + * Store a raw block and return a unique raw placeholder. + * + * @param string $value + * @return string + */ + protected function storeRawBlock($value) + { + return $this->getRawPlaceholder( + array_push($this->rawBlocks, $value) - 1 + ); + } + + /** + * Compile the component tags. + * + * @param string $value + * @return string + */ + protected function compileComponentTags($value) + { + if (! $this->compilesComponentTags) { + return $value; + } + + return (new ComponentTagCompiler( + $this->classComponentAliases, $this->classComponentNamespaces, $this + ))->compile($value); + } + + /** + * Replace the raw placeholders with the original code stored in the raw blocks. + * + * @param string $result + * @return string + */ + protected function restoreRawContent($result) + { + $result = preg_replace_callback('/'.$this->getRawPlaceholder('(\d+)').'/', function ($matches) { + return $this->rawBlocks[$matches[1]]; + }, $result); + + $this->rawBlocks = []; + + return $result; + } + + /** + * Get a placeholder to temporarily mark the position of raw blocks. + * + * @param int|string $replace + * @return string + */ + protected function getRawPlaceholder($replace) + { + return str_replace('#', $replace, '@__raw_block_#__@'); + } + + /** + * Add the stored footers onto the given content. + * + * @param string $result + * @return string + */ + protected function addFooters($result) + { + return ltrim($result, "\n") + ."\n".implode("\n", array_reverse($this->footer)); + } + + /** + * Parse the tokens from the template. + * + * @param array $token + * @return string + */ + protected function parseToken($token) + { + [$id, $content] = $token; + + if ($id == T_INLINE_HTML) { + foreach ($this->compilers as $type) { + $content = $this->{"compile{$type}"}($content); + } + } + + return $content; + } + + /** + * Execute the user defined extensions. + * + * @param string $value + * @return string + */ + protected function compileExtensions($value) + { + foreach ($this->extensions as $compiler) { + $value = $compiler($value, $this); + } + + return $value; + } + + /** + * Compile Blade statements that start with "@". + * + * @param string $template + * @return string + */ + protected function compileStatements($template) + { + preg_match_all('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( [\S\s]*? ) \))?/x', $template, $matches); + + $offset = 0; + + for ($i = 0; isset($matches[0][$i]); $i++) { + $match = [ + $matches[0][$i], + $matches[1][$i], + $matches[2][$i], + $matches[3][$i] ?: null, + $matches[4][$i] ?: null, + ]; + + // Here we check to see if we have properly found the closing parenthesis by + // regex pattern or not, and will recursively continue on to the next ")" + // then check again until the tokenizer confirms we find the right one. + while (isset($match[4]) && + Str::endsWith($match[0], ')') && + ! $this->hasEvenNumberOfParentheses($match[0])) { + if (($after = Str::after($template, $match[0])) === $template) { + break; + } + + $rest = Str::before($after, ')'); + + if (isset($matches[0][$i + 1]) && Str::contains($rest.')', $matches[0][$i + 1])) { + unset($matches[0][$i + 1]); + $i++; + } + + $match[0] = $match[0].$rest.')'; + $match[3] = $match[3].$rest.')'; + $match[4] = $match[4].$rest; + } + + [$template, $offset] = $this->replaceFirstStatement( + $match[0], + $this->compileStatement($match), + $template, + $offset + ); + } + + return $template; + } + + /** + * Replace the first match for a statement compilation operation. + * + * @param string $search + * @param string $replace + * @param string $subject + * @param int $offset + * @return array + */ + protected function replaceFirstStatement($search, $replace, $subject, $offset) + { + $search = (string) $search; + + if ($search === '') { + return $subject; + } + + $position = strpos($subject, $search, $offset); + + if ($position !== false) { + return [ + substr_replace($subject, $replace, $position, strlen($search)), + $position + strlen($replace), + ]; + } + + return [$subject, 0]; + } + + /** + * Determine if the given expression has the same number of opening and closing parentheses. + * + * @param string $expression + * @return bool + */ + protected function hasEvenNumberOfParentheses(string $expression) + { + $tokens = token_get_all('customDirectives[$match[1]])) { + $match[0] = $this->callCustomDirective($match[1], Arr::get($match, 3)); + } elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) { + $match[0] = $this->$method(Arr::get($match, 3)); + } else { + return $match[0]; + } + + return isset($match[3]) ? $match[0] : $match[0].$match[2]; + } + + /** + * Call the given directive with the given value. + * + * @param string $name + * @param string|null $value + * @return string + */ + protected function callCustomDirective($name, $value) + { + $value ??= ''; + + if (str_starts_with($value, '(') && str_ends_with($value, ')')) { + $value = Str::substr($value, 1, -1); + } + + return call_user_func($this->customDirectives[$name], trim($value)); + } + + /** + * Strip the parentheses from the given expression. + * + * @param string $expression + * @return string + */ + public function stripParentheses($expression) + { + if (Str::startsWith($expression, '(')) { + $expression = substr($expression, 1, -1); + } + + return $expression; + } + + /** + * Register a custom Blade compiler. + * + * @param callable $compiler + * @return void + */ + public function extend(callable $compiler) + { + $this->extensions[] = $compiler; + } + + /** + * Get the extensions used by the compiler. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Register an "if" statement directive. + * + * @param string $name + * @param callable $callback + * @return void + */ + public function if($name, callable $callback) + { + $this->conditions[$name] = $callback; + + $this->directive($name, function ($expression) use ($name) { + return $expression !== '' + ? "" + : ""; + }); + + $this->directive('unless'.$name, function ($expression) use ($name) { + return $expression !== '' + ? "" + : ""; + }); + + $this->directive('else'.$name, function ($expression) use ($name) { + return $expression !== '' + ? "" + : ""; + }); + + $this->directive('end'.$name, function () { + return ''; + }); + } + + /** + * Check the result of a condition. + * + * @param string $name + * @param mixed ...$parameters + * @return bool + */ + public function check($name, ...$parameters) + { + return call_user_func($this->conditions[$name], ...$parameters); + } + + /** + * Register a class-based component alias directive. + * + * @param string $class + * @param string|null $alias + * @param string $prefix + * @return void + */ + public function component($class, $alias = null, $prefix = '') + { + if (! is_null($alias) && str_contains($alias, '\\')) { + [$class, $alias] = [$alias, $class]; + } + + if (is_null($alias)) { + $alias = str_contains($class, '\\View\\Components\\') + ? (new Collection(explode('\\', Str::after($class, '\\View\\Components\\'))))->map(function ($segment) { + return Str::kebab($segment); + })->implode(':') + : Str::kebab(class_basename($class)); + } + + if (! empty($prefix)) { + $alias = $prefix.'-'.$alias; + } + + $this->classComponentAliases[$alias] = $class; + } + + /** + * Register an array of class-based components. + * + * @param array $components + * @param string $prefix + * @return void + */ + public function components(array $components, $prefix = '') + { + foreach ($components as $key => $value) { + if (is_numeric($key)) { + $this->component($value, null, $prefix); + } else { + $this->component($key, $value, $prefix); + } + } + } + + /** + * Get the registered class component aliases. + * + * @return array + */ + public function getClassComponentAliases() + { + return $this->classComponentAliases; + } + + /** + * Register a new anonymous component path. + * + * @param string $path + * @param string|null $prefix + * @return void + */ + public function anonymousComponentPath(string $path, ?string $prefix = null) + { + $prefixHash = md5($prefix ?: $path); + + $this->anonymousComponentPaths[] = [ + 'path' => $path, + 'prefix' => $prefix, + 'prefixHash' => $prefixHash, + ]; + + Container::getInstance() + ->make(ViewFactory::class) + ->addNamespace($prefixHash, $path); + } + + /** + * Register an anonymous component namespace. + * + * @param string $directory + * @param string|null $prefix + * @return void + */ + public function anonymousComponentNamespace(string $directory, ?string $prefix = null) + { + $prefix ??= $directory; + + $this->anonymousComponentNamespaces[$prefix] = (new Stringable($directory)) + ->replace('/', '.') + ->trim('. ') + ->toString(); + } + + /** + * Register a class-based component namespace. + * + * @param string $namespace + * @param string $prefix + * @return void + */ + public function componentNamespace($namespace, $prefix) + { + $this->classComponentNamespaces[$prefix] = $namespace; + } + + /** + * Get the registered anonymous component paths. + * + * @return array + */ + public function getAnonymousComponentPaths() + { + return $this->anonymousComponentPaths; + } + + /** + * Get the registered anonymous component namespaces. + * + * @return array + */ + public function getAnonymousComponentNamespaces() + { + return $this->anonymousComponentNamespaces; + } + + /** + * Get the registered class component namespaces. + * + * @return array + */ + public function getClassComponentNamespaces() + { + return $this->classComponentNamespaces; + } + + /** + * Register a component alias directive. + * + * @param string $path + * @param string|null $alias + * @return void + */ + public function aliasComponent($path, $alias = null) + { + $alias = $alias ?: Arr::last(explode('.', $path)); + + $this->directive($alias, function ($expression) use ($path) { + return $expression + ? "startComponent('{$path}', {$expression}); ?>" + : "startComponent('{$path}'); ?>"; + }); + + $this->directive('end'.$alias, function ($expression) { + return 'renderComponent(); ?>'; + }); + } + + /** + * Register an include alias directive. + * + * @param string $path + * @param string|null $alias + * @return void + */ + public function include($path, $alias = null) + { + $this->aliasInclude($path, $alias); + } + + /** + * Register an include alias directive. + * + * @param string $path + * @param string|null $alias + * @return void + */ + public function aliasInclude($path, $alias = null) + { + $alias = $alias ?: Arr::last(explode('.', $path)); + + $this->directive($alias, function ($expression) use ($path) { + $expression = $this->stripParentheses($expression) ?: '[]'; + + return "make('{$path}', {$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>"; + }); + } + + /** + * Register a handler for custom directives, binding the handler to the compiler. + * + * @param string $name + * @param callable $handler + * @return void + * + * @throws \InvalidArgumentException + */ + public function bindDirective($name, callable $handler) + { + $this->directive($name, $handler, bind: true); + } + + /** + * Register a handler for custom directives. + * + * @param string $name + * @param callable $handler + * @param bool $bind + * @return void + * + * @throws \InvalidArgumentException + */ + public function directive($name, callable $handler, bool $bind = false) + { + if (! preg_match('/^\w+(?:::\w+)?$/x', $name)) { + throw new InvalidArgumentException("The directive name [{$name}] is not valid. Directive names must only contain alphanumeric characters and underscores."); + } + + $this->customDirectives[$name] = $bind ? $handler->bindTo($this, BladeCompiler::class) : $handler; + } + + /** + * Get the list of custom directives. + * + * @return array + */ + public function getCustomDirectives() + { + return $this->customDirectives; + } + + /** + * Indicate that the following callable should be used to prepare strings for compilation. + * + * @param callable $callback + * @return $this + */ + public function prepareStringsForCompilationUsing(callable $callback) + { + $this->prepareStringsForCompilationUsing[] = $callback; + + return $this; + } + + /** + * Register a new precompiler. + * + * @param callable $precompiler + * @return void + */ + public function precompiler(callable $precompiler) + { + $this->precompilers[] = $precompiler; + } + + /** + * Execute the given callback using a custom echo format. + * + * @param string $format + * @param callable $callback + * @return string + */ + public function usingEchoFormat($format, callable $callback) + { + $originalEchoFormat = $this->echoFormat; + + $this->setEchoFormat($format); + + try { + $output = call_user_func($callback); + } finally { + $this->setEchoFormat($originalEchoFormat); + } + + return $output; + } + + /** + * Set the echo format to be used by the compiler. + * + * @param string $format + * @return void + */ + public function setEchoFormat($format) + { + $this->echoFormat = $format; + } + + /** + * Set the "echo" format to double encode entities. + * + * @return void + */ + public function withDoubleEncoding() + { + $this->setEchoFormat('e(%s, true)'); + } + + /** + * Set the "echo" format to not double encode entities. + * + * @return void + */ + public function withoutDoubleEncoding() + { + $this->setEchoFormat('e(%s, false)'); + } + + /** + * Indicate that component tags should not be compiled. + * + * @return void + */ + public function withoutComponentTags() + { + $this->compilesComponentTags = false; + } +} diff --git a/src/view/src/Compilers/Compiler.php b/src/view/src/Compilers/Compiler.php new file mode 100755 index 000000000..e93b78310 --- /dev/null +++ b/src/view/src/Compilers/Compiler.php @@ -0,0 +1,135 @@ +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.'/'.hash('xxh128', 'v2'.Str::after($path, $this->basePath)).'.'.$this->compiledExtension; + } + + /** + * Determine if the view at the given path is expired. + * + * @param string $path + * @return bool + * + * @throws \ErrorException + */ + 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; + } + + try { + return $this->files->lastModified($path) >= + $this->files->lastModified($compiled); + } catch (ErrorException $exception) { + if (! $this->files->exists($compiled)) { + return true; + } + + throw $exception; + } + } + + /** + * 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); + } + } +} diff --git a/src/view/src/Compilers/CompilerInterface.php b/src/view/src/Compilers/CompilerInterface.php new file mode 100755 index 000000000..dfcb023af --- /dev/null +++ b/src/view/src/Compilers/CompilerInterface.php @@ -0,0 +1,30 @@ + + * @author Taylor Otwell + */ +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\-\:\.]*) + (? + (?: + \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* + (? + (?: + \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).']) + +except(\\'.$class.'::ignoredParameterNames()); ?> + +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 (class_exists($class = $class.'\\'.Str::afterLast($class, '\\'))) { + 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, + $viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent.'.'.Str::afterLast($formattedComponent, '.')) => $guess, + default => null, + })) { + return $guess; + } + } catch (InvalidArgumentException) { + // + } + } + } + + /** + * 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 (new Collection($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; + } + + $lastViewSegment = Str::afterLast(Str::afterLast($componentName, '.'), ':'); + + if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory).'.'.$lastViewSegment)) { + 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 [new Collection($attributes), new Collection($attributes)]; + } + + $constructor = (new ReflectionClass($class))->getConstructor(); + + $parameterNames = $constructor + ? (new Collection($constructor->getParameters()))->map->getName()->all() + : []; + + return (new Collection($attributes)) + ->partition(fn ($value, $key) => 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 + (?:\:(?\w+(?:-\w+)*))? + (?:\s+name=(?(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))? + (?:\s+\:name=(?(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))? + (? + (?: + \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'] ?: $matches['boundName']); + + if (Str::contains($name, '-') && ! empty($matches['inlineName'])) { + $name = Str::camel($name); + } + + // If the name was given as a simple string, we will wrap it in quotes as if it was bound for convenience... + if (! empty($matches['inlineName']) || ! empty($matches['name'])) { + $name = "'{$name}'"; + } + + $this->boundAttributes = []; + + $attributes = $this->getAttributesFromAttributeString($matches['attributes']); + + // If an inline name was provided and a name or bound name was *also* provided, we will assume the name should be an attribute... + if (! empty($matches['inlineName']) && (! empty($matches['name']) || ! empty($matches['boundName']))) { + $attributes = ! empty($matches['name']) + ? array_merge($attributes, $this->getAttributesFromAttributeString('name='.$matches['name'])) + : array_merge($attributes, $this->getAttributesFromAttributeString(':name='.$matches['boundName'])); + } + + 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 = '/ + (?[\w\-:.@%]+) + ( + = + (? + ( + \"[^\"]+\" + | + \\\'[^\\\']+\\\' + | + [^\s>]+ + ) + ) + )? + /x'; + + if (! preg_match_all($pattern, $attributeString, $matches, PREG_SET_ORDER)) { + return []; + } + + return (new Collection($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(?:[^}]+?(?[^()]+) | (?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('', '.\'', $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 (new Collection(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 (new Collection($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; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesAuthorizations.php b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php new file mode 100644 index 000000000..4f969982c --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php @@ -0,0 +1,102 @@ +check{$expression}): ?>"; + } + + /** + * Compile the cannot statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileCannot($expression) + { + return "denies{$expression}): ?>"; + } + + /** + * Compile the canany statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileCanany($expression) + { + return "any{$expression}): ?>"; + } + + /** + * Compile the else-can statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElsecan($expression) + { + return "check{$expression}): ?>"; + } + + /** + * Compile the else-cannot statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElsecannot($expression) + { + return "denies{$expression}): ?>"; + } + + /** + * Compile the else-canany statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElsecanany($expression) + { + return "any{$expression}): ?>"; + } + + /** + * Compile the end-can statements into valid PHP. + * + * @return string + */ + protected function compileEndcan() + { + return ''; + } + + /** + * Compile the end-cannot statements into valid PHP. + * + * @return string + */ + protected function compileEndcannot() + { + return ''; + } + + /** + * Compile the end-canany statements into valid PHP. + * + * @return string + */ + protected function compileEndcanany() + { + return ''; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesClasses.php b/src/view/src/Compilers/Concerns/CompilesClasses.php new file mode 100644 index 000000000..d2507e711 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesClasses.php @@ -0,0 +1,19 @@ +\""; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesComments.php b/src/view/src/Compilers/Concerns/CompilesComments.php new file mode 100644 index 000000000..104a9c815 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesComments.php @@ -0,0 +1,19 @@ +contentTags[0], $this->contentTags[1]); + + return preg_replace($pattern, '', $value); + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesComponents.php b/src/view/src/Compilers/Concerns/CompilesComponents.php new file mode 100644 index 000000000..56d1399e8 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesComponents.php @@ -0,0 +1,222 @@ +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 = hash('xxh128', $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", [ + '', + '', + 'all() : [])); ?>', + 'withName('.$alias.'); ?>', + 'shouldRender()): ?>', + 'startComponent($component->resolveView(), $component->data()); ?>', + ]); + } + + /** + * Compile the end-component statements into valid PHP. + * + * @return string + */ + protected function compileEndComponent() + { + return '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", [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]); + } + + /** + * Compile the slot statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSlot($expression) + { + return "slot{$expression}; ?>"; + } + + /** + * Compile the end-slot statements into valid PHP. + * + * @return string + */ + protected function compileEndSlot() + { + return 'endSlot(); ?>'; + } + + /** + * Compile the component-first statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileComponentFirst($expression) + { + return "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 "all() as \$__key => \$__value) { + if (in_array(\$__key, \$__propNames)) { + \$\$__key = \$\$__key ?? \$__value; + } else { + \$__newAttributes[\$__key] = \$__value; + } +} + +\$attributes = new \Illuminate\View\ComponentAttributeBag(\$__newAttributes); + +unset(\$__propNames); +unset(\$__newAttributes); + +foreach (array_filter({$expression}, 'is_string', ARRAY_FILTER_USE_KEY) as \$__key => \$__value) { + \$\$__key = \$\$__key ?? \$__value; +} + +\$__defined_vars = get_defined_vars(); + +foreach (\$attributes->all() as \$__key => \$__value) { + if (array_key_exists(\$__key, \$__defined_vars)) unset(\$\$__key); +} + +unset(\$__defined_vars); ?>"; + } + + /** + * Compile the aware statement into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileAware($expression) + { + return " \$__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; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesConditionals.php b/src/view/src/Compilers/Concerns/CompilesConditionals.php new file mode 100644 index 000000000..f54d33b5f --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesConditionals.php @@ -0,0 +1,420 @@ +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 "guard{$guard}->check()): ?>"; + } + + /** + * Compile the end-auth statements into valid PHP. + * + * @return string + */ + protected function compileEndAuth() + { + return ''; + } + + /** + * Compile the env statements into valid PHP. + * + * @param string $environments + * @return string + */ + protected function compileEnv($environments) + { + return "environment{$environments}): ?>"; + } + + /** + * Compile the end-env statements into valid PHP. + * + * @return string + */ + protected function compileEndEnv() + { + return ''; + } + + /** + * Compile the production statements into valid PHP. + * + * @return string + */ + protected function compileProduction() + { + return "environment('production')): ?>"; + } + + /** + * Compile the end-production statements into valid PHP. + * + * @return string + */ + protected function compileEndProduction() + { + return ''; + } + + /** + * 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 "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 "guard{$guard}->guest()): ?>"; + } + + /** + * Compile the end-guest statements into valid PHP. + * + * @return string + */ + protected function compileEndGuest() + { + return ''; + } + + /** + * Compile the has-section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileHasSection($expression) + { + return "yieldContent{$expression}))): ?>"; + } + + /** + * Compile the section-missing statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSectionMissing($expression) + { + return "yieldContent{$expression}))): ?>"; + } + + /** + * Compile the if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIf($expression) + { + return ""; + } + + /** + * Compile the unless statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileUnless($expression) + { + return ""; + } + + /** + * Compile the else-if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElseif($expression) + { + return ""; + } + + /** + * Compile the else statements into valid PHP. + * + * @return string + */ + protected function compileElse() + { + return ''; + } + + /** + * Compile the end-if statements into valid PHP. + * + * @return string + */ + protected function compileEndif() + { + return ''; + } + + /** + * Compile the end-unless statements into valid PHP. + * + * @return string + */ + protected function compileEndunless() + { + return ''; + } + + /** + * Compile the if-isset statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIsset($expression) + { + return ""; + } + + /** + * Compile the end-isset statements into valid PHP. + * + * @return string + */ + protected function compileEndIsset() + { + return ''; + } + + /** + * Compile the switch statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSwitch($expression) + { + $this->firstCaseInSwitch = true; + + return "firstCaseInSwitch) { + $this->firstCaseInSwitch = false; + + return "case {$expression}: ?>"; + } + + return ""; + } + + /** + * Compile the default statements in switch case into valid PHP. + * + * @return string + */ + protected function compileDefault() + { + return ''; + } + + /** + * Compile the end switch statements into valid PHP. + * + * @return string + */ + protected function compileEndSwitch() + { + return ''; + } + + /** + * 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 'hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); ?>'; + } + + /** + * Compile an end-once block into valid PHP. + * + * @return string + */ + public function compileEndOnce() + { + return ''; + } + + /** + * Compile a boolean value into a raw true / false value for embedding into HTML attributes or JavaScript. + * + * @param bool $condition + * @return string + */ + protected function compileBool($condition) + { + return ""; + } + + /** + * Compile a checked block into valid PHP. + * + * @param string $condition + * @return string + */ + protected function compileChecked($condition) + { + return ""; + } + + /** + * Compile a disabled block into valid PHP. + * + * @param string $condition + * @return string + */ + protected function compileDisabled($condition) + { + return ""; + } + + /** + * Compile a required block into valid PHP. + * + * @param string $condition + * @return string + */ + protected function compileRequired($condition) + { + return ""; + } + + /** + * Compile a readonly block into valid PHP. + * + * @param string $condition + * @return string + */ + protected function compileReadonly($condition) + { + return ""; + } + + /** + * Compile a selected block into valid PHP. + * + * @param string $condition + * @return string + */ + protected function compileSelected($condition) + { + return ""; + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compilePushIf($expression) + { + $parts = explode(',', $this->stripParentheses($expression), 2); + + return "startPush({$parts[1]}); ?>"; + } + + /** + * Compile the else-if push statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElsePushIf($expression) + { + $parts = explode(',', $this->stripParentheses($expression), 2); + + return "stopPush(); elseif({$parts[0]}): \$__env->startPush({$parts[1]}); ?>"; + } + + /** + * Compile the else push statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElsePush($expression) + { + return "stopPush(); else: \$__env->startPush{$expression}; ?>"; + } + + /** + * Compile the end-push statements into valid PHP. + * + * @return string + */ + protected function compileEndPushIf() + { + return 'stopPush(); endif; ?>'; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesEchos.php b/src/view/src/Compilers/Concerns/CompilesEchos.php new file mode 100644 index 000000000..57896fe8f --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesEchos.php @@ -0,0 +1,171 @@ +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) + : "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) : "{$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] + : "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 "".$result; + } + + /** + * Wrap the echoable value in an echo handler if applicable. + * + * @param string $value + * @return string + */ + protected function wrapInEchoHandler($value) + { + $value = (new Stringable($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); + } + + if (is_iterable($value) && isset($this->echoHandlers['iterable'])) { + return call_user_func($this->echoHandlers['iterable'], $value); + } + + return $value; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesErrors.php b/src/view/src/Compilers/Concerns/CompilesErrors.php new file mode 100644 index 000000000..77edc4bf8 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesErrors.php @@ -0,0 +1,37 @@ +stripParentheses($expression); + + return '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 ''; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesFragments.php b/src/view/src/Compilers/Concerns/CompilesFragments.php new file mode 100644 index 000000000..607b6dd2e --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesFragments.php @@ -0,0 +1,36 @@ +lastFragment = trim($expression, "()'\" "); + + return "startFragment{$expression}; ?>"; + } + + /** + * Compile the end-fragment statements into valid PHP. + * + * @return string + */ + protected function compileEndfragment() + { + return 'stopFragment(); ?>'; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesHelpers.php b/src/view/src/Compilers/Concerns/CompilesHelpers.php new file mode 100644 index 000000000..f217f59be --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesHelpers.php @@ -0,0 +1,78 @@ +'; + } + + /** + * Compile the "dd" statements into valid PHP. + * + * @param string $arguments + * @return string + */ + protected function compileDd($arguments) + { + return ""; + } + + /** + * Compile the "dump" statements into valid PHP. + * + * @param string $arguments + * @return string + */ + protected function compileDump($arguments) + { + return ""; + } + + /** + * Compile the method statements into valid PHP. + * + * @param string $method + * @return string + */ + protected function compileMethod($method) + { + return ""; + } + + /** + * Compile the "vite" statements into valid PHP. + * + * @param string|null $arguments + * @return string + */ + protected function compileVite($arguments) + { + $arguments ??= '()'; + + $class = Vite::class; + + return ""; + } + + /** + * Compile the "viteReactRefresh" statements into valid PHP. + * + * @return string + */ + protected function compileViteReactRefresh() + { + $class = Vite::class; + + return "reactRefresh(); ?>"; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesIncludes.php b/src/view/src/Compilers/Concerns/CompilesIncludes.php new file mode 100644 index 000000000..647fe4578 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesIncludes.php @@ -0,0 +1,82 @@ +renderEach{$expression}; ?>"; + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileInclude($expression) + { + $expression = $this->stripParentheses($expression); + + return "make({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>"; + } + + /** + * Compile the include-if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeIf($expression) + { + $expression = $this->stripParentheses($expression); + + return "exists({$expression})) echo \$__env->make({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>"; + } + + /** + * Compile the include-when statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeWhen($expression) + { + $expression = $this->stripParentheses($expression); + + return "renderWhen($expression, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; + } + + /** + * Compile the include-unless statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeUnless($expression) + { + $expression = $this->stripParentheses($expression); + + return "renderUnless($expression, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; + } + + /** + * Compile the include-first statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeFirst($expression) + { + $expression = $this->stripParentheses($expression); + + return "first({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>"; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesInjections.php b/src/view/src/Compilers/Concerns/CompilesInjections.php new file mode 100644 index 000000000..a0d1ccf5e --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesInjections.php @@ -0,0 +1,23 @@ +"; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesJs.php b/src/view/src/Compilers/Concerns/CompilesJs.php new file mode 100644 index 000000000..3104057df --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesJs.php @@ -0,0 +1,22 @@ +toHtml() ?>", + Js::class, $this->stripParentheses($expression) + ); + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesJson.php b/src/view/src/Compilers/Concerns/CompilesJson.php new file mode 100644 index 000000000..cf343e972 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesJson.php @@ -0,0 +1,30 @@ +stripParentheses($expression)); + + $options = isset($parts[1]) ? trim($parts[1]) : $this->encodingOptions; + + $depth = isset($parts[2]) ? trim($parts[2]) : 512; + + return ""; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesLayouts.php b/src/view/src/Compilers/Concerns/CompilesLayouts.php new file mode 100644 index 000000000..697e1a8b1 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesLayouts.php @@ -0,0 +1,133 @@ +stripParentheses($expression); + + $echo = "make({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->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 = "first({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->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 "startSection{$expression}; ?>"; + } + + /** + * Replace the @parent directive to a placeholder. + * + * @return string + */ + protected function compileParent() + { + $escapedLastSection = strtr($this->lastSection, ['\\' => '\\\\', "'" => "\\'"]); + + return ""; + } + + /** + * Compile the yield statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileYield($expression) + { + return "yieldContent{$expression}; ?>"; + } + + /** + * Compile the show statements into valid PHP. + * + * @return string + */ + protected function compileShow() + { + return 'yieldSection(); ?>'; + } + + /** + * Compile the append statements into valid PHP. + * + * @return string + */ + protected function compileAppend() + { + return 'appendSection(); ?>'; + } + + /** + * Compile the overwrite statements into valid PHP. + * + * @return string + */ + protected function compileOverwrite() + { + return 'stopSection(true); ?>'; + } + + /** + * Compile the stop statements into valid PHP. + * + * @return string + */ + protected function compileStop() + { + return 'stopSection(); ?>'; + } + + /** + * Compile the end-section statements into valid PHP. + * + * @return string + */ + protected function compileEndsection() + { + return 'stopSection(); ?>'; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesLoops.php b/src/view/src/Compilers/Concerns/CompilesLoops.php new file mode 100644 index 000000000..ab6d867d0 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesLoops.php @@ -0,0 +1,194 @@ +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 ""; + } + + /** + * Compile the for-else-empty and empty statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEmpty($expression) + { + if ($expression) { + return ""; + } + + $empty = '$__empty_'.$this->forElseCounter--; + + return "popLoop(); \$loop = \$__env->getLastLoop(); if ({$empty}): ?>"; + } + + /** + * Compile the end-for-else statements into valid PHP. + * + * @return string + */ + protected function compileEndforelse() + { + return ''; + } + + /** + * Compile the end-empty statements into valid PHP. + * + * @return string + */ + protected function compileEndEmpty() + { + return ''; + } + + /** + * Compile the for statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileFor($expression) + { + return ""; + } + + /** + * Compile the for-each statements into valid PHP. + * + * @param string|null $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 ""; + } + + /** + * 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 ? '' : ""; + } + + return ''; + } + + /** + * 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 ? '' : ""; + } + + return ''; + } + + /** + * Compile the end-for statements into valid PHP. + * + * @return string + */ + protected function compileEndfor() + { + return ''; + } + + /** + * Compile the end-for-each statements into valid PHP. + * + * @return string + */ + protected function compileEndforeach() + { + return 'popLoop(); $loop = $__env->getLastLoop(); ?>'; + } + + /** + * Compile the while statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileWhile($expression) + { + return ""; + } + + /** + * Compile the end-while statements into valid PHP. + * + * @return string + */ + protected function compileEndwhile() + { + return ''; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesRawPhp.php b/src/view/src/Compilers/Concerns/CompilesRawPhp.php new file mode 100644 index 000000000..41c7edfd5 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesRawPhp.php @@ -0,0 +1,32 @@ +"; + } + + return '@php'; + } + + /** + * Compile the unset statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileUnset($expression) + { + return ""; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesSessions.php b/src/view/src/Compilers/Concerns/CompilesSessions.php new file mode 100644 index 000000000..0c375b406 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesSessions.php @@ -0,0 +1,37 @@ +stripParentheses($expression); + + return 'has($__sessionArgs[0])) : +if (isset($value)) { $__sessionPrevious[] = $value; } +$value = session()->get($__sessionArgs[0]); ?>'; + } + + /** + * Compile the endsession statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndsession($expression) + { + return ''; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesStacks.php b/src/view/src/Compilers/Concerns/CompilesStacks.php new file mode 100644 index 000000000..16ceef37c --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesStacks.php @@ -0,0 +1,117 @@ +yieldPushContent{$expression}; ?>"; + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compilePush($expression) + { + return "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 'hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); +$__env->startPush('.$stack.'); ?>'; + } + + /** + * Compile the end-push statements into valid PHP. + * + * @return string + */ + protected function compileEndpush() + { + return 'stopPush(); ?>'; + } + + /** + * Compile the end-push-once statements into valid PHP. + * + * @return string + */ + protected function compileEndpushOnce() + { + return 'stopPush(); endif; ?>'; + } + + /** + * Compile the prepend statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compilePrepend($expression) + { + return "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 'hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); +$__env->startPrepend('.$stack.'); ?>'; + } + + /** + * Compile the end-prepend statements into valid PHP. + * + * @return string + */ + protected function compileEndprepend() + { + return 'stopPrepend(); ?>'; + } + + /** + * Compile the end-prepend-once statements into valid PHP. + * + * @return string + */ + protected function compileEndprependOnce() + { + return 'stopPrepend(); endif; ?>'; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesStyles.php b/src/view/src/Compilers/Concerns/CompilesStyles.php new file mode 100644 index 000000000..6c715061c --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesStyles.php @@ -0,0 +1,19 @@ +\""; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesTranslations.php b/src/view/src/Compilers/Concerns/CompilesTranslations.php new file mode 100644 index 000000000..7cbafdb93 --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesTranslations.php @@ -0,0 +1,44 @@ +startTranslation(); ?>'; + } elseif ($expression[1] === '[') { + return "startTranslation{$expression}; ?>"; + } + + return "get{$expression}; ?>"; + } + + /** + * Compile the end-lang statements into valid PHP. + * + * @return string + */ + protected function compileEndlang() + { + return 'renderTranslation(); ?>'; + } + + /** + * Compile the choice statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileChoice($expression) + { + return "choice{$expression}; ?>"; + } +} diff --git a/src/view/src/Compilers/Concerns/CompilesUseStatements.php b/src/view/src/Compilers/Concerns/CompilesUseStatements.php new file mode 100644 index 000000000..8218c9fdf --- /dev/null +++ b/src/view/src/Compilers/Concerns/CompilesUseStatements.php @@ -0,0 +1,22 @@ +"; + } +} diff --git a/src/view/src/Component.php b/src/view/src/Component.php new file mode 100644 index 000000000..ae4f760c6 --- /dev/null +++ b/src/view/src/Component.php @@ -0,0 +1,491 @@ + + */ + 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> + */ + protected static $constructorParametersCache = []; + + /** + * The cache of ignored parameter names. + * + * @var array + */ + protected static $ignoredParameterNames = []; + + /** + * 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 + ? (new Collection($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) { + if ($view instanceof ViewContract) { + return $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 ($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.'/'.hash('xxh128', $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] = (new Collection($reflection->getProperties(ReflectionProperty::IS_PUBLIC))) + ->reject(fn (ReflectionProperty $property) => $property->isStatic()) + ->reject(fn (ReflectionProperty $property) => $this->shouldIgnore($property->getName())) + ->map(fn (ReflectionProperty $property) => $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] = (new Collection($reflection->getMethods(ReflectionMethod::IS_PUBLIC))) + ->reject(fn (ReflectionMethod $method) => $this->shouldIgnore($method->getName())) + ->map(fn (ReflectionMethod $method) => $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', + 'resolve', + 'resolveView', + 'shouldRender', + 'view', + 'withName', + 'withAttributes', + 'flushCache', + 'forgetFactory', + 'forgetComponentsResolver', + 'resolveComponentsUsing', + ], $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; + } + + /** + * Get the cached set of anonymous component constructor parameter names to exclude. + * + * @return array + */ + public static function ignoredParameterNames() + { + if (! isset(static::$ignoredParameterNames[static::class])) { + $constructor = (new ReflectionClass( + static::class + ))->getConstructor(); + + if (! $constructor) { + return static::$ignoredParameterNames[static::class] = []; + } + + static::$ignoredParameterNames[static::class] = (new Collection($constructor->getParameters())) + ->map + ->getName() + ->all(); + } + + return static::$ignoredParameterNames[static::class]; + } + + /** + * 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; + } +} diff --git a/src/view/src/ComponentAttributeBag.php b/src/view/src/ComponentAttributeBag.php new file mode 100644 index 000000000..780d93deb --- /dev/null +++ b/src/view/src/ComponentAttributeBag.php @@ -0,0 +1,524 @@ +setAttributes($attributes); + } + + /** + * Get all of the attribute values. + * + * @return array + */ + public function all() + { + return $this->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 array|string $key + * @return bool + */ + public function has($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if (! array_key_exists($value, $this->attributes)) { + return false; + } + } + + return true; + } + + /** + * Determine if any of the keys exist in the attribute array. + * + * @param array|string $key + * @return bool + */ + public function hasAny($key) + { + if (! count($this->attributes)) { + return false; + } + + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if ($this->has($value)) { + return true; + } + } + + return false; + } + + /** + * Determine if a given attribute is missing from the attribute array. + * + * @param string $key + * @return bool + */ + public function missing($key) + { + return ! $this->has($key); + } + + /** + * 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((new Collection($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(static::extractPropNames($keys)); + } + + /** + * Exclude the given attribute from the attribute array. + * + * @param mixed|array $keys + * @return static + */ + public function exceptProps($keys) + { + return $this->except(static::extractPropNames($keys)); + } + + /** + * 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] = (new Collection($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; + } + + /** + * Determine if the attribute bag is empty. + * + * @return bool + */ + public function isEmpty() + { + return trim((string) $this) === ''; + } + + /** + * Determine if the attribute bag is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * 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; + } + + /** + * Extract "prop" names from given keys. + * + * @param array $keys + * @return array + */ + public static function extractPropNames(array $keys) + { + $props = []; + + foreach ($keys as $key => $default) { + $key = is_numeric($key) ? $default : $key; + + $props[] = $key; + $props[] = Str::kebab($key); + } + + return $props; + } + + /** + * 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); + } + + /** + * Convert the object into a JSON serializable form. + * + * @return mixed + */ + public function jsonSerialize(): mixed + { + return $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 === 'x-data' || str_starts_with($key, 'wire:') ? '' : $key; + } + + $string .= ' '.$key.'="'.str_replace('"', '\\"', trim($value)).'"'; + } + + return trim($string); + } +} diff --git a/src/view/src/ComponentSlot.php b/src/view/src/ComponentSlot.php new file mode 100644 index 000000000..f363391cf --- /dev/null +++ b/src/view/src/ComponentSlot.php @@ -0,0 +1,110 @@ +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(); + } + + /** + * Determine if the slot has non-comment content. + * + * @param callable|string|null $callable + * @return bool + */ + public function hasActualContent(callable|string|null $callable = null) + { + if (is_string($callable) && ! function_exists($callable)) { + throw new InvalidArgumentException('Callable does not exist.'); + } + + return filter_var( + $this->contents, + FILTER_CALLBACK, + ['options' => $callable ?? fn ($input) => trim(preg_replace("//", '', $input))] + ) !== ''; + } + + /** + * Get the slot's HTML string. + * + * @return string + */ + public function __toString() + { + return $this->toHtml(); + } +} diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php new file mode 100644 index 000000000..456e6a044 --- /dev/null +++ b/src/view/src/Concerns/ManagesComponents.php @@ -0,0 +1,221 @@ +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 ComponentSlot(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 = []; + } +} diff --git a/src/view/src/Concerns/ManagesEvents.php b/src/view/src/Concerns/ManagesEvents.php new file mode 100644 index 000000000..7ad3c689d --- /dev/null +++ b/src/view/src/Concerns/ManagesEvents.php @@ -0,0 +1,194 @@ +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) + { + if ($this->events->hasListeners($event = 'composing: '.$view->name())) { + $this->events->dispatch($event, [$view]); + } + } + + /** + * Call the creator for a given view. + * + * @param \Illuminate\Contracts\View\View $view + * @return void + */ + public function callCreator(ViewContract $view) + { + if ($this->events->hasListeners($event = 'creating: '.$view->name())) { + $this->events->dispatch($event, [$view]); + } + } +} diff --git a/src/view/src/Concerns/ManagesFragments.php b/src/view/src/Concerns/ManagesFragments.php new file mode 100644 index 000000000..7273da64a --- /dev/null +++ b/src/view/src/Concerns/ManagesFragments.php @@ -0,0 +1,88 @@ +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 = []; + } +} diff --git a/src/view/src/Concerns/ManagesLayouts.php b/src/view/src/Concerns/ManagesLayouts.php new file mode 100644 index 000000000..38cc56c7d --- /dev/null +++ b/src/view/src/Concerns/ManagesLayouts.php @@ -0,0 +1,255 @@ +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-'.hash('xxh128', $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 = []; + } +} diff --git a/src/view/src/Concerns/ManagesLoops.php b/src/view/src/Concerns/ManagesLoops.php new file mode 100644 index 000000000..95f06c825 --- /dev/null +++ b/src/view/src/Concerns/ManagesLoops.php @@ -0,0 +1,96 @@ +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; + } +} diff --git a/src/view/src/Concerns/ManagesStacks.php b/src/view/src/Concerns/ManagesStacks.php new file mode 100644 index 000000000..4e063af1e --- /dev/null +++ b/src/view/src/Concerns/ManagesStacks.php @@ -0,0 +1,179 @@ +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 = []; + } +} diff --git a/src/view/src/Concerns/ManagesTranslations.php b/src/view/src/Concerns/ManagesTranslations.php new file mode 100644 index 000000000..a77fc26ac --- /dev/null +++ b/src/view/src/Concerns/ManagesTranslations.php @@ -0,0 +1,38 @@ +translationReplacements = $replacements; + } + + /** + * Render the current translation. + * + * @return string + */ + public function renderTranslation() + { + return $this->container->make('translator')->get( + trim(ob_get_clean()), $this->translationReplacements + ); + } +} diff --git a/src/view/src/DynamicComponent.php b/src/view/src/DynamicComponent.php new file mode 100644 index 000000000..0e0edd1f6 --- /dev/null +++ b/src/view/src/DynamicComponent.php @@ -0,0 +1,174 @@ +component = $component; + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\Contracts\View\View|string + */ + public function render() + { + $template = <<<'EOF' +getAttributes()))->mapWithKeys(function ($value, $key) { return [Illuminate\Support\Str::camel(str_replace([':', '.'], ' ', $key)) => $value]; })->all(), EXTR_SKIP); ?> +{{ props }} + +{{ slots }} +{{ defaultSlot }} + +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('\',\'', (new Collection($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 (new Collection($bindings)) + ->map(fn ($key) => ':'.$key.'="$'.Str::camel(str_replace([':', '.'], ' ', $key)).'"') + ->implode(' '); + } + + /** + * Compile the slots for the component. + * + * @param array $slots + * @return string + */ + protected function compileSlots(array $slots) + { + return (new Collection($slots)) + ->map(fn ($slot, $name) => $name === '__default' ? null : 'attributes).'>{{ $'.$name.' }}') + ->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; + } +} diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php new file mode 100755 index 000000000..a14903421 --- /dev/null +++ b/src/view/src/Engines/CompilerEngine.php @@ -0,0 +1,150 @@ + + */ + 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::of($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) + { + if ($e instanceof HttpException || + $e instanceof HttpResponseException || + $e instanceof RecordNotFoundException || + $e instanceof RecordsNotFoundException) { + parent::handleViewException($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 = []; + } +} diff --git a/src/view/src/Engines/Engine.php b/src/view/src/Engines/Engine.php new file mode 100755 index 000000000..bf5c748d8 --- /dev/null +++ b/src/view/src/Engines/Engine.php @@ -0,0 +1,23 @@ +lastRendered; + } +} diff --git a/src/view/src/Engines/EngineResolver.php b/src/view/src/Engines/EngineResolver.php new file mode 100755 index 000000000..674040770 --- /dev/null +++ b/src/view/src/Engines/EngineResolver.php @@ -0,0 +1,71 @@ +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]); + } +} diff --git a/src/view/src/Engines/FileEngine.php b/src/view/src/Engines/FileEngine.php new file mode 100644 index 000000000..992f6758d --- /dev/null +++ b/src/view/src/Engines/FileEngine.php @@ -0,0 +1,39 @@ +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); + } +} diff --git a/src/view/src/Engines/PhpEngine.php b/src/view/src/Engines/PhpEngine.php new file mode 100755 index 000000000..13525aeea --- /dev/null +++ b/src/view/src/Engines/PhpEngine.php @@ -0,0 +1,83 @@ +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; + } +} diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php new file mode 100755 index 000000000..e5efe067e --- /dev/null +++ b/src/view/src/Factory.php @@ -0,0 +1,645 @@ + '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 = []; + + /** + * The cached array of engines for paths. + * + * @var array + */ + protected $pathEngineCache = []; + + /** + * The cache of normalized names for views. + * + * @var array + */ + protected $normalizedNameCache = []; + + /** + * 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 $this->normalizedNameCache[$name] ??= 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) { + 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 (isset($this->pathEngineCache[$path])) { + return $this->engines->resolve($this->pathEngineCache[$path]); + } + + if (! $extension = $this->getExtension($path)) { + throw new InvalidArgumentException("Unrecognized extension in file: {$path}."); + } + + return $this->engines->resolve( + $this->pathEngineCache[$path] = $this->extensions[$extension] + ); + } + + /** + * 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); + } + + /** + * Prepend a location to the array of view locations. + * + * @param string $location + * @return void + */ + public function prependLocation($location) + { + $this->finder->prependLocation($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); + + $this->pathEngineCache = []; + } + + /** + * 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; + } +} diff --git a/src/view/src/FileViewFinder.php b/src/view/src/FileViewFinder.php new file mode 100755 index 000000000..c2f49bd68 --- /dev/null +++ b/src/view/src/FileViewFinder.php @@ -0,0 +1,332 @@ +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) { + $viewPath = $path.'/'.$file; + + if (strlen($viewPath) < (PHP_MAXPATHLEN - 1) && $this->files->exists($viewPath)) { + 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; + } +} diff --git a/src/view/src/InvokableComponentVariable.php b/src/view/src/InvokableComponentVariable.php new file mode 100644 index 000000000..d1ea11768 --- /dev/null +++ b/src/view/src/InvokableComponentVariable.php @@ -0,0 +1,97 @@ +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 iterator 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 string + */ + public function __toString() + { + return (string) $this->__invoke(); + } +} diff --git a/src/view/src/LICENSE.md b/src/view/src/LICENSE.md new file mode 100644 index 000000000..79810c848 --- /dev/null +++ b/src/view/src/LICENSE.md @@ -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. diff --git a/src/view/src/Middleware/ShareErrorsFromSession.php b/src/view/src/Middleware/ShareErrorsFromSession.php new file mode 100644 index 000000000..64015d586 --- /dev/null +++ b/src/view/src/Middleware/ShareErrorsFromSession.php @@ -0,0 +1,51 @@ +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); + } +} diff --git a/src/view/src/View.php b/src/view/src/View.php new file mode 100755 index 000000000..ac165842a --- /dev/null +++ b/src/view/src/View.php @@ -0,0 +1,508 @@ +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 or return all fragments. + * + * @param array|null $fragments + * @return string + */ + public function fragments(?array $fragments = null) + { + return is_null($fragments) + ? $this->allFragments() + : (new Collection($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|null $fragments + * @return string + */ + public function fragmentsIf($boolean, ?array $fragments = null) + { + if (value($boolean)) { + return $this->fragments($fragments); + } + + return $this->render(); + } + + /** + * Get all fragments as a single string. + * + * @return string + */ + protected function allFragments() + { + return (new Collection($this->render(fn () => $this->factory->getFragments())))->implode(''); + } + + /** + * 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|string $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(); + } +} diff --git a/src/view/src/ViewException.php b/src/view/src/ViewException.php new file mode 100644 index 000000000..77f9ee65b --- /dev/null +++ b/src/view/src/ViewException.php @@ -0,0 +1,41 @@ +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); + } + } +} diff --git a/src/view/src/ViewFinderInterface.php b/src/view/src/ViewFinderInterface.php new file mode 100755 index 000000000..7b8a849e0 --- /dev/null +++ b/src/view/src/ViewFinderInterface.php @@ -0,0 +1,71 @@ +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(Container::getInstance()->make('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(Container::getInstance()->make('files')); + }); + } + + /** + * Register the Blade engine implementation. + * + * @param \Illuminate\View\Engines\EngineResolver $resolver + * @return void + */ + public function registerBladeEngine($resolver) + { + $resolver->register('blade', function () { + $app = Container::getInstance(); + + $compiler = new CompilerEngine( + $app->make('blade.compiler'), + $app->make('files'), + ); + + $app->terminating(static function () use ($compiler) { + $compiler->forgetCompiledOrNotExpired(); + }); + + return $compiler; + }); + } +} diff --git a/src/view/src/composer.json b/src/view/src/composer.json new file mode 100644 index 000000000..41472bc1c --- /dev/null +++ b/src/view/src/composer.json @@ -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.2", + "ext-tokenizer": "*", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/events": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0" + }, + "autoload": { + "psr-4": { + "Illuminate\\View\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev" +} From d78e603a4f7f694bb90efbf0438a6478eae9a7d7 Mon Sep 17 00:00:00 2001 From: Yu Shing Date: Wed, 15 Oct 2025 10:32:00 +0800 Subject: [PATCH 02/21] feat: change namespace from Illuminate to Hypervel --- src/view/src/AnonymousComponent.php | 2 +- src/view/src/AppendableAttributeValue.php | 2 +- src/view/src/Compilers/BladeCompiler.php | 24 +++++++++---------- src/view/src/Compilers/Compiler.php | 6 ++--- src/view/src/Compilers/CompilerInterface.php | 2 +- .../src/Compilers/ComponentTagCompiler.php | 22 ++++++++--------- .../Concerns/CompilesAuthorizations.php | 2 +- .../Compilers/Concerns/CompilesClasses.php | 2 +- .../Compilers/Concerns/CompilesComments.php | 2 +- .../Compilers/Concerns/CompilesComponents.php | 10 ++++---- .../Concerns/CompilesConditionals.php | 4 ++-- .../src/Compilers/Concerns/CompilesEchos.php | 4 ++-- .../src/Compilers/Concerns/CompilesErrors.php | 2 +- .../Compilers/Concerns/CompilesFragments.php | 2 +- .../Compilers/Concerns/CompilesHelpers.php | 4 ++-- .../Compilers/Concerns/CompilesIncludes.php | 2 +- .../Compilers/Concerns/CompilesInjections.php | 2 +- .../src/Compilers/Concerns/CompilesJs.php | 4 ++-- .../src/Compilers/Concerns/CompilesJson.php | 2 +- .../Compilers/Concerns/CompilesLayouts.php | 2 +- .../src/Compilers/Concerns/CompilesLoops.php | 4 ++-- .../src/Compilers/Concerns/CompilesRawPhp.php | 2 +- .../Compilers/Concerns/CompilesSessions.php | 2 +- .../src/Compilers/Concerns/CompilesStacks.php | 4 ++-- .../src/Compilers/Concerns/CompilesStyles.php | 2 +- .../Concerns/CompilesTranslations.php | 2 +- .../Concerns/CompilesUseStatements.php | 2 +- src/view/src/Component.php | 10 ++++---- src/view/src/ComponentAttributeBag.php | 16 ++++++------- src/view/src/ComponentSlot.php | 4 ++-- src/view/src/Concerns/ManagesComponents.php | 10 ++++---- src/view/src/Concerns/ManagesEvents.php | 6 ++--- src/view/src/Concerns/ManagesFragments.php | 2 +- src/view/src/Concerns/ManagesLayouts.php | 6 ++--- src/view/src/Concerns/ManagesLoops.php | 6 ++--- src/view/src/Concerns/ManagesStacks.php | 2 +- src/view/src/Concerns/ManagesTranslations.php | 2 +- src/view/src/DynamicComponent.php | 10 ++++---- src/view/src/Engines/CompilerEngine.php | 18 +++++++------- src/view/src/Engines/Engine.php | 2 +- src/view/src/Engines/EngineResolver.php | 2 +- src/view/src/Engines/FileEngine.php | 6 ++--- src/view/src/Engines/PhpEngine.php | 6 ++--- src/view/src/Factory.php | 18 +++++++------- src/view/src/FileViewFinder.php | 4 ++-- src/view/src/InvokableComponentVariable.php | 6 ++--- .../src/Middleware/ShareErrorsFromSession.php | 6 ++--- src/view/src/View.php | 24 +++++++++---------- src/view/src/ViewException.php | 6 ++--- src/view/src/ViewFinderInterface.php | 2 +- src/view/src/ViewName.php | 2 +- src/view/src/ViewServiceProvider.php | 18 +++++++------- 52 files changed, 157 insertions(+), 157 deletions(-) diff --git a/src/view/src/AnonymousComponent.php b/src/view/src/AnonymousComponent.php index eba643656..966ed4b16 100644 --- a/src/view/src/AnonymousComponent.php +++ b/src/view/src/AnonymousComponent.php @@ -1,6 +1,6 @@ Date: Wed, 15 Oct 2025 11:23:25 +0800 Subject: [PATCH 03/21] feat: add type hints --- src/view/src/AnonymousComponent.php | 10 +- src/view/src/AppendableAttributeValue.php | 6 +- src/view/src/Compilers/BladeCompiler.php | 16 +- src/view/src/Compilers/Compiler.php | 30 ++-- .../src/Compilers/ComponentTagCompiler.php | 18 +-- .../Concerns/CompilesAuthorizations.php | 30 ++-- .../Compilers/Concerns/CompilesClasses.php | 6 +- .../Compilers/Concerns/CompilesComments.php | 2 +- .../Compilers/Concerns/CompilesComponents.php | 34 ++-- .../Concerns/CompilesConditionals.php | 74 ++++----- .../src/Compilers/Concerns/CompilesEchos.php | 28 ++-- .../src/Compilers/Concerns/CompilesErrors.php | 4 +- .../Compilers/Concerns/CompilesFragments.php | 8 +- .../Compilers/Concerns/CompilesHelpers.php | 12 +- .../Compilers/Concerns/CompilesIncludes.php | 12 +- .../Compilers/Concerns/CompilesInjections.php | 2 +- .../src/Compilers/Concerns/CompilesJs.php | 2 +- .../src/Compilers/Concerns/CompilesJson.php | 4 +- .../Compilers/Concerns/CompilesLayouts.php | 26 ++-- .../src/Compilers/Concerns/CompilesLoops.php | 30 ++-- .../src/Compilers/Concerns/CompilesRawPhp.php | 6 +- .../Compilers/Concerns/CompilesSessions.php | 4 +- .../src/Compilers/Concerns/CompilesStacks.php | 18 +-- .../src/Compilers/Concerns/CompilesStyles.php | 6 +- .../Concerns/CompilesTranslations.php | 6 +- .../Concerns/CompilesUseStatements.php | 2 +- src/view/src/Component.php | 84 +++++----- src/view/src/ComponentAttributeBag.php | 72 ++++----- src/view/src/ComponentSlot.php | 20 +-- src/view/src/Concerns/ManagesComponents.php | 30 ++-- src/view/src/Concerns/ManagesEvents.php | 26 ++-- src/view/src/Concerns/ManagesFragments.php | 14 +- src/view/src/Concerns/ManagesLayouts.php | 36 ++--- src/view/src/Concerns/ManagesLoops.php | 12 +- src/view/src/Concerns/ManagesStacks.php | 22 +-- src/view/src/Concerns/ManagesTranslations.php | 6 +- src/view/src/DynamicComponent.php | 30 ++-- src/view/src/Engines/CompilerEngine.php | 24 +-- src/view/src/Engines/Engine.php | 4 +- src/view/src/Engines/EngineResolver.php | 12 +- src/view/src/Engines/FileEngine.php | 10 +- src/view/src/Engines/PhpEngine.php | 14 +- src/view/src/Factory.php | 145 +++++++++--------- src/view/src/FileViewFinder.php | 127 ++++----------- src/view/src/InvokableComponentVariable.php | 16 +- .../src/Middleware/ShareErrorsFromSession.php | 10 +- src/view/src/View.php | 46 +++--- src/view/src/ViewException.php | 8 +- src/view/src/ViewFinderInterface.php | 41 +---- src/view/src/ViewName.php | 2 +- src/view/src/ViewServiceProvider.php | 32 ++-- 51 files changed, 572 insertions(+), 667 deletions(-) diff --git a/src/view/src/AnonymousComponent.php b/src/view/src/AnonymousComponent.php index 966ed4b16..cd287883f 100644 --- a/src/view/src/AnonymousComponent.php +++ b/src/view/src/AnonymousComponent.php @@ -9,14 +9,14 @@ class AnonymousComponent extends Component * * @var string */ - protected $view; + protected string $view; /** * The component data. * * @var array */ - protected $data = []; + protected array $data = []; /** * Create a new anonymous component instance. @@ -25,7 +25,7 @@ class AnonymousComponent extends Component * @param array $data * @return void */ - public function __construct($view, $data) + public function __construct(string $view, array $data) { $this->view = $view; $this->data = $data; @@ -36,7 +36,7 @@ public function __construct($view, $data) * * @return string */ - public function render() + public function render(): string { return $this->view; } @@ -46,7 +46,7 @@ public function render() * * @return array */ - public function data() + public function data(): array { $this->attributes = $this->attributes ?: $this->newAttributeBag(); diff --git a/src/view/src/AppendableAttributeValue.php b/src/view/src/AppendableAttributeValue.php index e828e4cf0..ea7646bad 100644 --- a/src/view/src/AppendableAttributeValue.php +++ b/src/view/src/AppendableAttributeValue.php @@ -11,7 +11,7 @@ class AppendableAttributeValue implements Stringable * * @var mixed */ - public $value; + public mixed $value; /** * Create a new appendable attribute value. @@ -19,7 +19,7 @@ class AppendableAttributeValue implements Stringable * @param mixed $value * @return void */ - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } @@ -29,7 +29,7 @@ public function __construct($value) * * @return string */ - public function __toString() + public function __toString(): string { return (string) $this->value; } diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php index 90d924fe6..96f6526a3 100644 --- a/src/view/src/Compilers/BladeCompiler.php +++ b/src/view/src/Compilers/BladeCompiler.php @@ -218,7 +218,7 @@ protected function appendFilePath($contents) * Get the open and closing PHP tag tokens from the given string. * * @param string $contents - * @return \Illuminate\Support\Collection + * @return \Hypervel\Support\Collection */ protected function getOpenAndClosingPhpTokens($contents) { @@ -344,7 +344,7 @@ public function render() /** * Render a component instance to HTML. * - * @param \Illuminate\View\Component $component + * @param \Hypervel\View\Component $component * @return string */ public static function renderComponent(Component $component) @@ -713,20 +713,20 @@ public function if($name, callable $callback) $this->directive($name, function ($expression) use ($name) { return $expression !== '' - ? "" - : ""; + ? "" + : ""; }); $this->directive('unless'.$name, function ($expression) use ($name) { return $expression !== '' - ? "" - : ""; + ? "" + : ""; }); $this->directive('else'.$name, function ($expression) use ($name) { return $expression !== '' - ? "" - : ""; + ? "" + : ""; }); $this->directive('end'.$name, function () { diff --git a/src/view/src/Compilers/Compiler.php b/src/view/src/Compilers/Compiler.php index 6f90d61e4..967554196 100755 --- a/src/view/src/Compilers/Compiler.php +++ b/src/view/src/Compilers/Compiler.php @@ -12,42 +12,42 @@ abstract class Compiler /** * The filesystem instance. * - * @var \Illuminate\Filesystem\Filesystem + * @var \Hypervel\Filesystem\Filesystem */ - protected $files; + protected Filesystem $files; /** * The cache path for the compiled views. * * @var string */ - protected $cachePath; + protected string $cachePath; /** * The base path that should be removed from paths before hashing. * * @var string */ - protected $basePath; + protected string $basePath; /** * Determines if compiled views should be cached. * * @var bool */ - protected $shouldCache; + protected bool $shouldCache; /** * The compiled view file extension. * * @var string */ - protected $compiledExtension = 'php'; + protected string $compiledExtension = 'php'; /** * Create a new compiler instance. * - * @param \Illuminate\Filesystem\Filesystem $files + * @param \Hypervel\Filesystem\Filesystem $files * @param string $cachePath * @param string $basePath * @param bool $shouldCache @@ -58,11 +58,11 @@ abstract class Compiler */ public function __construct( Filesystem $files, - $cachePath, - $basePath = '', - $shouldCache = true, - $compiledExtension = 'php', - ) { + string $cachePath, + string $basePath = '', + bool $shouldCache = true, + string $compiledExtension = 'php', + ): void { if (! $cachePath) { throw new InvalidArgumentException('Please provide a valid cache path.'); } @@ -80,7 +80,7 @@ public function __construct( * @param string $path * @return string */ - public function getCompiledPath($path) + public function getCompiledPath(string $path): string { return $this->cachePath.'/'.hash('xxh128', 'v2'.Str::after($path, $this->basePath)).'.'.$this->compiledExtension; } @@ -93,7 +93,7 @@ public function getCompiledPath($path) * * @throws \ErrorException */ - public function isExpired($path) + public function isExpired(string $path): bool { if (! $this->shouldCache) { return true; @@ -126,7 +126,7 @@ public function isExpired($path) * @param string $path * @return void */ - protected function ensureCompiledDirectoryExists($path) + protected function ensureCompiledDirectoryExists(string $path): void { if (! $this->files->exists(dirname($path))) { $this->files->makeDirectory(dirname($path), 0777, true, true); diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php index 33b0ad88f..0d3d42f09 100644 --- a/src/view/src/Compilers/ComponentTagCompiler.php +++ b/src/view/src/Compilers/ComponentTagCompiler.php @@ -23,7 +23,7 @@ class ComponentTagCompiler /** * The Blade compiler instance. * - * @var \Illuminate\View\Compilers\BladeCompiler + * @var \Hypervel\View\Compilers\BladeCompiler */ protected $blade; @@ -53,7 +53,7 @@ class ComponentTagCompiler * * @param array $aliases * @param array $namespaces - * @param \Illuminate\View\Compilers\BladeCompiler|null $blade + * @param \Hypervel\View\Compilers\BladeCompiler|null $blade * @return void */ public function __construct(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null) @@ -246,7 +246,7 @@ protected function componentString(string $component, array $attributes) // 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}')" + ? "\$__env->getContainer()->make(Hypervel\\View\\Factory::class)->make('{$component}')" : "'$class'"; $parameters = [ @@ -260,7 +260,7 @@ protected function componentString(string $component, array $attributes) } return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).']) - + except(\\'.$class.'::ignoredParameterNames()); ?> withAttributes(['.$this->attributesToString($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>'; @@ -321,7 +321,7 @@ public function componentClass(string $component) /** * Attempt to find an anonymous component using the registered anonymous component paths. * - * @param \Illuminate\Contracts\View\Factory $viewFactory + * @param \Hypervel\Contracts\View\Factory $viewFactory * @param string $component * @return string|null */ @@ -357,7 +357,7 @@ protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, strin /** * Attempt to find an anonymous component using the registered anonymous component namespaces. * - * @param \Illuminate\Contracts\View\Factory $viewFactory + * @param \Hypervel\Contracts\View\Factory $viewFactory * @param string $component * @return string|null */ @@ -691,7 +691,7 @@ protected function parseComponentTagClassStatements(string $attributeString) if ($match[1] === 'class') { $match[2] = str_replace('"', "'", $match[2]); - return ":class=\"\Illuminate\Support\Arr::toCssClasses{$match[2]}\""; + return ":class=\"\Hypervel\Support\Arr::toCssClasses{$match[2]}\""; } return $match[0]; @@ -712,7 +712,7 @@ protected function parseComponentTagStyleStatements(string $attributeString) if ($match[1] === 'style') { $match[2] = str_replace('"', "'", $match[2]); - return ":style=\"\Illuminate\Support\Arr::toCssStyles{$match[2]}\""; + return ":style=\"\Hypervel\Support\Arr::toCssStyles{$match[2]}\""; } return $match[0]; @@ -789,7 +789,7 @@ protected function attributesToString(array $attributes, $escapeBound = true) return (new Collection($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}' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute({$value})" : "'{$attribute}' => {$value}"; }) ->implode(','); diff --git a/src/view/src/Compilers/Concerns/CompilesAuthorizations.php b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php index 0460448c0..39eecfc63 100644 --- a/src/view/src/Compilers/Concerns/CompilesAuthorizations.php +++ b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php @@ -10,9 +10,9 @@ trait CompilesAuthorizations * @param string $expression * @return string */ - protected function compileCan($expression) + protected function compileCan(string $expression): string { - return "check{$expression}): ?>"; + return "check{$expression}): ?>"; } /** @@ -21,9 +21,9 @@ protected function compileCan($expression) * @param string $expression * @return string */ - protected function compileCannot($expression) + protected function compileCannot(string $expression): string { - return "denies{$expression}): ?>"; + return "denies{$expression}): ?>"; } /** @@ -32,9 +32,9 @@ protected function compileCannot($expression) * @param string $expression * @return string */ - protected function compileCanany($expression) + protected function compileCanany(string $expression): string { - return "any{$expression}): ?>"; + return "any{$expression}): ?>"; } /** @@ -43,9 +43,9 @@ protected function compileCanany($expression) * @param string $expression * @return string */ - protected function compileElsecan($expression) + protected function compileElsecan(string $expression): string { - return "check{$expression}): ?>"; + return "check{$expression}): ?>"; } /** @@ -54,9 +54,9 @@ protected function compileElsecan($expression) * @param string $expression * @return string */ - protected function compileElsecannot($expression) + protected function compileElsecannot(string $expression): string { - return "denies{$expression}): ?>"; + return "denies{$expression}): ?>"; } /** @@ -65,9 +65,9 @@ protected function compileElsecannot($expression) * @param string $expression * @return string */ - protected function compileElsecanany($expression) + protected function compileElsecanany(string $expression): string { - return "any{$expression}): ?>"; + return "any{$expression}): ?>"; } /** @@ -75,7 +75,7 @@ protected function compileElsecanany($expression) * * @return string */ - protected function compileEndcan() + protected function compileEndcan(): string { return ''; } @@ -85,7 +85,7 @@ protected function compileEndcan() * * @return string */ - protected function compileEndcannot() + protected function compileEndcannot(): string { return ''; } @@ -95,7 +95,7 @@ protected function compileEndcannot() * * @return string */ - protected function compileEndcanany() + protected function compileEndcanany(): string { return ''; } diff --git a/src/view/src/Compilers/Concerns/CompilesClasses.php b/src/view/src/Compilers/Concerns/CompilesClasses.php index 4c925935f..fd9d677fa 100644 --- a/src/view/src/Compilers/Concerns/CompilesClasses.php +++ b/src/view/src/Compilers/Concerns/CompilesClasses.php @@ -7,13 +7,13 @@ trait CompilesClasses /** * Compile the conditional class statement into valid PHP. * - * @param string $expression + * @param string|null $expression * @return string */ - protected function compileClass($expression) + protected function compileClass(?string $expression): string { $expression = is_null($expression) ? '([])' : $expression; - return "class=\"\""; + return "class=\"\""; } } diff --git a/src/view/src/Compilers/Concerns/CompilesComments.php b/src/view/src/Compilers/Concerns/CompilesComments.php index a7aa67bbf..42674e22a 100644 --- a/src/view/src/Compilers/Concerns/CompilesComments.php +++ b/src/view/src/Compilers/Concerns/CompilesComments.php @@ -10,7 +10,7 @@ trait CompilesComments * @param string $value * @return string */ - protected function compileComments($value) + protected function compileComments(string $value): string { $pattern = sprintf('/%s--(.*?)--%s/s', $this->contentTags[0], $this->contentTags[1]); diff --git a/src/view/src/Compilers/Concerns/CompilesComponents.php b/src/view/src/Compilers/Concerns/CompilesComponents.php index 70256fa47..7f90a2c99 100644 --- a/src/view/src/Compilers/Concerns/CompilesComponents.php +++ b/src/view/src/Compilers/Concerns/CompilesComponents.php @@ -12,9 +12,9 @@ trait CompilesComponents /** * The component name hash stack. * - * @var array + * @var array */ - protected static $componentHashStack = []; + protected static array $componentHashStack = []; /** * Compile the component statements into valid PHP. @@ -22,7 +22,7 @@ trait CompilesComponents * @param string $expression * @return string */ - protected function compileComponent($expression) + protected function compileComponent(string $expression): string { [$component, $alias, $data] = str_contains($expression, ',') ? array_map('trim', explode(',', trim($expression, '()'), 3)) + ['', '', ''] @@ -63,12 +63,12 @@ public static function newComponentHash(string $component) * @param string $hash * @return string */ - public static function compileClassComponentOpening(string $component, string $alias, string $data, string $hash) + public static function compileClassComponentOpening(string $component, string $alias, string $data, string $hash): string { return implode("\n", [ '', '', - 'all() : [])); ?>', + 'all() : [])); ?>', 'withName('.$alias.'); ?>', 'shouldRender()): ?>', 'startComponent($component->resolveView(), $component->data()); ?>', @@ -80,7 +80,7 @@ public static function compileClassComponentOpening(string $component, string $a * * @return string */ - protected function compileEndComponent() + protected function compileEndComponent(): string { return 'renderComponent(); ?>'; } @@ -90,7 +90,7 @@ protected function compileEndComponent() * * @return string */ - public function compileEndComponentClass() + public function compileEndComponentClass(): string { $hash = array_pop(static::$componentHashStack); @@ -113,7 +113,7 @@ public function compileEndComponentClass() * @param string $expression * @return string */ - protected function compileSlot($expression) + protected function compileSlot(string $expression): string { return "slot{$expression}; ?>"; } @@ -123,7 +123,7 @@ protected function compileSlot($expression) * * @return string */ - protected function compileEndSlot() + protected function compileEndSlot(): string { return 'endSlot(); ?>'; } @@ -134,7 +134,7 @@ protected function compileEndSlot() * @param string $expression * @return string */ - protected function compileComponentFirst($expression) + protected function compileComponentFirst(string $expression): string { return "startComponentFirst{$expression}; ?>"; } @@ -144,7 +144,7 @@ protected function compileComponentFirst($expression) * * @return string */ - protected function compileEndComponentFirst() + protected function compileEndComponentFirst(): string { return $this->compileEndComponent(); } @@ -155,12 +155,12 @@ protected function compileEndComponentFirst() * @param string $expression * @return string */ - protected function compileProps($expression) + protected function compileProps(string $expression): string { - return "all() as \$__key => \$__value) { if (in_array(\$__key, \$__propNames)) { @@ -170,7 +170,7 @@ protected function compileProps($expression) } } -\$attributes = new \Illuminate\View\ComponentAttributeBag(\$__newAttributes); +\$attributes = new \Hypervel\View\ComponentAttributeBag(\$__newAttributes); unset(\$__propNames); unset(\$__newAttributes); @@ -194,7 +194,7 @@ protected function compileProps($expression) * @param string $expression * @return string */ - protected function compileAware($expression) + protected function compileAware(string $expression): string { return " \$__value) { \$__consumeVariable = is_string(\$__key) ? \$__key : \$__value; @@ -208,7 +208,7 @@ protected function compileAware($expression) * @param mixed $value * @return mixed */ - public static function sanitizeComponentAttribute($value) + public static function sanitizeComponentAttribute(mixed $value): mixed { if ($value instanceof CanBeEscapedWhenCastToString) { return $value->escapeWhenCastingToString(); diff --git a/src/view/src/Compilers/Concerns/CompilesConditionals.php b/src/view/src/Compilers/Concerns/CompilesConditionals.php index f8d490399..6b2831cd8 100644 --- a/src/view/src/Compilers/Concerns/CompilesConditionals.php +++ b/src/view/src/Compilers/Concerns/CompilesConditionals.php @@ -11,7 +11,7 @@ trait CompilesConditionals * * @var bool */ - protected $firstCaseInSwitch = true; + protected bool $firstCaseInSwitch = true; /** * Compile the if-auth statements into valid PHP. @@ -19,7 +19,7 @@ trait CompilesConditionals * @param string|null $guard * @return string */ - protected function compileAuth($guard = null) + protected function compileAuth(?string $guard = null): string { $guard = is_null($guard) ? '()' : $guard; @@ -32,7 +32,7 @@ protected function compileAuth($guard = null) * @param string|null $guard * @return string */ - protected function compileElseAuth($guard = null) + protected function compileElseAuth(?string $guard = null): string { $guard = is_null($guard) ? '()' : $guard; @@ -44,7 +44,7 @@ protected function compileElseAuth($guard = null) * * @return string */ - protected function compileEndAuth() + protected function compileEndAuth(): string { return ''; } @@ -55,7 +55,7 @@ protected function compileEndAuth() * @param string $environments * @return string */ - protected function compileEnv($environments) + protected function compileEnv(string $environments): string { return "environment{$environments}): ?>"; } @@ -65,7 +65,7 @@ protected function compileEnv($environments) * * @return string */ - protected function compileEndEnv() + protected function compileEndEnv(): string { return ''; } @@ -75,7 +75,7 @@ protected function compileEndEnv() * * @return string */ - protected function compileProduction() + protected function compileProduction(): string { return "environment('production')): ?>"; } @@ -85,7 +85,7 @@ protected function compileProduction() * * @return string */ - protected function compileEndProduction() + protected function compileEndProduction(): string { return ''; } @@ -96,7 +96,7 @@ protected function compileEndProduction() * @param string|null $guard * @return string */ - protected function compileGuest($guard = null) + protected function compileGuest(?string $guard = null): string { $guard = is_null($guard) ? '()' : $guard; @@ -109,7 +109,7 @@ protected function compileGuest($guard = null) * @param string|null $guard * @return string */ - protected function compileElseGuest($guard = null) + protected function compileElseGuest(?string $guard = null): string { $guard = is_null($guard) ? '()' : $guard; @@ -121,7 +121,7 @@ protected function compileElseGuest($guard = null) * * @return string */ - protected function compileEndGuest() + protected function compileEndGuest(): string { return ''; } @@ -132,7 +132,7 @@ protected function compileEndGuest() * @param string $expression * @return string */ - protected function compileHasSection($expression) + protected function compileHasSection(string $expression): string { return "yieldContent{$expression}))): ?>"; } @@ -143,7 +143,7 @@ protected function compileHasSection($expression) * @param string $expression * @return string */ - protected function compileSectionMissing($expression) + protected function compileSectionMissing(string $expression): string { return "yieldContent{$expression}))): ?>"; } @@ -154,7 +154,7 @@ protected function compileSectionMissing($expression) * @param string $expression * @return string */ - protected function compileIf($expression) + protected function compileIf(string $expression): string { return ""; } @@ -165,7 +165,7 @@ protected function compileIf($expression) * @param string $expression * @return string */ - protected function compileUnless($expression) + protected function compileUnless(string $expression): string { return ""; } @@ -176,7 +176,7 @@ protected function compileUnless($expression) * @param string $expression * @return string */ - protected function compileElseif($expression) + protected function compileElseif(string $expression): string { return ""; } @@ -186,7 +186,7 @@ protected function compileElseif($expression) * * @return string */ - protected function compileElse() + protected function compileElse(): string { return ''; } @@ -196,7 +196,7 @@ protected function compileElse() * * @return string */ - protected function compileEndif() + protected function compileEndif(): string { return ''; } @@ -206,7 +206,7 @@ protected function compileEndif() * * @return string */ - protected function compileEndunless() + protected function compileEndunless(): string { return ''; } @@ -217,7 +217,7 @@ protected function compileEndunless() * @param string $expression * @return string */ - protected function compileIsset($expression) + protected function compileIsset(string $expression): string { return ""; } @@ -227,7 +227,7 @@ protected function compileIsset($expression) * * @return string */ - protected function compileEndIsset() + protected function compileEndIsset(): string { return ''; } @@ -238,7 +238,7 @@ protected function compileEndIsset() * @param string $expression * @return string */ - protected function compileSwitch($expression) + protected function compileSwitch(string $expression): string { $this->firstCaseInSwitch = true; @@ -251,7 +251,7 @@ protected function compileSwitch($expression) * @param string $expression * @return string */ - protected function compileCase($expression) + protected function compileCase(string $expression): string { if ($this->firstCaseInSwitch) { $this->firstCaseInSwitch = false; @@ -267,7 +267,7 @@ protected function compileCase($expression) * * @return string */ - protected function compileDefault() + protected function compileDefault(): string { return ''; } @@ -277,7 +277,7 @@ protected function compileDefault() * * @return string */ - protected function compileEndSwitch() + protected function compileEndSwitch(): string { return ''; } @@ -288,7 +288,7 @@ protected function compileEndSwitch() * @param string|null $id * @return string */ - protected function compileOnce($id = null) + protected function compileOnce(?string $id = null): string { $id = $id ? $this->stripParentheses($id) : "'".(string) Str::uuid()."'"; @@ -300,7 +300,7 @@ protected function compileOnce($id = null) * * @return string */ - public function compileEndOnce() + public function compileEndOnce(): string { return ''; } @@ -311,7 +311,7 @@ public function compileEndOnce() * @param bool $condition * @return string */ - protected function compileBool($condition) + protected function compileBool(bool $condition): string { return ""; } @@ -322,7 +322,7 @@ protected function compileBool($condition) * @param string $condition * @return string */ - protected function compileChecked($condition) + protected function compileChecked(string $condition): string { return ""; } @@ -333,7 +333,7 @@ protected function compileChecked($condition) * @param string $condition * @return string */ - protected function compileDisabled($condition) + protected function compileDisabled(string $condition): string { return ""; } @@ -344,7 +344,7 @@ protected function compileDisabled($condition) * @param string $condition * @return string */ - protected function compileRequired($condition) + protected function compileRequired(string $condition): string { return ""; } @@ -355,7 +355,7 @@ protected function compileRequired($condition) * @param string $condition * @return string */ - protected function compileReadonly($condition) + protected function compileReadonly(string $condition): string { return ""; } @@ -366,7 +366,7 @@ protected function compileReadonly($condition) * @param string $condition * @return string */ - protected function compileSelected($condition) + protected function compileSelected(string $condition): string { return ""; } @@ -377,7 +377,7 @@ protected function compileSelected($condition) * @param string $expression * @return string */ - protected function compilePushIf($expression) + protected function compilePushIf(string $expression): string { $parts = explode(',', $this->stripParentheses($expression), 2); @@ -390,7 +390,7 @@ protected function compilePushIf($expression) * @param string $expression * @return string */ - protected function compileElsePushIf($expression) + protected function compileElsePushIf(string $expression): string { $parts = explode(',', $this->stripParentheses($expression), 2); @@ -403,7 +403,7 @@ protected function compileElsePushIf($expression) * @param string $expression * @return string */ - protected function compileElsePush($expression) + protected function compileElsePush(string $expression): string { return "stopPush(); else: \$__env->startPush{$expression}; ?>"; } @@ -413,7 +413,7 @@ protected function compileElsePush($expression) * * @return string */ - protected function compileEndPushIf() + protected function compileEndPushIf(): string { return 'stopPush(); endif; ?>'; } diff --git a/src/view/src/Compilers/Concerns/CompilesEchos.php b/src/view/src/Compilers/Concerns/CompilesEchos.php index f35c212d0..11c1e5a5b 100644 --- a/src/view/src/Compilers/Concerns/CompilesEchos.php +++ b/src/view/src/Compilers/Concerns/CompilesEchos.php @@ -10,9 +10,9 @@ trait CompilesEchos /** * Custom rendering callbacks for stringable objects. * - * @var array + * @var array */ - protected $echoHandlers = []; + protected array $echoHandlers = []; /** * Add a handler to be executed before echoing a given class. @@ -21,7 +21,7 @@ trait CompilesEchos * @param callable|null $handler * @return void */ - public function stringable($class, $handler = null) + public function stringable(string|callable $class, ?callable $handler = null): void { if ($class instanceof Closure) { [$class, $handler] = [$this->firstClosureParameterType($class), $class]; @@ -36,7 +36,7 @@ public function stringable($class, $handler = null) * @param string $value * @return string */ - public function compileEchos($value) + public function compileEchos(string $value): string { foreach ($this->getEchoMethods() as $method) { $value = $this->$method($value); @@ -48,9 +48,9 @@ public function compileEchos($value) /** * Get the echo methods in the proper order for compilation. * - * @return array + * @return array */ - protected function getEchoMethods() + protected function getEchoMethods(): array { return [ 'compileRawEchos', @@ -65,7 +65,7 @@ protected function getEchoMethods() * @param string $value * @return string */ - protected function compileRawEchos($value) + protected function compileRawEchos(string $value): string { $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]); @@ -86,7 +86,7 @@ protected function compileRawEchos($value) * @param string $value * @return string */ - protected function compileRegularEchos($value) + protected function compileRegularEchos(string $value): string { $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]); @@ -107,7 +107,7 @@ protected function compileRegularEchos($value) * @param string $value * @return string */ - protected function compileEscapedEchos($value) + protected function compileEscapedEchos(string $value): string { $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]); @@ -128,7 +128,7 @@ protected function compileEscapedEchos($value) * @param string $result * @return string */ - protected function addBladeCompilerVariable($result) + protected function addBladeCompilerVariable(string $result): string { return "".$result; } @@ -139,7 +139,7 @@ protected function addBladeCompilerVariable($result) * @param string $value * @return string */ - protected function wrapInEchoHandler($value) + protected function wrapInEchoHandler(string $value): string { $value = (new Stringable($value)) ->trim() @@ -153,10 +153,10 @@ protected function wrapInEchoHandler($value) /** * Apply the echo handler for the value if it exists. * - * @param string $value - * @return string + * @param mixed $value + * @return mixed */ - public function applyEchoHandler($value) + public function applyEchoHandler(mixed $value): mixed { if (is_object($value) && isset($this->echoHandlers[get_class($value)])) { return call_user_func($this->echoHandlers[get_class($value)], $value); diff --git a/src/view/src/Compilers/Concerns/CompilesErrors.php b/src/view/src/Compilers/Concerns/CompilesErrors.php index 9965ec9d1..b091daf58 100644 --- a/src/view/src/Compilers/Concerns/CompilesErrors.php +++ b/src/view/src/Compilers/Concerns/CompilesErrors.php @@ -10,7 +10,7 @@ trait CompilesErrors * @param string $expression * @return string */ - protected function compileError($expression) + protected function compileError(string $expression): string { $expression = $this->stripParentheses($expression); @@ -27,7 +27,7 @@ protected function compileError($expression) * @param string $expression * @return string */ - protected function compileEnderror($expression) + protected function compileEnderror(string $expression): string { return 'lastFragment = trim($expression, "()'\" "); @@ -29,7 +29,7 @@ protected function compileFragment($expression) * * @return string */ - protected function compileEndfragment() + protected function compileEndfragment(): string { return 'stopFragment(); ?>'; } diff --git a/src/view/src/Compilers/Concerns/CompilesHelpers.php b/src/view/src/Compilers/Concerns/CompilesHelpers.php index f861c894a..6acb1fe6f 100644 --- a/src/view/src/Compilers/Concerns/CompilesHelpers.php +++ b/src/view/src/Compilers/Concerns/CompilesHelpers.php @@ -11,7 +11,7 @@ trait CompilesHelpers * * @return string */ - protected function compileCsrf() + protected function compileCsrf(): string { return ''; } @@ -22,7 +22,7 @@ protected function compileCsrf() * @param string $arguments * @return string */ - protected function compileDd($arguments) + protected function compileDd(string $arguments): string { return ""; } @@ -33,7 +33,7 @@ protected function compileDd($arguments) * @param string $arguments * @return string */ - protected function compileDump($arguments) + protected function compileDump(string $arguments): string { return ""; } @@ -44,7 +44,7 @@ protected function compileDump($arguments) * @param string $method * @return string */ - protected function compileMethod($method) + protected function compileMethod(string $method): string { return ""; } @@ -55,7 +55,7 @@ protected function compileMethod($method) * @param string|null $arguments * @return string */ - protected function compileVite($arguments) + protected function compileVite(?string $arguments): string { $arguments ??= '()'; @@ -69,7 +69,7 @@ protected function compileVite($arguments) * * @return string */ - protected function compileViteReactRefresh() + protected function compileViteReactRefresh(): string { $class = Vite::class; diff --git a/src/view/src/Compilers/Concerns/CompilesIncludes.php b/src/view/src/Compilers/Concerns/CompilesIncludes.php index 168203421..aca01aa35 100644 --- a/src/view/src/Compilers/Concerns/CompilesIncludes.php +++ b/src/view/src/Compilers/Concerns/CompilesIncludes.php @@ -10,7 +10,7 @@ trait CompilesIncludes * @param string $expression * @return string */ - protected function compileEach($expression) + protected function compileEach(string $expression): string { return "renderEach{$expression}; ?>"; } @@ -21,7 +21,7 @@ protected function compileEach($expression) * @param string $expression * @return string */ - protected function compileInclude($expression) + protected function compileInclude(string $expression): string { $expression = $this->stripParentheses($expression); @@ -34,7 +34,7 @@ protected function compileInclude($expression) * @param string $expression * @return string */ - protected function compileIncludeIf($expression) + protected function compileIncludeIf(string $expression): string { $expression = $this->stripParentheses($expression); @@ -47,7 +47,7 @@ protected function compileIncludeIf($expression) * @param string $expression * @return string */ - protected function compileIncludeWhen($expression) + protected function compileIncludeWhen(string $expression): string { $expression = $this->stripParentheses($expression); @@ -60,7 +60,7 @@ protected function compileIncludeWhen($expression) * @param string $expression * @return string */ - protected function compileIncludeUnless($expression) + protected function compileIncludeUnless(string $expression): string { $expression = $this->stripParentheses($expression); @@ -73,7 +73,7 @@ protected function compileIncludeUnless($expression) * @param string $expression * @return string */ - protected function compileIncludeFirst($expression) + protected function compileIncludeFirst(string $expression): string { $expression = $this->stripParentheses($expression); diff --git a/src/view/src/Compilers/Concerns/CompilesInjections.php b/src/view/src/Compilers/Concerns/CompilesInjections.php index dcd8ccfa4..9691d7531 100644 --- a/src/view/src/Compilers/Concerns/CompilesInjections.php +++ b/src/view/src/Compilers/Concerns/CompilesInjections.php @@ -10,7 +10,7 @@ trait CompilesInjections * @param string $expression * @return string */ - protected function compileInject($expression) + protected function compileInject(string $expression): string { $segments = explode(',', preg_replace("/[\(\)]/", '', $expression)); diff --git a/src/view/src/Compilers/Concerns/CompilesJs.php b/src/view/src/Compilers/Concerns/CompilesJs.php index 175d7f02a..ea0d3e996 100644 --- a/src/view/src/Compilers/Concerns/CompilesJs.php +++ b/src/view/src/Compilers/Concerns/CompilesJs.php @@ -12,7 +12,7 @@ trait CompilesJs * @param string $expression * @return string */ - protected function compileJs(string $expression) + protected function compileJs(string $expression): string { return sprintf( "toHtml() ?>", diff --git a/src/view/src/Compilers/Concerns/CompilesJson.php b/src/view/src/Compilers/Concerns/CompilesJson.php index 851b70e5a..6ec0266d0 100644 --- a/src/view/src/Compilers/Concerns/CompilesJson.php +++ b/src/view/src/Compilers/Concerns/CompilesJson.php @@ -9,7 +9,7 @@ trait CompilesJson * * @var int */ - private $encodingOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; + private int $encodingOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; /** * Compile the JSON statement into valid PHP. @@ -17,7 +17,7 @@ trait CompilesJson * @param string $expression * @return string */ - protected function compileJson($expression) + protected function compileJson(string $expression): string { $parts = explode(',', $this->stripParentheses($expression)); diff --git a/src/view/src/Compilers/Concerns/CompilesLayouts.php b/src/view/src/Compilers/Concerns/CompilesLayouts.php index 7766b8230..223abfb22 100644 --- a/src/view/src/Compilers/Concerns/CompilesLayouts.php +++ b/src/view/src/Compilers/Concerns/CompilesLayouts.php @@ -7,9 +7,9 @@ trait CompilesLayouts /** * The name of the last section that was started. * - * @var string + * @var string|null */ - protected $lastSection; + protected ?string $lastSection = null; /** * Compile the extends statements into valid PHP. @@ -17,7 +17,7 @@ trait CompilesLayouts * @param string $expression * @return string */ - protected function compileExtends($expression) + protected function compileExtends(string $expression): string { $expression = $this->stripParentheses($expression); @@ -34,7 +34,7 @@ protected function compileExtends($expression) * @param string $expression * @return string */ - protected function compileExtendsFirst($expression) + protected function compileExtendsFirst(string $expression): string { $expression = $this->stripParentheses($expression); @@ -51,7 +51,7 @@ protected function compileExtendsFirst($expression) * @param string $expression * @return string */ - protected function compileSection($expression) + protected function compileSection(string $expression): string { $this->lastSection = trim($expression, "()'\" "); @@ -63,11 +63,11 @@ protected function compileSection($expression) * * @return string */ - protected function compileParent() + protected function compileParent(): string { $escapedLastSection = strtr($this->lastSection, ['\\' => '\\\\', "'" => "\\'"]); - return ""; + return ""; } /** @@ -76,7 +76,7 @@ protected function compileParent() * @param string $expression * @return string */ - protected function compileYield($expression) + protected function compileYield(string $expression): string { return "yieldContent{$expression}; ?>"; } @@ -86,7 +86,7 @@ protected function compileYield($expression) * * @return string */ - protected function compileShow() + protected function compileShow(): string { return 'yieldSection(); ?>'; } @@ -96,7 +96,7 @@ protected function compileShow() * * @return string */ - protected function compileAppend() + protected function compileAppend(): string { return 'appendSection(); ?>'; } @@ -106,7 +106,7 @@ protected function compileAppend() * * @return string */ - protected function compileOverwrite() + protected function compileOverwrite(): string { return 'stopSection(true); ?>'; } @@ -116,7 +116,7 @@ protected function compileOverwrite() * * @return string */ - protected function compileStop() + protected function compileStop(): string { return 'stopSection(); ?>'; } @@ -126,7 +126,7 @@ protected function compileStop() * * @return string */ - protected function compileEndsection() + protected function compileEndsection(): string { return 'stopSection(); ?>'; } diff --git a/src/view/src/Compilers/Concerns/CompilesLoops.php b/src/view/src/Compilers/Concerns/CompilesLoops.php index eb9145251..91807f356 100644 --- a/src/view/src/Compilers/Concerns/CompilesLoops.php +++ b/src/view/src/Compilers/Concerns/CompilesLoops.php @@ -11,7 +11,7 @@ trait CompilesLoops * * @var int */ - protected $forElseCounter = 0; + protected int $forElseCounter = 0; /** * Compile the for-else statements into valid PHP. @@ -19,9 +19,9 @@ trait CompilesLoops * @param string|null $expression * @return string * - * @throws \Illuminate\Contracts\View\ViewCompilationException + * @throws \Hypervel\Contracts\View\ViewCompilationException */ - protected function compileForelse($expression) + protected function compileForelse(?string $expression): string { $empty = '$__empty_'.++$this->forElseCounter; @@ -48,7 +48,7 @@ protected function compileForelse($expression) * @param string $expression * @return string */ - protected function compileEmpty($expression) + protected function compileEmpty(?string $expression): string { if ($expression) { return ""; @@ -64,7 +64,7 @@ protected function compileEmpty($expression) * * @return string */ - protected function compileEndforelse() + protected function compileEndforelse(): string { return ''; } @@ -74,7 +74,7 @@ protected function compileEndforelse() * * @return string */ - protected function compileEndEmpty() + protected function compileEndEmpty(): string { return ''; } @@ -85,7 +85,7 @@ protected function compileEndEmpty() * @param string $expression * @return string */ - protected function compileFor($expression) + protected function compileFor(string $expression): string { return ""; } @@ -96,9 +96,9 @@ protected function compileFor($expression) * @param string|null $expression * @return string * - * @throws \Illuminate\Contracts\View\ViewCompilationException + * @throws \Hypervel\Contracts\View\ViewCompilationException */ - protected function compileForeach($expression) + protected function compileForeach(?string $expression): string { preg_match('/\( *(.+) +as +(.*)\)$/is', $expression ?? '', $matches); @@ -123,7 +123,7 @@ protected function compileForeach($expression) * @param string $expression * @return string */ - protected function compileBreak($expression) + protected function compileBreak(?string $expression): string { if ($expression) { preg_match('/\(\s*(-?\d+)\s*\)$/', $expression, $matches); @@ -140,7 +140,7 @@ protected function compileBreak($expression) * @param string $expression * @return string */ - protected function compileContinue($expression) + protected function compileContinue(?string $expression): string { if ($expression) { preg_match('/\(\s*(-?\d+)\s*\)$/', $expression, $matches); @@ -156,7 +156,7 @@ protected function compileContinue($expression) * * @return string */ - protected function compileEndfor() + protected function compileEndfor(): string { return ''; } @@ -166,7 +166,7 @@ protected function compileEndfor() * * @return string */ - protected function compileEndforeach() + protected function compileEndforeach(): string { return 'popLoop(); $loop = $__env->getLastLoop(); ?>'; } @@ -177,7 +177,7 @@ protected function compileEndforeach() * @param string $expression * @return string */ - protected function compileWhile($expression) + protected function compileWhile(string $expression): string { return ""; } @@ -187,7 +187,7 @@ protected function compileWhile($expression) * * @return string */ - protected function compileEndwhile() + protected function compileEndwhile(): string { return ''; } diff --git a/src/view/src/Compilers/Concerns/CompilesRawPhp.php b/src/view/src/Compilers/Concerns/CompilesRawPhp.php index 1bd55fa42..5325306f1 100644 --- a/src/view/src/Compilers/Concerns/CompilesRawPhp.php +++ b/src/view/src/Compilers/Concerns/CompilesRawPhp.php @@ -7,10 +7,10 @@ trait CompilesRawPhp /** * Compile the raw PHP statements into valid PHP. * - * @param string $expression + * @param string|null $expression * @return string */ - protected function compilePhp($expression) + protected function compilePhp(?string $expression): string { if ($expression) { return ""; @@ -25,7 +25,7 @@ protected function compilePhp($expression) * @param string $expression * @return string */ - protected function compileUnset($expression) + protected function compileUnset(string $expression): string { return ""; } diff --git a/src/view/src/Compilers/Concerns/CompilesSessions.php b/src/view/src/Compilers/Concerns/CompilesSessions.php index 202c17f6c..cc6ccd154 100644 --- a/src/view/src/Compilers/Concerns/CompilesSessions.php +++ b/src/view/src/Compilers/Concerns/CompilesSessions.php @@ -10,7 +10,7 @@ trait CompilesSessions * @param string $expression * @return string */ - protected function compileSession($expression) + protected function compileSession(string $expression): string { $expression = $this->stripParentheses($expression); @@ -26,7 +26,7 @@ protected function compileSession($expression) * @param string $expression * @return string */ - protected function compileEndsession($expression) + protected function compileEndsession(string $expression): string { return 'yieldPushContent{$expression}; ?>"; } @@ -23,7 +23,7 @@ protected function compileStack($expression) * @param string $expression * @return string */ - protected function compilePush($expression) + protected function compilePush(string $expression): string { return "startPush{$expression}; ?>"; } @@ -34,7 +34,7 @@ protected function compilePush($expression) * @param string $expression * @return string */ - protected function compilePushOnce($expression) + protected function compilePushOnce(string $expression): string { $parts = explode(',', $this->stripParentheses($expression), 2); @@ -51,7 +51,7 @@ protected function compilePushOnce($expression) * * @return string */ - protected function compileEndpush() + protected function compileEndpush(): string { return 'stopPush(); ?>'; } @@ -61,7 +61,7 @@ protected function compileEndpush() * * @return string */ - protected function compileEndpushOnce() + protected function compileEndpushOnce(): string { return 'stopPush(); endif; ?>'; } @@ -72,7 +72,7 @@ protected function compileEndpushOnce() * @param string $expression * @return string */ - protected function compilePrepend($expression) + protected function compilePrepend(string $expression): string { return "startPrepend{$expression}; ?>"; } @@ -83,7 +83,7 @@ protected function compilePrepend($expression) * @param string $expression * @return string */ - protected function compilePrependOnce($expression) + protected function compilePrependOnce(string $expression): string { $parts = explode(',', $this->stripParentheses($expression), 2); @@ -100,7 +100,7 @@ protected function compilePrependOnce($expression) * * @return string */ - protected function compileEndprepend() + protected function compileEndprepend(): string { return 'stopPrepend(); ?>'; } @@ -110,7 +110,7 @@ protected function compileEndprepend() * * @return string */ - protected function compileEndprependOnce() + protected function compileEndprependOnce(): string { return 'stopPrepend(); endif; ?>'; } diff --git a/src/view/src/Compilers/Concerns/CompilesStyles.php b/src/view/src/Compilers/Concerns/CompilesStyles.php index 36e80dcdf..4e4ca9da7 100644 --- a/src/view/src/Compilers/Concerns/CompilesStyles.php +++ b/src/view/src/Compilers/Concerns/CompilesStyles.php @@ -7,13 +7,13 @@ trait CompilesStyles /** * Compile the conditional style statement into valid PHP. * - * @param string $expression + * @param string|null $expression * @return string */ - protected function compileStyle($expression) + protected function compileStyle(?string $expression): string { $expression = is_null($expression) ? '([])' : $expression; - return "style=\"\""; + return "style=\"\""; } } diff --git a/src/view/src/Compilers/Concerns/CompilesTranslations.php b/src/view/src/Compilers/Concerns/CompilesTranslations.php index 77b4ae497..ffa1cf830 100644 --- a/src/view/src/Compilers/Concerns/CompilesTranslations.php +++ b/src/view/src/Compilers/Concerns/CompilesTranslations.php @@ -10,7 +10,7 @@ trait CompilesTranslations * @param string|null $expression * @return string */ - protected function compileLang($expression) + protected function compileLang(?string $expression): string { if (is_null($expression)) { return 'startTranslation(); ?>'; @@ -26,7 +26,7 @@ protected function compileLang($expression) * * @return string */ - protected function compileEndlang() + protected function compileEndlang(): string { return 'renderTranslation(); ?>'; } @@ -37,7 +37,7 @@ protected function compileEndlang() * @param string $expression * @return string */ - protected function compileChoice($expression) + protected function compileChoice(string $expression): string { return "choice{$expression}; ?>"; } diff --git a/src/view/src/Compilers/Concerns/CompilesUseStatements.php b/src/view/src/Compilers/Concerns/CompilesUseStatements.php index 0a4875f12..a846014ba 100644 --- a/src/view/src/Compilers/Concerns/CompilesUseStatements.php +++ b/src/view/src/Compilers/Concerns/CompilesUseStatements.php @@ -10,7 +10,7 @@ trait CompilesUseStatements * @param string $expression * @return string */ - protected function compileUse($expression) + protected function compileUse(string $expression): string { $segments = explode(',', preg_replace("/[\(\)]/", '', $expression)); diff --git a/src/view/src/Component.php b/src/view/src/Component.php index d0f07adfa..a92ad6125 100644 --- a/src/view/src/Component.php +++ b/src/view/src/Component.php @@ -18,35 +18,35 @@ abstract class Component * * @var array */ - protected $except = []; + protected array $except = []; /** * The component alias name. * * @var string */ - public $componentName; + public ?string $componentName = null; /** * The component attributes. * - * @var \Illuminate\View\ComponentAttributeBag + * @var \Hypervel\View\ComponentAttributeBag */ - public $attributes; + public ?ComponentAttributeBag $attributes = null; /** * The view factory instance, if any. * - * @var \Illuminate\Contracts\View\Factory|null + * @var \Hypervel\Contracts\View\Factory|null */ - protected static $factory; + protected static $factory = null; /** * The component resolver callback. * * @var (\Closure(string, array): Component)|null */ - protected static $componentsResolver; + protected static ?Closure $componentsResolver = null; /** * The cache of blade view names, keyed by contents. @@ -60,14 +60,14 @@ abstract class Component * * @var array */ - protected static $propertyCache = []; + protected static array $propertyCache = []; /** * The cache of public method names, keyed by class. * * @var array */ - protected static $methodCache = []; + protected static array $methodCache = []; /** * The cache of constructor parameters, keyed by class. @@ -81,14 +81,14 @@ abstract class Component * * @var array */ - protected static $ignoredParameterNames = []; + protected static array $ignoredParameterNames = []; /** * Get the view / view contents that represent the component. * - * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\Support\Htmlable|\Closure|string + * @return \Hypervel\Contracts\View\View|\Hypervel\Contracts\Support\Htmlable|\Closure|string */ - abstract public function render(); + abstract public function render(): mixed; /** * Resolve the component instance with the given data. @@ -96,7 +96,7 @@ abstract public function render(); * @param array $data * @return static */ - public static function resolve($data) + public static function resolve(array $data): static { if (static::$componentsResolver) { return call_user_func(static::$componentsResolver, static::class, $data); @@ -118,7 +118,7 @@ public static function resolve($data) * * @return array */ - protected static function extractConstructorParameters() + protected static function extractConstructorParameters(): array { if (! isset(static::$constructorParametersCache[static::class])) { $class = new ReflectionClass(static::class); @@ -136,9 +136,9 @@ protected static function extractConstructorParameters() /** * 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 + * @return \Hypervel\Contracts\View\View|\Hypervel\Contracts\Support\Htmlable|\Closure|string */ - public function resolveView() + public function resolveView(): mixed { $view = $this->render(); @@ -170,7 +170,7 @@ public function resolveView() * @param string $contents * @return string */ - protected function extractBladeViewFromString($contents) + protected function extractBladeViewFromString(string $contents): string { $key = sprintf('%s::%s', static::class, $contents); @@ -188,11 +188,11 @@ protected function extractBladeViewFromString($contents) /** * Create a Blade view with the raw component string content. * - * @param \Illuminate\Contracts\View\Factory $factory + * @param \Hypervel\Contracts\View\Factory $factory * @param string $contents * @return string */ - protected function createBladeViewFromString($factory, $contents) + protected function createBladeViewFromString($factory, string $contents): string { $factory->addNamespace( '__components', @@ -218,7 +218,7 @@ protected function createBladeViewFromString($factory, $contents) * * @return array */ - public function data() + public function data(): array { $this->attributes = $this->attributes ?: $this->newAttributeBag(); @@ -230,7 +230,7 @@ public function data() * * @return array */ - protected function extractPublicProperties() + protected function extractPublicProperties(): array { $class = get_class($this); @@ -258,7 +258,7 @@ protected function extractPublicProperties() * * @return array */ - protected function extractPublicMethods() + protected function extractPublicMethods(): array { $class = get_class($this); @@ -285,7 +285,7 @@ protected function extractPublicMethods() * @param \ReflectionMethod $method * @return mixed */ - protected function createVariableFromMethod(ReflectionMethod $method) + protected function createVariableFromMethod(ReflectionMethod $method): mixed { return $method->getNumberOfParameters() === 0 ? $this->createInvokableVariable($method->getName()) @@ -296,9 +296,9 @@ protected function createVariableFromMethod(ReflectionMethod $method) * Create an invokable, toStringable variable for the given component method. * * @param string $method - * @return \Illuminate\View\InvokableComponentVariable + * @return \Hypervel\View\InvokableComponentVariable */ - protected function createInvokableVariable(string $method) + protected function createInvokableVariable(string $method): InvokableComponentVariable { return new InvokableComponentVariable(function () use ($method) { return $this->{$method}(); @@ -311,7 +311,7 @@ protected function createInvokableVariable(string $method) * @param string $name * @return bool */ - protected function shouldIgnore($name) + protected function shouldIgnore(string $name): bool { return str_starts_with($name, '__') || in_array($name, $this->ignoredMethods()); @@ -322,7 +322,7 @@ protected function shouldIgnore($name) * * @return array */ - protected function ignoredMethods() + protected function ignoredMethods(): array { return array_merge([ 'data', @@ -346,7 +346,7 @@ protected function ignoredMethods() * @param string $name * @return $this */ - public function withName($name) + public function withName(string $name): static { $this->componentName = $name; @@ -359,7 +359,7 @@ public function withName($name) * @param array $attributes * @return $this */ - public function withAttributes(array $attributes) + public function withAttributes(array $attributes): static { $this->attributes = $this->attributes ?: $this->newAttributeBag(); @@ -372,9 +372,9 @@ public function withAttributes(array $attributes) * Get a new attribute bag instance. * * @param array $attributes - * @return \Illuminate\View\ComponentAttributeBag + * @return \Hypervel\View\ComponentAttributeBag */ - protected function newAttributeBag(array $attributes = []) + protected function newAttributeBag(array $attributes = []): ComponentAttributeBag { return new ComponentAttributeBag($attributes); } @@ -384,7 +384,7 @@ protected function newAttributeBag(array $attributes = []) * * @return bool */ - public function shouldRender() + public function shouldRender(): bool { return true; } @@ -393,11 +393,11 @@ public function shouldRender() * Get the evaluated view contents for the given view. * * @param string|null $view - * @param \Illuminate\Contracts\Support\Arrayable|array $data + * @param \Hypervel\Contracts\Support\Arrayable|array $data * @param array $mergeData - * @return \Illuminate\Contracts\View\View + * @return \Hypervel\Contracts\View\View */ - public function view($view, $data = [], $mergeData = []) + public function view(?string $view, mixed $data = [], array $mergeData = []): ViewContract { return $this->factory()->make($view, $data, $mergeData); } @@ -405,9 +405,9 @@ public function view($view, $data = [], $mergeData = []) /** * Get the view factory instance. * - * @return \Illuminate\Contracts\View\Factory + * @return \Hypervel\Contracts\View\Factory */ - protected function factory() + protected function factory(): mixed { if (is_null(static::$factory)) { static::$factory = Container::getInstance()->make('view'); @@ -421,7 +421,7 @@ protected function factory() * * @return array */ - public static function ignoredParameterNames() + public static function ignoredParameterNames(): array { if (! isset(static::$ignoredParameterNames[static::class])) { $constructor = (new ReflectionClass( @@ -446,7 +446,7 @@ public static function ignoredParameterNames() * * @return void */ - public static function flushCache() + public static function flushCache(): void { static::$bladeViewCache = []; static::$constructorParametersCache = []; @@ -459,7 +459,7 @@ public static function flushCache() * * @return void */ - public static function forgetFactory() + public static function forgetFactory(): void { static::$factory = null; } @@ -471,7 +471,7 @@ public static function forgetFactory() * * @internal */ - public static function forgetComponentsResolver() + public static function forgetComponentsResolver(): void { static::$componentsResolver = null; } @@ -484,7 +484,7 @@ public static function forgetComponentsResolver() * * @internal */ - public static function resolveComponentsUsing($resolver) + public static function resolveComponentsUsing(Closure $resolver): void { static::$componentsResolver = $resolver; } diff --git a/src/view/src/ComponentAttributeBag.php b/src/view/src/ComponentAttributeBag.php index 18714359f..0767bf9a4 100644 --- a/src/view/src/ComponentAttributeBag.php +++ b/src/view/src/ComponentAttributeBag.php @@ -25,7 +25,7 @@ class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSeria * * @var array */ - protected $attributes = []; + protected array $attributes = []; /** * Create a new component attribute bag instance. @@ -33,7 +33,7 @@ class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSeria * @param array $attributes * @return void */ - public function __construct(array $attributes = []) + public function __construct(array $attributes = []): void { $this->setAttributes($attributes); } @@ -43,7 +43,7 @@ public function __construct(array $attributes = []) * * @return array */ - public function all() + public function all(): array { return $this->attributes; } @@ -54,7 +54,7 @@ public function all() * @param mixed $default * @return mixed */ - public function first($default = null) + public function first(mixed $default = null): mixed { return $this->getIterator()->current() ?? value($default); } @@ -66,7 +66,7 @@ public function first($default = null) * @param mixed $default * @return mixed */ - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { return $this->attributes[$key] ?? value($default); } @@ -77,7 +77,7 @@ public function get($key, $default = null) * @param array|string $key * @return bool */ - public function has($key) + public function has(array|string $key): bool { $keys = is_array($key) ? $key : func_get_args(); @@ -96,7 +96,7 @@ public function has($key) * @param array|string $key * @return bool */ - public function hasAny($key) + public function hasAny(array|string $key): bool { if (! count($this->attributes)) { return false; @@ -119,7 +119,7 @@ public function hasAny($key) * @param string $key * @return bool */ - public function missing($key) + public function missing(string $key): bool { return ! $this->has($key); } @@ -130,7 +130,7 @@ public function missing($key) * @param mixed $keys * @return static */ - public function only($keys) + public function only(mixed $keys): static { if (is_null($keys)) { $values = $this->attributes; @@ -149,7 +149,7 @@ public function only($keys) * @param mixed|array $keys * @return static */ - public function except($keys) + public function except(mixed $keys): static { if (is_null($keys)) { $values = $this->attributes; @@ -168,7 +168,7 @@ public function except($keys) * @param callable $callback * @return static */ - public function filter($callback) + public function filter(callable $callback): static { return new static((new Collection($this->attributes))->filter($callback)->all()); } @@ -179,7 +179,7 @@ public function filter($callback) * @param string|string[] $needles * @return static */ - public function whereStartsWith($needles) + public function whereStartsWith(string|array $needles): static { return $this->filter(function ($value, $key) use ($needles) { return Str::startsWith($key, $needles); @@ -192,7 +192,7 @@ public function whereStartsWith($needles) * @param string|string[] $needles * @return static */ - public function whereDoesntStartWith($needles) + public function whereDoesntStartWith(string|array $needles): static { return $this->filter(function ($value, $key) use ($needles) { return ! Str::startsWith($key, $needles); @@ -205,7 +205,7 @@ public function whereDoesntStartWith($needles) * @param string|string[] $needles * @return static */ - public function thatStartWith($needles) + public function thatStartWith(string|array $needles): static { return $this->whereStartsWith($needles); } @@ -216,7 +216,7 @@ public function thatStartWith($needles) * @param mixed|array $keys * @return static */ - public function onlyProps($keys) + public function onlyProps(mixed $keys): static { return $this->only(static::extractPropNames($keys)); } @@ -227,7 +227,7 @@ public function onlyProps($keys) * @param mixed|array $keys * @return static */ - public function exceptProps($keys) + public function exceptProps(mixed $keys): static { return $this->except(static::extractPropNames($keys)); } @@ -238,7 +238,7 @@ public function exceptProps($keys) * @param mixed|array $classList * @return static */ - public function class($classList) + public function class(mixed $classList): static { $classList = Arr::wrap($classList); @@ -251,7 +251,7 @@ public function class($classList) * @param mixed|array $styleList * @return static */ - public function style($styleList) + public function style(mixed $styleList): static { $styleList = Arr::wrap($styleList); @@ -265,7 +265,7 @@ public function style($styleList) * @param bool $escape * @return static */ - public function merge(array $attributeDefaults = [], $escape = true) + public function merge(array $attributeDefaults = [], bool $escape = true): static { $attributeDefaults = array_map(function ($value) use ($escape) { return $this->shouldEscapeAttributeValue($escape, $value) @@ -303,7 +303,7 @@ public function merge(array $attributeDefaults = [], $escape = true) * @param mixed $value * @return bool */ - protected function shouldEscapeAttributeValue($escape, $value) + protected function shouldEscapeAttributeValue(bool $escape, mixed $value): bool { if (! $escape) { return false; @@ -318,9 +318,9 @@ protected function shouldEscapeAttributeValue($escape, $value) * Create a new appendable attribute value. * * @param mixed $value - * @return \Illuminate\View\AppendableAttributeValue + * @return \Hypervel\View\AppendableAttributeValue */ - public function prepends($value) + public function prepends(mixed $value): AppendableAttributeValue { return new AppendableAttributeValue($value); } @@ -333,7 +333,7 @@ public function prepends($value) * @param bool $escape * @return mixed */ - protected function resolveAppendableAttributeDefault($attributeDefaults, $key, $escape) + protected function resolveAppendableAttributeDefault(array $attributeDefaults, string $key, bool $escape): mixed { if ($this->shouldEscapeAttributeValue($escape, $value = $attributeDefaults[$key]->value)) { $value = e($value); @@ -347,7 +347,7 @@ protected function resolveAppendableAttributeDefault($attributeDefaults, $key, $ * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return trim((string) $this) === ''; } @@ -357,7 +357,7 @@ public function isEmpty() * * @return bool */ - public function isNotEmpty() + public function isNotEmpty(): bool { return ! $this->isEmpty(); } @@ -367,7 +367,7 @@ public function isNotEmpty() * * @return array */ - public function getAttributes() + public function getAttributes(): array { return $this->attributes; } @@ -378,7 +378,7 @@ public function getAttributes() * @param array $attributes * @return void */ - public function setAttributes(array $attributes) + public function setAttributes(array $attributes): void { if (isset($attributes['attributes']) && $attributes['attributes'] instanceof self) { @@ -398,7 +398,7 @@ public function setAttributes(array $attributes) * @param array $keys * @return array */ - public static function extractPropNames(array $keys) + public static function extractPropNames(array $keys): array { $props = []; @@ -417,7 +417,7 @@ public static function extractPropNames(array $keys) * * @return string */ - public function toHtml() + public function toHtml(): string { return (string) $this; } @@ -426,9 +426,9 @@ public function toHtml() * Merge additional attributes / values into the attribute bag. * * @param array $attributeDefaults - * @return \Illuminate\Support\HtmlString + * @return \Hypervel\Support\HtmlString */ - public function __invoke(array $attributeDefaults = []) + public function __invoke(array $attributeDefaults = []): HtmlString { return new HtmlString((string) $this->merge($attributeDefaults)); } @@ -439,7 +439,7 @@ public function __invoke(array $attributeDefaults = []) * @param string $offset * @return bool */ - public function offsetExists($offset): bool + public function offsetExists(mixed $offset): bool { return isset($this->attributes[$offset]); } @@ -450,7 +450,7 @@ public function offsetExists($offset): bool * @param string $offset * @return mixed */ - public function offsetGet($offset): mixed + public function offsetGet(mixed $offset): mixed { return $this->get($offset); } @@ -462,7 +462,7 @@ public function offsetGet($offset): mixed * @param mixed $value * @return void */ - public function offsetSet($offset, $value): void + public function offsetSet(mixed $offset, mixed $value): void { $this->attributes[$offset] = $value; } @@ -473,7 +473,7 @@ public function offsetSet($offset, $value): void * @param string $offset * @return void */ - public function offsetUnset($offset): void + public function offsetUnset(mixed $offset): void { unset($this->attributes[$offset]); } @@ -503,7 +503,7 @@ public function jsonSerialize(): mixed * * @return string */ - public function __toString() + public function __toString(): string { $string = ''; diff --git a/src/view/src/ComponentSlot.php b/src/view/src/ComponentSlot.php index f0cd330c1..468f36feb 100644 --- a/src/view/src/ComponentSlot.php +++ b/src/view/src/ComponentSlot.php @@ -11,16 +11,16 @@ class ComponentSlot implements Htmlable, Stringable /** * The slot attribute bag. * - * @var \Illuminate\View\ComponentAttributeBag + * @var \Hypervel\View\ComponentAttributeBag */ - public $attributes; + public ComponentAttributeBag $attributes; /** * The slot contents. * * @var string */ - protected $contents; + protected string $contents; /** * Create a new slot instance. @@ -29,7 +29,7 @@ class ComponentSlot implements Htmlable, Stringable * @param array $attributes * @return void */ - public function __construct($contents = '', $attributes = []) + public function __construct(string $contents = '', array $attributes = []): void { $this->contents = $contents; @@ -42,7 +42,7 @@ public function __construct($contents = '', $attributes = []) * @param array $attributes * @return $this */ - public function withAttributes(array $attributes) + public function withAttributes(array $attributes): static { $this->attributes = new ComponentAttributeBag($attributes); @@ -54,7 +54,7 @@ public function withAttributes(array $attributes) * * @return string */ - public function toHtml() + public function toHtml(): string { return $this->contents; } @@ -64,7 +64,7 @@ public function toHtml() * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return $this->contents === ''; } @@ -74,7 +74,7 @@ public function isEmpty() * * @return bool */ - public function isNotEmpty() + public function isNotEmpty(): bool { return ! $this->isEmpty(); } @@ -85,7 +85,7 @@ public function isNotEmpty() * @param callable|string|null $callable * @return bool */ - public function hasActualContent(callable|string|null $callable = null) + public function hasActualContent(callable|string|null $callable = null): bool { if (is_string($callable) && ! function_exists($callable)) { throw new InvalidArgumentException('Callable does not exist.'); @@ -103,7 +103,7 @@ public function hasActualContent(callable|string|null $callable = null) * * @return string */ - public function __toString() + public function __toString(): string { return $this->toHtml(); } diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php index 260fa33dd..f12e62534 100644 --- a/src/view/src/Concerns/ManagesComponents.php +++ b/src/view/src/Concerns/ManagesComponents.php @@ -14,44 +14,44 @@ trait ManagesComponents * * @var array */ - protected $componentStack = []; + protected array $componentStack = []; /** * The original data passed to the component. * * @var array */ - protected $componentData = []; + protected array $componentData = []; /** * The component data for the component that is currently being rendered. * * @var array */ - protected $currentComponentData = []; + protected array $currentComponentData = []; /** * The slot contents for the component. * * @var array */ - protected $slots = []; + protected array $slots = []; /** * The names of the slots being rendered. * * @var array */ - protected $slotStack = []; + protected array $slotStack = []; /** * Start a component rendering process. * - * @param \Illuminate\Contracts\View\View|\Illuminate\Contracts\Support\Htmlable|\Closure|string $view + * @param \Hypervel\Contracts\View\View|\Hypervel\Contracts\Support\Htmlable|\Closure|string $view * @param array $data * @return void */ - public function startComponent($view, array $data = []) + public function startComponent(mixed $view, array $data = []): void { if (ob_start()) { $this->componentStack[] = $view; @@ -69,7 +69,7 @@ public function startComponent($view, array $data = []) * @param array $data * @return void */ - public function startComponentFirst(array $names, array $data = []) + public function startComponentFirst(array $names, array $data = []): void { $name = Arr::first($names, function ($item) { return $this->exists($item); @@ -83,7 +83,7 @@ public function startComponentFirst(array $names, array $data = []) * * @return string */ - public function renderComponent() + public function renderComponent(): string { $view = array_pop($this->componentStack); @@ -112,7 +112,7 @@ public function renderComponent() * * @return array */ - protected function componentData() + protected function componentData(): array { $defaultSlot = new ComponentSlot(trim(ob_get_clean())); @@ -135,7 +135,7 @@ protected function componentData() * @param mixed $default * @return mixed|null */ - public function getConsumableComponentData($key, $default = null) + public function getConsumableComponentData(string $key, mixed $default = null): mixed { if (array_key_exists($key, $this->currentComponentData)) { return $this->currentComponentData[$key]; @@ -166,7 +166,7 @@ public function getConsumableComponentData($key, $default = null) * @param array $attributes * @return void */ - public function slot($name, $content = null, $attributes = []) + public function slot(string $name, ?string $content = null, array $attributes = []): void { if (func_num_args() === 2 || $content !== null) { $this->slots[$this->currentComponent()][$name] = $content; @@ -182,7 +182,7 @@ public function slot($name, $content = null, $attributes = []) * * @return void */ - public function endSlot() + public function endSlot(): void { last($this->componentStack); @@ -202,7 +202,7 @@ public function endSlot() * * @return int */ - protected function currentComponent() + protected function currentComponent(): int { return count($this->componentStack) - 1; } @@ -212,7 +212,7 @@ protected function currentComponent() * * @return void */ - protected function flushComponents() + protected function flushComponents(): void { $this->componentStack = []; $this->componentData = []; diff --git a/src/view/src/Concerns/ManagesEvents.php b/src/view/src/Concerns/ManagesEvents.php index c25167c51..628a5220a 100644 --- a/src/view/src/Concerns/ManagesEvents.php +++ b/src/view/src/Concerns/ManagesEvents.php @@ -15,7 +15,7 @@ trait ManagesEvents * @param \Closure|string $callback * @return array */ - public function creator($views, $callback) + public function creator(array|string $views, Closure|string $callback): array { $creators = []; @@ -32,7 +32,7 @@ public function creator($views, $callback) * @param array $composers * @return array */ - public function composers(array $composers) + public function composers(array $composers): array { $registered = []; @@ -50,7 +50,7 @@ public function composers(array $composers) * @param \Closure|string $callback * @return array */ - public function composer($views, $callback) + public function composer(array|string $views, Closure|string $callback): array { $composers = []; @@ -69,7 +69,7 @@ public function composer($views, $callback) * @param string $prefix * @return \Closure|null */ - protected function addViewEvent($view, $callback, $prefix = 'composing: ') + protected function addViewEvent(string $view, Closure|string $callback, string $prefix = 'composing: '): ?Closure { $view = $this->normalizeName($view); @@ -90,7 +90,7 @@ protected function addViewEvent($view, $callback, $prefix = 'composing: ') * @param string $prefix * @return \Closure */ - protected function addClassEvent($view, $class, $prefix) + protected function addClassEvent(string $view, string $class, string $prefix): Closure { $name = $prefix.$view; @@ -113,7 +113,7 @@ protected function addClassEvent($view, $class, $prefix) * @param string $prefix * @return \Closure */ - protected function buildClassEventCallback($class, $prefix) + protected function buildClassEventCallback(string $class, string $prefix): Closure { [$class, $method] = $this->parseClassEvent($class, $prefix); @@ -132,7 +132,7 @@ protected function buildClassEventCallback($class, $prefix) * @param string $prefix * @return array */ - protected function parseClassEvent($class, $prefix) + protected function parseClassEvent(string $class, string $prefix): array { return Str::parseCallback($class, $this->classEventMethodForPrefix($prefix)); } @@ -143,7 +143,7 @@ protected function parseClassEvent($class, $prefix) * @param string $prefix * @return string */ - protected function classEventMethodForPrefix($prefix) + protected function classEventMethodForPrefix(string $prefix): string { return str_contains($prefix, 'composing') ? 'compose' : 'create'; } @@ -155,7 +155,7 @@ protected function classEventMethodForPrefix($prefix) * @param \Closure $callback * @return void */ - protected function addEventListener($name, $callback) + protected function addEventListener(string $name, Closure $callback): void { if (str_contains($name, '*')) { $callback = function ($name, array $data) use ($callback) { @@ -169,10 +169,10 @@ protected function addEventListener($name, $callback) /** * Call the composer for a given view. * - * @param \Illuminate\Contracts\View\View $view + * @param \Hypervel\Contracts\View\View $view * @return void */ - public function callComposer(ViewContract $view) + public function callComposer(ViewContract $view): void { if ($this->events->hasListeners($event = 'composing: '.$view->name())) { $this->events->dispatch($event, [$view]); @@ -182,10 +182,10 @@ public function callComposer(ViewContract $view) /** * Call the creator for a given view. * - * @param \Illuminate\Contracts\View\View $view + * @param \Hypervel\Contracts\View\View $view * @return void */ - public function callCreator(ViewContract $view) + public function callCreator(ViewContract $view): void { if ($this->events->hasListeners($event = 'creating: '.$view->name())) { $this->events->dispatch($event, [$view]); diff --git a/src/view/src/Concerns/ManagesFragments.php b/src/view/src/Concerns/ManagesFragments.php index 131a3aed7..317dee232 100644 --- a/src/view/src/Concerns/ManagesFragments.php +++ b/src/view/src/Concerns/ManagesFragments.php @@ -11,14 +11,14 @@ trait ManagesFragments * * @var array */ - protected $fragments = []; + protected array $fragments = []; /** * The stack of in-progress fragment renders. * * @var array */ - protected $fragmentStack = []; + protected array $fragmentStack = []; /** * Start injecting content into a fragment. @@ -26,7 +26,7 @@ trait ManagesFragments * @param string $fragment * @return void */ - public function startFragment($fragment) + public function startFragment(string $fragment): void { if (ob_start()) { $this->fragmentStack[] = $fragment; @@ -40,7 +40,7 @@ public function startFragment($fragment) * * @throws \InvalidArgumentException */ - public function stopFragment() + public function stopFragment(): string { if (empty($this->fragmentStack)) { throw new InvalidArgumentException('Cannot end a fragment without first starting one.'); @@ -60,7 +60,7 @@ public function stopFragment() * @param string|null $default * @return mixed */ - public function getFragment($name, $default = null) + public function getFragment(string $name, ?string $default = null): mixed { return $this->getFragments()[$name] ?? $default; } @@ -70,7 +70,7 @@ public function getFragment($name, $default = null) * * @return array */ - public function getFragments() + public function getFragments(): array { return $this->fragments; } @@ -80,7 +80,7 @@ public function getFragments() * * @return void */ - public function flushFragments() + public function flushFragments(): void { $this->fragments = []; $this->fragmentStack = []; diff --git a/src/view/src/Concerns/ManagesLayouts.php b/src/view/src/Concerns/ManagesLayouts.php index 831108bd1..af92190c3 100644 --- a/src/view/src/Concerns/ManagesLayouts.php +++ b/src/view/src/Concerns/ManagesLayouts.php @@ -13,28 +13,28 @@ trait ManagesLayouts * * @var array */ - protected $sections = []; + protected array $sections = []; /** * The stack of in-progress sections. * * @var array */ - protected $sectionStack = []; + protected array $sectionStack = []; /** * The parent placeholder for the request. * * @var mixed */ - protected static $parentPlaceholder = []; + protected static array $parentPlaceholder = []; /** * The parent placeholder salt for the request. * * @var string */ - protected static $parentPlaceholderSalt; + protected static ?string $parentPlaceholderSalt = null; /** * Start injecting content into a section. @@ -43,7 +43,7 @@ trait ManagesLayouts * @param string|null $content * @return void */ - public function startSection($section, $content = null) + public function startSection(string $section, mixed $content = null): void { if ($content === null) { if (ob_start()) { @@ -61,7 +61,7 @@ public function startSection($section, $content = null) * @param string $content * @return void */ - public function inject($section, $content) + public function inject(string $section, string $content): void { $this->startSection($section, $content); } @@ -71,7 +71,7 @@ public function inject($section, $content) * * @return string */ - public function yieldSection() + public function yieldSection(): string { if (empty($this->sectionStack)) { return ''; @@ -88,7 +88,7 @@ public function yieldSection() * * @throws \InvalidArgumentException */ - public function stopSection($overwrite = false) + public function stopSection(bool $overwrite = false): string { if (empty($this->sectionStack)) { throw new InvalidArgumentException('Cannot end a section without first starting one.'); @@ -112,7 +112,7 @@ public function stopSection($overwrite = false) * * @throws \InvalidArgumentException */ - public function appendSection() + public function appendSection(): string { if (empty($this->sectionStack)) { throw new InvalidArgumentException('Cannot end a section without first starting one.'); @@ -136,7 +136,7 @@ public function appendSection() * @param string $content * @return void */ - protected function extendSection($section, $content) + protected function extendSection(string $section, string $content): void { if (isset($this->sections[$section])) { $content = str_replace(static::parentPlaceholder($section), $content, $this->sections[$section]); @@ -152,7 +152,7 @@ protected function extendSection($section, $content) * @param string $default * @return string */ - public function yieldContent($section, $default = '') + public function yieldContent(string $section, string $default = ''): string { $sectionContent = $default instanceof View ? $default : e($default); @@ -173,7 +173,7 @@ public function yieldContent($section, $default = '') * @param string $section * @return string */ - public static function parentPlaceholder($section = '') + public static function parentPlaceholder(string $section = ''): string { if (! isset(static::$parentPlaceholder[$section])) { $salt = static::parentPlaceholderSalt(); @@ -189,7 +189,7 @@ public static function parentPlaceholder($section = '') * * @return string */ - protected static function parentPlaceholderSalt() + protected static function parentPlaceholderSalt(): string { if (! static::$parentPlaceholderSalt) { return static::$parentPlaceholderSalt = Str::random(40); @@ -204,7 +204,7 @@ protected static function parentPlaceholderSalt() * @param string $name * @return bool */ - public function hasSection($name) + public function hasSection(string $name): bool { return array_key_exists($name, $this->sections); } @@ -215,7 +215,7 @@ public function hasSection($name) * @param string $name * @return bool */ - public function sectionMissing($name) + public function sectionMissing(string $name): bool { return ! $this->hasSection($name); } @@ -227,7 +227,7 @@ public function sectionMissing($name) * @param string|null $default * @return mixed */ - public function getSection($name, $default = null) + public function getSection(string $name, ?string $default = null): mixed { return $this->getSections()[$name] ?? $default; } @@ -237,7 +237,7 @@ public function getSection($name, $default = null) * * @return array */ - public function getSections() + public function getSections(): array { return $this->sections; } @@ -247,7 +247,7 @@ public function getSections() * * @return void */ - public function flushSections() + public function flushSections(): void { $this->sections = []; $this->sectionStack = []; diff --git a/src/view/src/Concerns/ManagesLoops.php b/src/view/src/Concerns/ManagesLoops.php index 1a367f5b0..e154298a1 100644 --- a/src/view/src/Concerns/ManagesLoops.php +++ b/src/view/src/Concerns/ManagesLoops.php @@ -12,7 +12,7 @@ trait ManagesLoops * * @var array */ - protected $loopsStack = []; + protected array $loopsStack = []; /** * Add new loop to the stack. @@ -20,7 +20,7 @@ trait ManagesLoops * @param \Countable|array $data * @return void */ - public function addLoop($data) + public function addLoop(mixed $data): void { $length = is_countable($data) && ! $data instanceof LazyCollection ? count($data) @@ -47,7 +47,7 @@ public function addLoop($data) * * @return void */ - public function incrementLoopIndices() + public function incrementLoopIndices(): void { $loop = $this->loopsStack[$index = count($this->loopsStack) - 1]; @@ -67,7 +67,7 @@ public function incrementLoopIndices() * * @return void */ - public function popLoop() + public function popLoop(): void { array_pop($this->loopsStack); } @@ -77,7 +77,7 @@ public function popLoop() * * @return \stdClass|null */ - public function getLastLoop() + public function getLastLoop(): ?\stdClass { if ($last = Arr::last($this->loopsStack)) { return (object) $last; @@ -89,7 +89,7 @@ public function getLastLoop() * * @return array */ - public function getLoopStack() + public function getLoopStack(): array { return $this->loopsStack; } diff --git a/src/view/src/Concerns/ManagesStacks.php b/src/view/src/Concerns/ManagesStacks.php index 28ca7fa08..c1af45c40 100644 --- a/src/view/src/Concerns/ManagesStacks.php +++ b/src/view/src/Concerns/ManagesStacks.php @@ -11,21 +11,21 @@ trait ManagesStacks * * @var array */ - protected $pushes = []; + protected array $pushes = []; /** * All of the finished, captured prepend sections. * * @var array */ - protected $prepends = []; + protected array $prepends = []; /** * The stack of in-progress push sections. * * @var array */ - protected $pushStack = []; + protected array $pushStack = []; /** * Start injecting content into a push section. @@ -34,7 +34,7 @@ trait ManagesStacks * @param string $content * @return void */ - public function startPush($section, $content = '') + public function startPush(string $section, string $content = ''): void { if ($content === '') { if (ob_start()) { @@ -52,7 +52,7 @@ public function startPush($section, $content = '') * * @throws \InvalidArgumentException */ - public function stopPush() + public function stopPush(): string { if (empty($this->pushStack)) { throw new InvalidArgumentException('Cannot end a push stack without first starting one.'); @@ -70,7 +70,7 @@ public function stopPush() * @param string $content * @return void */ - protected function extendPush($section, $content) + protected function extendPush(string $section, string $content): void { if (! isset($this->pushes[$section])) { $this->pushes[$section] = []; @@ -90,7 +90,7 @@ protected function extendPush($section, $content) * @param string $content * @return void */ - public function startPrepend($section, $content = '') + public function startPrepend(string $section, string $content = ''): void { if ($content === '') { if (ob_start()) { @@ -108,7 +108,7 @@ public function startPrepend($section, $content = '') * * @throws \InvalidArgumentException */ - public function stopPrepend() + public function stopPrepend(): string { if (empty($this->pushStack)) { throw new InvalidArgumentException('Cannot end a prepend operation without first starting one.'); @@ -126,7 +126,7 @@ public function stopPrepend() * @param string $content * @return void */ - protected function extendPrepend($section, $content) + protected function extendPrepend(string $section, string $content): void { if (! isset($this->prepends[$section])) { $this->prepends[$section] = []; @@ -146,7 +146,7 @@ protected function extendPrepend($section, $content) * @param string $default * @return string */ - public function yieldPushContent($section, $default = '') + public function yieldPushContent(string $section, string $default = ''): string { if (! isset($this->pushes[$section]) && ! isset($this->prepends[$section])) { return $default; @@ -170,7 +170,7 @@ public function yieldPushContent($section, $default = '') * * @return void */ - public function flushStacks() + public function flushStacks(): void { $this->pushes = []; $this->prepends = []; diff --git a/src/view/src/Concerns/ManagesTranslations.php b/src/view/src/Concerns/ManagesTranslations.php index a41a76b8d..f0aaa467e 100644 --- a/src/view/src/Concerns/ManagesTranslations.php +++ b/src/view/src/Concerns/ManagesTranslations.php @@ -9,7 +9,7 @@ trait ManagesTranslations * * @var array */ - protected $translationReplacements = []; + protected array $translationReplacements = []; /** * Start a translation block. @@ -17,7 +17,7 @@ trait ManagesTranslations * @param array $replacements * @return void */ - public function startTranslation($replacements = []) + public function startTranslation(array $replacements = []): void { ob_start(); @@ -29,7 +29,7 @@ public function startTranslation($replacements = []) * * @return string */ - public function renderTranslation() + public function renderTranslation(): string { return $this->container->make('translator')->get( trim(ob_get_clean()), $this->translationReplacements diff --git a/src/view/src/DynamicComponent.php b/src/view/src/DynamicComponent.php index f25d4e71f..f86cd9384 100644 --- a/src/view/src/DynamicComponent.php +++ b/src/view/src/DynamicComponent.php @@ -14,21 +14,21 @@ class DynamicComponent extends Component * * @var string */ - public $component; + public string $component; /** * The component tag compiler instance. * - * @var \Illuminate\View\Compilers\BladeTagCompiler + * @var \Hypervel\View\Compilers\ComponentTagCompiler */ - protected static $compiler; + protected static ?ComponentTagCompiler $compiler = null; /** * The cached component classes. * * @var array */ - protected static $componentClasses = []; + protected static array $componentClasses = []; /** * Create a new component instance. @@ -36,7 +36,7 @@ class DynamicComponent extends Component * @param string $component * @return void */ - public function __construct(string $component) + public function __construct(string $component): void { $this->component = $component; } @@ -44,12 +44,12 @@ public function __construct(string $component) /** * Get the view / contents that represent the component. * - * @return \Illuminate\Contracts\View\View|string + * @return \Hypervel\Contracts\View\View|string */ - public function render() + public function render(): \Closure { $template = <<<'EOF' -getAttributes()))->mapWithKeys(function ($value, $key) { return [Illuminate\Support\Str::camel(str_replace([':', '.'], ' ', $key)) => $value]; })->all(), EXTR_SKIP); ?> +getAttributes()))->mapWithKeys(function ($value, $key) { return [Hypervel\Support\Str::camel(str_replace([':', '.'], ' ', $key)) => $value]; })->all(), EXTR_SKIP); ?> {{ props }} {{ slots }} @@ -88,7 +88,7 @@ class_exists($class) ? '{{ $attributes }}' : '', * @param array $bindings * @return string */ - protected function compileProps(array $bindings) + protected function compileProps(array $bindings): string { if (empty($bindings)) { return ''; @@ -105,7 +105,7 @@ protected function compileProps(array $bindings) * @param array $bindings * @return string */ - protected function compileBindings(array $bindings) + protected function compileBindings(array $bindings): string { return (new Collection($bindings)) ->map(fn ($key) => ':'.$key.'="$'.Str::camel(str_replace([':', '.'], ' ', $key)).'"') @@ -118,7 +118,7 @@ protected function compileBindings(array $bindings) * @param array $slots * @return string */ - protected function compileSlots(array $slots) + protected function compileSlots(array $slots): string { return (new Collection($slots)) ->map(fn ($slot, $name) => $name === '__default' ? null : 'attributes).'>{{ $'.$name.' }}') @@ -131,7 +131,7 @@ protected function compileSlots(array $slots) * * @return string */ - protected function classForComponent() + protected function classForComponent(): string { if (isset(static::$componentClasses[$this->component])) { return static::$componentClasses[$this->component]; @@ -147,7 +147,7 @@ protected function classForComponent() * @param string $class * @return array */ - protected function bindings(string $class) + protected function bindings(string $class): array { [$data, $attributes] = $this->compiler()->partitionDataAndAttributes($class, $this->attributes->getAttributes()); @@ -157,9 +157,9 @@ protected function bindings(string $class) /** * Get an instance of the Blade tag compiler. * - * @return \Illuminate\View\Compilers\ComponentTagCompiler + * @return \Hypervel\View\Compilers\ComponentTagCompiler */ - protected function compiler() + protected function compiler(): ComponentTagCompiler { if (! static::$compiler) { static::$compiler = new ComponentTagCompiler( diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index e9d0b40b9..cc113f5fb 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -17,16 +17,16 @@ class CompilerEngine extends PhpEngine /** * The Blade compiler instance. * - * @var \Illuminate\View\Compilers\CompilerInterface + * @var \Hypervel\View\Compilers\CompilerInterface */ - protected $compiler; + protected CompilerInterface $compiler; /** * A stack of the last compiled templates. * * @var array */ - protected $lastCompiled = []; + protected array $lastCompiled = []; /** * The view paths that were compiled or are not expired, keyed by the path. @@ -38,11 +38,11 @@ class CompilerEngine extends PhpEngine /** * Create a new compiler engine instance. * - * @param \Illuminate\View\Compilers\CompilerInterface $compiler - * @param \Illuminate\Filesystem\Filesystem|null $files + * @param \Hypervel\View\Compilers\CompilerInterface $compiler + * @param \Hypervel\Filesystem\Filesystem|null $files * @return void */ - public function __construct(CompilerInterface $compiler, ?Filesystem $files = null) + public function __construct(CompilerInterface $compiler, ?Filesystem $files = null): void { parent::__construct($files ?: new Filesystem); @@ -56,7 +56,7 @@ public function __construct(CompilerInterface $compiler, ?Filesystem $files = nu * @param array $data * @return string */ - public function get($path, array $data = []) + public function get(string $path, array $data = []): string { $this->lastCompiled[] = $path; @@ -103,7 +103,7 @@ public function get($path, array $data = []) * * @throws \Throwable */ - protected function handleViewException(Throwable $e, $obLevel) + protected function handleViewException(Throwable $e, int $obLevel): void { if ($e instanceof HttpException || $e instanceof HttpResponseException || @@ -123,7 +123,7 @@ protected function handleViewException(Throwable $e, $obLevel) * @param \Throwable $e * @return string */ - protected function getMessage(Throwable $e) + protected function getMessage(Throwable $e): string { return $e->getMessage().' (View: '.realpath(last($this->lastCompiled)).')'; } @@ -131,9 +131,9 @@ protected function getMessage(Throwable $e) /** * Get the compiler implementation. * - * @return \Illuminate\View\Compilers\CompilerInterface + * @return \Hypervel\View\Compilers\CompilerInterface */ - public function getCompiler() + public function getCompiler(): CompilerInterface { return $this->compiler; } @@ -143,7 +143,7 @@ public function getCompiler() * * @return void */ - public function forgetCompiledOrNotExpired() + public function forgetCompiledOrNotExpired(): void { $this->compiledOrNotExpired = []; } diff --git a/src/view/src/Engines/Engine.php b/src/view/src/Engines/Engine.php index 9e64010d6..6d88ddb6b 100755 --- a/src/view/src/Engines/Engine.php +++ b/src/view/src/Engines/Engine.php @@ -9,14 +9,14 @@ abstract class Engine * * @var string */ - protected $lastRendered; + protected ?string $lastRendered = null; /** * Get the last view that was rendered. * * @return string */ - public function getLastRendered() + public function getLastRendered(): ?string { return $this->lastRendered; } diff --git a/src/view/src/Engines/EngineResolver.php b/src/view/src/Engines/EngineResolver.php index 3d8768d09..7cb022aa7 100755 --- a/src/view/src/Engines/EngineResolver.php +++ b/src/view/src/Engines/EngineResolver.php @@ -12,14 +12,14 @@ class EngineResolver * * @var array */ - protected $resolvers = []; + protected array $resolvers = []; /** * The resolved engine instances. * * @var array */ - protected $resolved = []; + protected array $resolved = []; /** * Register a new engine resolver. @@ -30,7 +30,7 @@ class EngineResolver * @param \Closure $resolver * @return void */ - public function register($engine, Closure $resolver) + public function register(string $engine, Closure $resolver): void { $this->forget($engine); @@ -41,11 +41,11 @@ public function register($engine, Closure $resolver) * Resolve an engine instance by name. * * @param string $engine - * @return \Illuminate\Contracts\View\Engine + * @return \Hypervel\Contracts\View\Engine * * @throws \InvalidArgumentException */ - public function resolve($engine) + public function resolve(string $engine): mixed { if (isset($this->resolved[$engine])) { return $this->resolved[$engine]; @@ -64,7 +64,7 @@ public function resolve($engine) * @param string $engine * @return void */ - public function forget($engine) + public function forget(string $engine): void { unset($this->resolved[$engine]); } diff --git a/src/view/src/Engines/FileEngine.php b/src/view/src/Engines/FileEngine.php index d3bca7e1a..ad09e0ae4 100644 --- a/src/view/src/Engines/FileEngine.php +++ b/src/view/src/Engines/FileEngine.php @@ -10,17 +10,17 @@ class FileEngine implements Engine /** * The filesystem instance. * - * @var \Illuminate\Filesystem\Filesystem + * @var \Hypervel\Filesystem\Filesystem */ - protected $files; + protected Filesystem $files; /** * Create a new file engine instance. * - * @param \Illuminate\Filesystem\Filesystem $files + * @param \Hypervel\Filesystem\Filesystem $files * @return void */ - public function __construct(Filesystem $files) + public function __construct(Filesystem $files): void { $this->files = $files; } @@ -32,7 +32,7 @@ public function __construct(Filesystem $files) * @param array $data * @return string */ - public function get($path, array $data = []) + public function get(string $path, array $data = []): string { return $this->files->get($path); } diff --git a/src/view/src/Engines/PhpEngine.php b/src/view/src/Engines/PhpEngine.php index f071b6355..39805ff0d 100755 --- a/src/view/src/Engines/PhpEngine.php +++ b/src/view/src/Engines/PhpEngine.php @@ -11,17 +11,17 @@ class PhpEngine implements Engine /** * The filesystem instance. * - * @var \Illuminate\Filesystem\Filesystem + * @var \Hypervel\Filesystem\Filesystem */ - protected $files; + protected Filesystem $files; /** * Create a new file engine instance. * - * @param \Illuminate\Filesystem\Filesystem $files + * @param \Hypervel\Filesystem\Filesystem $files * @return void */ - public function __construct(Filesystem $files) + public function __construct(Filesystem $files): void { $this->files = $files; } @@ -33,7 +33,7 @@ public function __construct(Filesystem $files) * @param array $data * @return string */ - public function get($path, array $data = []) + public function get(string $path, array $data = []): string { return $this->evaluatePath($path, $data); } @@ -45,7 +45,7 @@ public function get($path, array $data = []) * @param array $data * @return string */ - protected function evaluatePath($path, $data) + protected function evaluatePath(string $path, array $data): string { $obLevel = ob_get_level(); @@ -72,7 +72,7 @@ protected function evaluatePath($path, $data) * * @throws \Throwable */ - protected function handleViewException(Throwable $e, $obLevel) + protected function handleViewException(Throwable $e, int $obLevel): void { while (ob_get_level() > $obLevel) { ob_end_clean(); diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index 5130f3d13..c66c3f4b6 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -5,6 +5,7 @@ use Hypervel\Contracts\Container\Container; use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Contracts\Support\Arrayable; +use Hypervel\Contracts\View\Engine; use Hypervel\Contracts\View\Factory as FactoryContract; use Hypervel\Support\Arr; use Hypervel\Support\Traits\Macroable; @@ -25,44 +26,44 @@ class Factory implements FactoryContract /** * The engine implementation. * - * @var \Illuminate\View\Engines\EngineResolver + * @var \Hypervel\View\Engines\EngineResolver */ - protected $engines; + protected EngineResolver $engines; /** * The view finder implementation. * - * @var \Illuminate\View\ViewFinderInterface + * @var \Hypervel\View\ViewFinderInterface */ - protected $finder; + protected ViewFinderInterface $finder; /** * The event dispatcher instance. * - * @var \Illuminate\Contracts\Events\Dispatcher + * @var \Hypervel\Contracts\Events\Dispatcher */ - protected $events; + protected Dispatcher $events; /** * The IoC container instance. * - * @var \Illuminate\Contracts\Container\Container + * @var \Hypervel\Contracts\Container\Container */ - protected $container; + protected Container $container; /** * Data that should be available to all templates. * * @var array */ - protected $shared = []; + protected array $shared = []; /** * The extension to engine bindings. * * @var array */ - protected $extensions = [ + protected array $extensions = [ 'blade.php' => 'blade', 'php' => 'php', 'css' => 'file', @@ -74,42 +75,42 @@ class Factory implements FactoryContract * * @var array */ - protected $composers = []; + protected array $composers = []; /** * The number of active rendering operations. * * @var int */ - protected $renderCount = 0; + protected int $renderCount = 0; /** * The "once" block IDs that have been rendered. * * @var array */ - protected $renderedOnce = []; + protected array $renderedOnce = []; /** * The cached array of engines for paths. * * @var array */ - protected $pathEngineCache = []; + protected array $pathEngineCache = []; /** * The cache of normalized names for views. * * @var array */ - protected $normalizedNameCache = []; + protected array $normalizedNameCache = []; /** * Create a new view factory instance. * - * @param \Illuminate\View\Engines\EngineResolver $engines - * @param \Illuminate\View\ViewFinderInterface $finder - * @param \Illuminate\Contracts\Events\Dispatcher $events + * @param \Hypervel\View\Engines\EngineResolver $engines + * @param \Hypervel\View\ViewFinderInterface $finder + * @param \Hypervel\Contracts\Events\Dispatcher $events * @return void */ public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events) @@ -125,11 +126,11 @@ public function __construct(EngineResolver $engines, ViewFinderInterface $finder * Get the evaluated view contents for the given view. * * @param string $path - * @param \Illuminate\Contracts\Support\Arrayable|array $data + * @param \Hypervel\Contracts\Support\Arrayable|array $data * @param array $mergeData - * @return \Illuminate\Contracts\View\View + * @return \Hypervel\Contracts\View\View */ - public function file($path, $data = [], $mergeData = []) + public function file(string $path, Arrayable|array $data = [], array $mergeData = []): View { $data = array_merge($mergeData, $this->parseData($data)); @@ -142,11 +143,11 @@ public function file($path, $data = [], $mergeData = []) * Get the evaluated view contents for the given view. * * @param string $view - * @param \Illuminate\Contracts\Support\Arrayable|array $data + * @param \Hypervel\Contracts\Support\Arrayable|array $data * @param array $mergeData - * @return \Illuminate\Contracts\View\View + * @return \Hypervel\Contracts\View\View */ - public function make($view, $data = [], $mergeData = []) + public function make(string $view, Arrayable|array $data = [], array $mergeData = []): View { $path = $this->finder->find( $view = $this->normalizeName($view) @@ -166,13 +167,13 @@ public function make($view, $data = [], $mergeData = []) * Get the first view that actually exists from the given list. * * @param array $views - * @param \Illuminate\Contracts\Support\Arrayable|array $data + * @param \Hypervel\Contracts\Support\Arrayable|array $data * @param array $mergeData - * @return \Illuminate\Contracts\View\View + * @return \Hypervel\Contracts\View\View * * @throws \InvalidArgumentException */ - public function first(array $views, $data = [], $mergeData = []) + public function first(array $views, Arrayable|array $data = [], array $mergeData = []): View { $view = Arr::first($views, function ($view) { return $this->exists($view); @@ -190,11 +191,11 @@ public function first(array $views, $data = [], $mergeData = []) * * @param bool $condition * @param string $view - * @param \Illuminate\Contracts\Support\Arrayable|array $data + * @param \Hypervel\Contracts\Support\Arrayable|array $data * @param array $mergeData * @return string */ - public function renderWhen($condition, $view, $data = [], $mergeData = []) + public function renderWhen(bool $condition, string $view, Arrayable|array $data = [], array $mergeData = []): string { if (! $condition) { return ''; @@ -208,11 +209,11 @@ public function renderWhen($condition, $view, $data = [], $mergeData = []) * * @param bool $condition * @param string $view - * @param \Illuminate\Contracts\Support\Arrayable|array $data + * @param \Hypervel\Contracts\Support\Arrayable|array $data * @param array $mergeData * @return string */ - public function renderUnless($condition, $view, $data = [], $mergeData = []) + public function renderUnless(bool $condition, string $view, Arrayable|array $data = [], array $mergeData = []): string { return $this->renderWhen(! $condition, $view, $data, $mergeData); } @@ -226,7 +227,7 @@ public function renderUnless($condition, $view, $data = [], $mergeData = []) * @param string $empty * @return string */ - public function renderEach($view, $data, $iterator, $empty = 'raw|') + public function renderEach(string $view, array $data, string $iterator, string $empty = 'raw|'): string { $result = ''; @@ -259,7 +260,7 @@ public function renderEach($view, $data, $iterator, $empty = 'raw|') * @param string $name * @return string */ - protected function normalizeName($name) + protected function normalizeName(string $name): string { return $this->normalizedNameCache[$name] ??= ViewName::normalize($name); } @@ -270,7 +271,7 @@ protected function normalizeName($name) * @param mixed $data * @return array */ - protected function parseData($data) + protected function parseData(mixed $data): array { return $data instanceof Arrayable ? $data->toArray() : $data; } @@ -280,10 +281,10 @@ protected function parseData($data) * * @param string $view * @param string $path - * @param \Illuminate\Contracts\Support\Arrayable|array $data - * @return \Illuminate\Contracts\View\View + * @param \Hypervel\Contracts\Support\Arrayable|array $data + * @return \Hypervel\Contracts\View\View */ - protected function viewInstance($view, $path, $data) + protected function viewInstance(string $view, string $path, Arrayable|array $data): View { return new View($this, $this->getEngineFromPath($path), $view, $path, $data); } @@ -294,7 +295,7 @@ protected function viewInstance($view, $path, $data) * @param string $view * @return bool */ - public function exists($view) + public function exists(string $view): bool { try { $this->finder->find($view); @@ -309,11 +310,11 @@ public function exists($view) * Get the appropriate view engine for the given path. * * @param string $path - * @return \Illuminate\Contracts\View\Engine + * @return \Hypervel\Contracts\View\Engine * * @throws \InvalidArgumentException */ - public function getEngineFromPath($path) + public function getEngineFromPath(string $path): Engine { if (isset($this->pathEngineCache[$path])) { return $this->engines->resolve($this->pathEngineCache[$path]); @@ -334,7 +335,7 @@ public function getEngineFromPath($path) * @param string $path * @return string|null */ - protected function getExtension($path) + protected function getExtension(string $path): ?string { $extensions = array_keys($this->extensions); @@ -350,7 +351,7 @@ protected function getExtension($path) * @param mixed|null $value * @return mixed */ - public function share($key, $value = null) + public function share(array|string $key, mixed $value = null): mixed { $keys = is_array($key) ? $key : [$key => $value]; @@ -366,7 +367,7 @@ public function share($key, $value = null) * * @return void */ - public function incrementRender() + public function incrementRender(): void { $this->renderCount++; } @@ -376,7 +377,7 @@ public function incrementRender() * * @return void */ - public function decrementRender() + public function decrementRender(): void { $this->renderCount--; } @@ -386,7 +387,7 @@ public function decrementRender() * * @return bool */ - public function doneRendering() + public function doneRendering(): bool { return $this->renderCount == 0; } @@ -408,7 +409,7 @@ public function hasRenderedOnce(string $id) * @param string $id * @return void */ - public function markAsRenderedOnce(string $id) + public function markAsRenderedOnce(string $id): void { $this->renderedOnce[$id] = true; } @@ -419,7 +420,7 @@ public function markAsRenderedOnce(string $id) * @param string $location * @return void */ - public function addLocation($location) + public function addLocation(string $location): void { $this->finder->addLocation($location); } @@ -430,7 +431,7 @@ public function addLocation($location) * @param string $location * @return void */ - public function prependLocation($location) + public function prependLocation(string $location): void { $this->finder->prependLocation($location); } @@ -442,7 +443,7 @@ public function prependLocation($location) * @param string|array $hints * @return $this */ - public function addNamespace($namespace, $hints) + public function addNamespace(string $namespace, string|array $hints): static { $this->finder->addNamespace($namespace, $hints); @@ -456,7 +457,7 @@ public function addNamespace($namespace, $hints) * @param string|array $hints * @return $this */ - public function prependNamespace($namespace, $hints) + public function prependNamespace(string $namespace, string|array $hints): static { $this->finder->prependNamespace($namespace, $hints); @@ -470,7 +471,7 @@ public function prependNamespace($namespace, $hints) * @param string|array $hints * @return $this */ - public function replaceNamespace($namespace, $hints) + public function replaceNamespace(string $namespace, string|array $hints): static { $this->finder->replaceNamespace($namespace, $hints); @@ -485,7 +486,7 @@ public function replaceNamespace($namespace, $hints) * @param \Closure|null $resolver * @return void */ - public function addExtension($extension, $engine, $resolver = null) + public function addExtension(string $extension, string $engine, \Closure $resolver = null): void { $this->finder->addExtension($extension); @@ -505,7 +506,7 @@ public function addExtension($extension, $engine, $resolver = null) * * @return void */ - public function flushState() + public function flushState(): void { $this->renderCount = 0; $this->renderedOnce = []; @@ -521,7 +522,7 @@ public function flushState() * * @return void */ - public function flushStateIfDoneRendering() + public function flushStateIfDoneRendering(): void { if ($this->doneRendering()) { $this->flushState(); @@ -533,7 +534,7 @@ public function flushStateIfDoneRendering() * * @return array */ - public function getExtensions() + public function getExtensions(): array { return $this->extensions; } @@ -541,9 +542,9 @@ public function getExtensions() /** * Get the engine resolver instance. * - * @return \Illuminate\View\Engines\EngineResolver + * @return \Hypervel\View\Engines\EngineResolver */ - public function getEngineResolver() + public function getEngineResolver(): EngineResolver { return $this->engines; } @@ -551,9 +552,9 @@ public function getEngineResolver() /** * Get the view finder instance. * - * @return \Illuminate\View\ViewFinderInterface + * @return \Hypervel\View\ViewFinderInterface */ - public function getFinder() + public function getFinder(): ViewFinderInterface { return $this->finder; } @@ -561,10 +562,10 @@ public function getFinder() /** * Set the view finder instance. * - * @param \Illuminate\View\ViewFinderInterface $finder + * @param \Hypervel\View\ViewFinderInterface $finder * @return void */ - public function setFinder(ViewFinderInterface $finder) + public function setFinder(ViewFinderInterface $finder): void { $this->finder = $finder; } @@ -574,7 +575,7 @@ public function setFinder(ViewFinderInterface $finder) * * @return void */ - public function flushFinderCache() + public function flushFinderCache(): void { $this->getFinder()->flush(); } @@ -582,9 +583,9 @@ public function flushFinderCache() /** * Get the event dispatcher instance. * - * @return \Illuminate\Contracts\Events\Dispatcher + * @return \Hypervel\Contracts\Events\Dispatcher */ - public function getDispatcher() + public function getDispatcher(): Dispatcher { return $this->events; } @@ -592,10 +593,10 @@ public function getDispatcher() /** * Set the event dispatcher instance. * - * @param \Illuminate\Contracts\Events\Dispatcher $events + * @param \Hypervel\Contracts\Events\Dispatcher $events * @return void */ - public function setDispatcher(Dispatcher $events) + public function setDispatcher(Dispatcher $events): void { $this->events = $events; } @@ -603,9 +604,9 @@ public function setDispatcher(Dispatcher $events) /** * Get the IoC container instance. * - * @return \Illuminate\Contracts\Container\Container + * @return \Hypervel\Contracts\Container\Container */ - public function getContainer() + public function getContainer(): Container { return $this->container; } @@ -613,10 +614,10 @@ public function getContainer() /** * Set the IoC container instance. * - * @param \Illuminate\Contracts\Container\Container $container + * @param \Hypervel\Contracts\Container\Container $container * @return void */ - public function setContainer(Container $container) + public function setContainer(Container $container): void { $this->container = $container; } @@ -628,7 +629,7 @@ public function setContainer(Container $container) * @param mixed $default * @return mixed */ - public function shared($key, $default = null) + public function shared(string $key, mixed $default = null): mixed { return Arr::get($this->shared, $key, $default); } @@ -638,7 +639,7 @@ public function shared($key, $default = null) * * @return array */ - public function getShared() + public function getShared(): array { return $this->shared; } diff --git a/src/view/src/FileViewFinder.php b/src/view/src/FileViewFinder.php index 9675bf694..e7845d073 100755 --- a/src/view/src/FileViewFinder.php +++ b/src/view/src/FileViewFinder.php @@ -9,48 +9,35 @@ class FileViewFinder implements ViewFinderInterface { /** * The filesystem instance. - * - * @var \Illuminate\Filesystem\Filesystem */ - protected $files; + protected Filesystem $files; /** * The array of active view paths. - * - * @var array */ - protected $paths; + protected array $paths; /** * The array of views that have been located. - * - * @var array */ - protected $views = []; + protected array $views = []; /** * The namespace to file path hints. - * - * @var array */ - protected $hints = []; + protected array $hints = []; /** * Register a view extension with the finder. * * @var string[] */ - protected $extensions = ['blade.php', 'php', 'css', 'html']; + protected array $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) + public function __construct(Filesystem $files, array $paths, ?array $extensions = null): void { $this->files = $files; $this->paths = array_map([$this, 'resolvePath'], $paths); @@ -62,11 +49,8 @@ public function __construct(Filesystem $files, array $paths, ?array $extensions /** * Get the fully qualified location of the view. - * - * @param string $name - * @return string */ - public function find($name) + public function find(string $name): string { if (isset($this->views[$name])) { return $this->views[$name]; @@ -81,11 +65,8 @@ public function find($name) /** * Get the path to a template with a named path. - * - * @param string $name - * @return string */ - protected function findNamespacedView($name) + protected function findNamespacedView(string $name): string { [$namespace, $view] = $this->parseNamespaceSegments($name); @@ -95,12 +76,9 @@ protected function findNamespacedView($name) /** * Get the segments of a template with a named path. * - * @param string $name - * @return array - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - protected function parseNamespaceSegments($name) + protected function parseNamespaceSegments(string $name): array { $segments = explode(static::HINT_PATH_DELIMITER, $name); @@ -118,13 +96,9 @@ protected function parseNamespaceSegments($name) /** * Find the given view in the list of paths. * - * @param string $name - * @param array $paths - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - protected function findInPaths($name, $paths) + protected function findInPaths(string $name, array $paths): string { foreach ((array) $paths as $path) { foreach ($this->getPossibleViewFiles($name) as $file) { @@ -141,56 +115,40 @@ protected function findInPaths($name, $paths) /** * Get an array of possible view files. - * - * @param string $name - * @return array */ - protected function getPossibleViewFiles($name) + protected function getPossibleViewFiles(string $name): array { 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) + public function addLocation(string $location): void { $this->paths[] = $this->resolvePath($location); } /** * Prepend a location to the finder. - * - * @param string $location - * @return void */ - public function prependLocation($location) + public function prependLocation(string $location): void { array_unshift($this->paths, $this->resolvePath($location)); } /** * Resolve the path. - * - * @param string $path - * @return string */ - protected function resolvePath($path) + protected function resolvePath(string $path): string { 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) + public function addNamespace(string $namespace, string|array $hints): void { $hints = (array) $hints; @@ -203,12 +161,8 @@ 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) + public function prependNamespace(string $namespace, string|array $hints): void { $hints = (array) $hints; @@ -221,23 +175,16 @@ 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) + public function replaceNamespace(string $namespace, string|array $hints): void { $this->hints[$namespace] = (array) $hints; } /** * Register an extension with the view finder. - * - * @param string $extension - * @return void */ - public function addExtension($extension) + public function addExtension(string $extension): void { if (($index = array_search($extension, $this->extensions)) !== false) { unset($this->extensions[$index]); @@ -248,42 +195,32 @@ public function addExtension($extension) /** * Returns whether or not the view name has any hint information. - * - * @param string $name - * @return bool */ - public function hasHintInformation($name) + public function hasHintInformation(string $name): bool { return strpos($name, static::HINT_PATH_DELIMITER) > 0; } /** * Flush the cache of located views. - * - * @return void */ - public function flush() + public function flush(): void { $this->views = []; } /** * Get the filesystem instance. - * - * @return \Illuminate\Filesystem\Filesystem */ - public function getFilesystem() + public function getFilesystem(): Filesystem { return $this->files; } /** * Set the active view paths. - * - * @param array $paths - * @return $this */ - public function setPaths($paths) + public function setPaths(array $paths): static { $this->paths = $paths; @@ -292,40 +229,32 @@ public function setPaths($paths) /** * Get the active view paths. - * - * @return array */ - public function getPaths() + public function getPaths(): array { return $this->paths; } /** * Get the views that have been located. - * - * @return array */ - public function getViews() + public function getViews(): array { return $this->views; } /** * Get the namespace to file path hints. - * - * @return array */ - public function getHints() + public function getHints(): array { return $this->hints; } /** * Get registered extensions. - * - * @return array */ - public function getExtensions() + public function getExtensions(): array { return $this->extensions; } diff --git a/src/view/src/InvokableComponentVariable.php b/src/view/src/InvokableComponentVariable.php index f5fd4f4d1..3bce7b1c6 100644 --- a/src/view/src/InvokableComponentVariable.php +++ b/src/view/src/InvokableComponentVariable.php @@ -17,7 +17,7 @@ class InvokableComponentVariable implements DeferringDisplayableValue, IteratorA * * @var \Closure */ - protected $callable; + protected Closure $callable; /** * Create a new variable instance. @@ -25,7 +25,7 @@ class InvokableComponentVariable implements DeferringDisplayableValue, IteratorA * @param \Closure $callable * @return void */ - public function __construct(Closure $callable) + public function __construct(Closure $callable): void { $this->callable = $callable; } @@ -33,9 +33,9 @@ public function __construct(Closure $callable) /** * Resolve the displayable value that the class is deferring. * - * @return \Illuminate\Contracts\Support\Htmlable|string + * @return \Hypervel\Contracts\Support\Htmlable|string */ - public function resolveDisplayableValue() + public function resolveDisplayableValue(): mixed { return $this->__invoke(); } @@ -58,7 +58,7 @@ public function getIterator(): Traversable * @param string $key * @return mixed */ - public function __get($key) + public function __get(string $key): mixed { return $this->__invoke()->{$key}; } @@ -70,7 +70,7 @@ public function __get($key) * @param array $parameters * @return mixed */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters): mixed { return $this->__invoke()->{$method}(...$parameters); } @@ -80,7 +80,7 @@ public function __call($method, $parameters) * * @return mixed */ - public function __invoke() + public function __invoke(): mixed { return call_user_func($this->callable); } @@ -90,7 +90,7 @@ public function __invoke() * * @return string */ - public function __toString() + public function __toString(): string { return (string) $this->__invoke(); } diff --git a/src/view/src/Middleware/ShareErrorsFromSession.php b/src/view/src/Middleware/ShareErrorsFromSession.php index 11d46cc95..8ad8d3ee4 100644 --- a/src/view/src/Middleware/ShareErrorsFromSession.php +++ b/src/view/src/Middleware/ShareErrorsFromSession.php @@ -11,14 +11,14 @@ class ShareErrorsFromSession /** * The view factory implementation. * - * @var \Illuminate\Contracts\View\Factory + * @var \Hypervel\Contracts\View\Factory */ - protected $view; + protected ViewFactory $view; /** * Create a new error binder instance. * - * @param \Illuminate\Contracts\View\Factory $view + * @param \Hypervel\Contracts\View\Factory $view * @return void */ public function __construct(ViewFactory $view) @@ -29,11 +29,11 @@ public function __construct(ViewFactory $view) /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request + * @param mixed $request * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(mixed $request, Closure $next): mixed { // 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 diff --git a/src/view/src/View.php b/src/view/src/View.php index dadbb5f61..12792c001 100755 --- a/src/view/src/View.php +++ b/src/view/src/View.php @@ -27,49 +27,49 @@ class View implements ArrayAccess, Htmlable, Stringable, ViewContract /** * The view factory instance. * - * @var \Illuminate\View\Factory + * @var \Hypervel\View\Factory */ - protected $factory; + protected Factory $factory; /** * The engine implementation. * - * @var \Illuminate\Contracts\View\Engine + * @var \Hypervel\Contracts\View\Engine */ - protected $engine; + protected Engine $engine; /** * The name of the view. * * @var string */ - protected $view; + protected string $view; /** * The array of view data. * * @var array */ - protected $data; + protected array $data; /** * The path to the view file. * * @var string */ - protected $path; + protected string $path; /** * Create a new view instance. * - * @param \Illuminate\View\Factory $factory - * @param \Illuminate\Contracts\View\Engine $engine + * @param \Hypervel\View\Factory $factory + * @param \Hypervel\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 = []) + public function __construct(Factory $factory, Engine $engine, string $view, string $path, mixed $data = []) { $this->view = $view; $this->path = $path; @@ -85,7 +85,7 @@ public function __construct(Factory $factory, Engine $engine, $view, $path, $dat * @param string $fragment * @return string */ - public function fragment($fragment) + public function fragment(string $fragment): string { return $this->render(function () use ($fragment) { return $this->factory->getFragment($fragment); @@ -98,7 +98,7 @@ public function fragment($fragment) * @param array|null $fragments * @return string */ - public function fragments(?array $fragments = null) + public function fragments(?array $fragments = null): string { return is_null($fragments) ? $this->allFragments() @@ -112,7 +112,7 @@ public function fragments(?array $fragments = null) * @param string $fragment * @return string */ - public function fragmentIf($boolean, $fragment) + public function fragmentIf(bool $boolean, string $fragment): string { if (value($boolean)) { return $this->fragment($fragment); @@ -128,7 +128,7 @@ public function fragmentIf($boolean, $fragment) * @param array|null $fragments * @return string */ - public function fragmentsIf($boolean, ?array $fragments = null) + public function fragmentsIf(bool $boolean, ?array $fragments = null): string { if (value($boolean)) { return $this->fragments($fragments); @@ -142,7 +142,7 @@ public function fragmentsIf($boolean, ?array $fragments = null) * * @return string */ - protected function allFragments() + protected function allFragments(): string { return (new Collection($this->render(fn () => $this->factory->getFragments())))->implode(''); } @@ -155,7 +155,7 @@ protected function allFragments() * * @throws \Throwable */ - public function render(?callable $callback = null) + public function render(?callable $callback = null): string { try { $contents = $this->renderContents(); @@ -180,7 +180,7 @@ public function render(?callable $callback = null) * * @return string */ - protected function renderContents() + protected function renderContents(): string { // 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 @@ -275,7 +275,7 @@ public function nest($key, $view, array $data = []) /** * Add validation errors to the view. * - * @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider + * @param \Hypervel\Contracts\Support\MessageProvider|array|string $provider * @param string $bag * @return $this */ @@ -289,8 +289,8 @@ public function withErrors($provider, $bag = 'default') /** * Parse the given errors into an appropriate value. * - * @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider - * @return \Illuminate\Support\MessageBag + * @param \Hypervel\Contracts\Support\MessageProvider|array|string $provider + * @return \Hypervel\Support\MessageBag */ protected function formatErrors($provider) { @@ -353,7 +353,7 @@ public function setPath($path) /** * Get the view factory instance. * - * @return \Illuminate\View\Factory + * @return \Hypervel\View\Factory */ public function getFactory() { @@ -363,7 +363,7 @@ public function getFactory() /** * Get the view's rendering engine. * - * @return \Illuminate\Contracts\View\Engine + * @return \Hypervel\Contracts\View\Engine */ public function getEngine() { @@ -465,7 +465,7 @@ public function __unset($key) * * @param string $method * @param array $parameters - * @return \Illuminate\View\View + * @return \Hypervel\View\View * * @throws \BadMethodCallException */ diff --git a/src/view/src/ViewException.php b/src/view/src/ViewException.php index 3ef62221f..a52d31daf 100644 --- a/src/view/src/ViewException.php +++ b/src/view/src/ViewException.php @@ -13,7 +13,7 @@ class ViewException extends ErrorException * * @return bool|null */ - public function report() + public function report(): bool|null { $exception = $this->getPrevious(); @@ -27,10 +27,10 @@ public function report() /** * Render the exception into an HTTP response. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response|null + * @param \Hypervel\Http\Request $request + * @return \Hypervel\Http\Response|null */ - public function render($request) + public function render($request): mixed { $exception = $this->getPrevious(); diff --git a/src/view/src/ViewFinderInterface.php b/src/view/src/ViewFinderInterface.php index 93572e747..070f700f4 100755 --- a/src/view/src/ViewFinderInterface.php +++ b/src/view/src/ViewFinderInterface.php @@ -6,66 +6,41 @@ interface ViewFinderInterface { /** * Hint path delimiter value. - * - * @var string */ - const HINT_PATH_DELIMITER = '::'; + const string HINT_PATH_DELIMITER = '::'; /** * Get the fully qualified location of the view. - * - * @param string $view - * @return string */ - public function find($view); + public function find(string $view): string; /** * Add a location to the finder. - * - * @param string $location - * @return void */ - public function addLocation($location); + public function addLocation(string $location): void; /** * Add a namespace hint to the finder. - * - * @param string $namespace - * @param string|array $hints - * @return void */ - public function addNamespace($namespace, $hints); + public function addNamespace(string $namespace, string|array $hints): void; /** * Prepend a namespace hint to the finder. - * - * @param string $namespace - * @param string|array $hints - * @return void */ - public function prependNamespace($namespace, $hints); + public function prependNamespace(string $namespace, string|array $hints): void; /** * Replace the namespace hints for the given namespace. - * - * @param string $namespace - * @param string|array $hints - * @return void */ - public function replaceNamespace($namespace, $hints); + public function replaceNamespace(string $namespace, string|array $hints): void; /** * Add a valid view extension to the finder. - * - * @param string $extension - * @return void */ - public function addExtension($extension); + public function addExtension(string $extension): void; /** * Flush the cache of located views. - * - * @return void */ - public function flush(); + public function flush(): void; } diff --git a/src/view/src/ViewName.php b/src/view/src/ViewName.php index b070f2602..c635f454b 100644 --- a/src/view/src/ViewName.php +++ b/src/view/src/ViewName.php @@ -10,7 +10,7 @@ class ViewName * @param string $name * @return string */ - public static function normalize($name) + public static function normalize(string $name): string { $delimiter = ViewFinderInterface::HINT_PATH_DELIMITER; diff --git a/src/view/src/ViewServiceProvider.php b/src/view/src/ViewServiceProvider.php index 84301241d..900400a06 100755 --- a/src/view/src/ViewServiceProvider.php +++ b/src/view/src/ViewServiceProvider.php @@ -17,7 +17,7 @@ class ViewServiceProvider extends ServiceProvider * * @return void */ - public function register() + public function register(): void { $this->registerFactory(); $this->registerViewFinder(); @@ -34,7 +34,7 @@ public function register() * * @return void */ - public function registerFactory() + public function registerFactory(): void { $this->app->singleton('view', function ($app) { // Next we need to grab the engine resolver instance that will be used by the @@ -64,12 +64,12 @@ public function registerFactory() /** * 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 + * @param \Hypervel\View\Engines\EngineResolver $resolver + * @param \Hypervel\View\ViewFinderInterface $finder + * @param \Hypervel\Contracts\Events\Dispatcher $events + * @return \Hypervel\View\Factory */ - protected function createFactory($resolver, $finder, $events) + protected function createFactory($resolver, $finder, $events): Factory { return new Factory($resolver, $finder, $events); } @@ -79,7 +79,7 @@ protected function createFactory($resolver, $finder, $events) * * @return void */ - public function registerViewFinder() + public function registerViewFinder(): void { $this->app->bind('view.finder', function ($app) { return new FileViewFinder($app['files'], $app['config']['view.paths']); @@ -91,7 +91,7 @@ public function registerViewFinder() * * @return void */ - public function registerBladeCompiler() + public function registerBladeCompiler(): void { $this->app->singleton('blade.compiler', function ($app) { return tap(new BladeCompiler( @@ -111,7 +111,7 @@ public function registerBladeCompiler() * * @return void */ - public function registerEngineResolver() + public function registerEngineResolver(): void { $this->app->singleton('view.engine.resolver', function () { $resolver = new EngineResolver; @@ -130,10 +130,10 @@ public function registerEngineResolver() /** * Register the file engine implementation. * - * @param \Illuminate\View\Engines\EngineResolver $resolver + * @param \Hypervel\View\Engines\EngineResolver $resolver * @return void */ - public function registerFileEngine($resolver) + public function registerFileEngine(EngineResolver $resolver): void { $resolver->register('file', function () { return new FileEngine(Container::getInstance()->make('files')); @@ -143,10 +143,10 @@ public function registerFileEngine($resolver) /** * Register the PHP engine implementation. * - * @param \Illuminate\View\Engines\EngineResolver $resolver + * @param \Hypervel\View\Engines\EngineResolver $resolver * @return void */ - public function registerPhpEngine($resolver) + public function registerPhpEngine(EngineResolver $resolver): void { $resolver->register('php', function () { return new PhpEngine(Container::getInstance()->make('files')); @@ -156,10 +156,10 @@ public function registerPhpEngine($resolver) /** * Register the Blade engine implementation. * - * @param \Illuminate\View\Engines\EngineResolver $resolver + * @param \Hypervel\View\Engines\EngineResolver $resolver * @return void */ - public function registerBladeEngine($resolver) + public function registerBladeEngine(EngineResolver $resolver): void { $resolver->register('blade', function () { $app = Container::getInstance(); From a2a12fb2632daf8316ec2f6698d2c8c7a2e711df Mon Sep 17 00:00:00 2001 From: Yu Shing Date: Wed, 15 Oct 2025 11:26:54 +0800 Subject: [PATCH 04/21] feat: copy test from Laravel --- tests/View/Blade/AbstractBladeTestCase.php | 42 + tests/View/Blade/BladeAppendTest.php | 11 + tests/View/Blade/BladeBoolTest.php | 67 + tests/View/Blade/BladeBreakStatementsTest.php | 71 ++ tests/View/Blade/BladeCanStatementsTest.php | 21 + .../View/Blade/BladeCananyStatementsTest.php | 21 + .../View/Blade/BladeCannotStatementsTest.php | 21 + .../View/Blade/BladeCheckedStatementsTest.php | 46 + tests/View/Blade/BladeClassTest.php | 14 + tests/View/Blade/BladeCommentsTest.php | 27 + tests/View/Blade/BladeComponentFirstTest.php | 12 + .../Blade/BladeComponentTagCompilerTest.php | 1000 +++++++++++++++ tests/View/Blade/BladeComponentsTest.php | 85 ++ .../Blade/BladeContinueStatementsTest.php | 71 ++ tests/View/Blade/BladeCustomTest.php | 233 ++++ tests/View/Blade/BladeEchoHandlerTest.php | 126 ++ tests/View/Blade/BladeEchoTest.php | 66 + .../Blade/BladeElseAuthStatementsTest.php | 36 + .../Blade/BladeElseGuestStatementsTest.php | 21 + .../View/Blade/BladeElseIfStatementsTest.php | 21 + tests/View/Blade/BladeElseStatementsTest.php | 36 + tests/View/Blade/BladeEndSectionsTest.php | 11 + .../Blade/BladeEnvironmentStatementsTest.php | 66 + tests/View/Blade/BladeErrorTest.php | 47 + tests/View/Blade/BladeEscapedTest.php | 36 + tests/View/Blade/BladeExpressionTest.php | 18 + tests/View/Blade/BladeExtendsTest.php | 43 + tests/View/Blade/BladeForStatementsTest.php | 32 + .../View/Blade/BladeForeachStatementsTest.php | 121 ++ .../View/Blade/BladeForelseStatementsTest.php | 109 ++ tests/View/Blade/BladeFragmentTest.php | 17 + tests/View/Blade/BladeHasSectionTest.php | 17 + tests/View/Blade/BladeHelpersTest.php | 20 + .../View/Blade/BladeIfAuthStatementsTest.php | 28 + .../View/Blade/BladeIfEmptyStatementsTest.php | 17 + .../View/Blade/BladeIfGuestStatementsTest.php | 17 + .../View/Blade/BladeIfIssetStatementsTest.php | 17 + tests/View/Blade/BladeIfStatementsTest.php | 56 + tests/View/Blade/BladeIncludesTest.php | 49 + tests/View/Blade/BladeInjectTest.php | 34 + tests/View/Blade/BladeJsTest.php | 30 + tests/View/Blade/BladeJsonTest.php | 22 + tests/View/Blade/BladeLangTest.php | 19 + .../View/Blade/BladeOverwriteSectionTest.php | 11 + tests/View/Blade/BladePhpStatementsTest.php | 181 +++ tests/View/Blade/BladePrependTest.php | 50 + tests/View/Blade/BladePropsTest.php | 63 + tests/View/Blade/BladePushTest.php | 92 ++ tests/View/Blade/BladeSectionMissingTest.php | 17 + tests/View/Blade/BladeSectionTest.php | 13 + tests/View/Blade/BladeSessionTest.php | 27 + tests/View/Blade/BladeShowTest.php | 11 + tests/View/Blade/BladeStackTest.php | 17 + tests/View/Blade/BladeStopSectionTest.php | 11 + tests/View/Blade/BladeStyleTest.php | 14 + .../View/Blade/BladeUnlessStatementsTest.php | 17 + tests/View/Blade/BladeUnsetStatementsTest.php | 17 + tests/View/Blade/BladeUseTest.php | 34 + tests/View/Blade/BladeVerbatimTest.php | 104 ++ tests/View/Blade/BladeWhileStatementsTest.php | 32 + tests/View/Blade/BladeYieldTest.php | 13 + tests/View/ComponentTest.php | 502 ++++++++ tests/View/ViewBladeCompilerTest.php | 270 ++++ tests/View/ViewCompilerEngineTest.php | 302 +++++ tests/View/ViewComponentAttributeBagTest.php | 156 +++ tests/View/ViewComponentTest.php | 258 ++++ tests/View/ViewEngineResolverTest.php | 30 + tests/View/ViewFactoryTest.php | 1101 +++++++++++++++++ tests/View/ViewFileViewFinderTest.php | 170 +++ tests/View/ViewPhpEngineTest.php | 17 + tests/View/ViewTest.php | 251 ++++ tests/View/fixtures/basic.php | 2 + tests/View/fixtures/component.php | 1 + tests/View/fixtures/http-exception.php | 5 + tests/View/fixtures/namespaced/basic.php | 1 + tests/View/fixtures/nested/basic.php | 0 tests/View/fixtures/nested/child.php | 1 + tests/View/fixtures/regular-exception.php | 3 + .../fixtures/section-exception-layout.php | 3 + tests/View/fixtures/section-exception.php | 4 + 80 files changed, 6675 insertions(+) create mode 100644 tests/View/Blade/AbstractBladeTestCase.php create mode 100644 tests/View/Blade/BladeAppendTest.php create mode 100644 tests/View/Blade/BladeBoolTest.php create mode 100644 tests/View/Blade/BladeBreakStatementsTest.php create mode 100644 tests/View/Blade/BladeCanStatementsTest.php create mode 100644 tests/View/Blade/BladeCananyStatementsTest.php create mode 100644 tests/View/Blade/BladeCannotStatementsTest.php create mode 100644 tests/View/Blade/BladeCheckedStatementsTest.php create mode 100644 tests/View/Blade/BladeClassTest.php create mode 100644 tests/View/Blade/BladeCommentsTest.php create mode 100644 tests/View/Blade/BladeComponentFirstTest.php create mode 100644 tests/View/Blade/BladeComponentTagCompilerTest.php create mode 100644 tests/View/Blade/BladeComponentsTest.php create mode 100644 tests/View/Blade/BladeContinueStatementsTest.php create mode 100644 tests/View/Blade/BladeCustomTest.php create mode 100644 tests/View/Blade/BladeEchoHandlerTest.php create mode 100644 tests/View/Blade/BladeEchoTest.php create mode 100644 tests/View/Blade/BladeElseAuthStatementsTest.php create mode 100644 tests/View/Blade/BladeElseGuestStatementsTest.php create mode 100644 tests/View/Blade/BladeElseIfStatementsTest.php create mode 100644 tests/View/Blade/BladeElseStatementsTest.php create mode 100644 tests/View/Blade/BladeEndSectionsTest.php create mode 100644 tests/View/Blade/BladeEnvironmentStatementsTest.php create mode 100644 tests/View/Blade/BladeErrorTest.php create mode 100644 tests/View/Blade/BladeEscapedTest.php create mode 100644 tests/View/Blade/BladeExpressionTest.php create mode 100644 tests/View/Blade/BladeExtendsTest.php create mode 100644 tests/View/Blade/BladeForStatementsTest.php create mode 100644 tests/View/Blade/BladeForeachStatementsTest.php create mode 100644 tests/View/Blade/BladeForelseStatementsTest.php create mode 100644 tests/View/Blade/BladeFragmentTest.php create mode 100644 tests/View/Blade/BladeHasSectionTest.php create mode 100644 tests/View/Blade/BladeHelpersTest.php create mode 100644 tests/View/Blade/BladeIfAuthStatementsTest.php create mode 100644 tests/View/Blade/BladeIfEmptyStatementsTest.php create mode 100644 tests/View/Blade/BladeIfGuestStatementsTest.php create mode 100644 tests/View/Blade/BladeIfIssetStatementsTest.php create mode 100644 tests/View/Blade/BladeIfStatementsTest.php create mode 100644 tests/View/Blade/BladeIncludesTest.php create mode 100644 tests/View/Blade/BladeInjectTest.php create mode 100644 tests/View/Blade/BladeJsTest.php create mode 100644 tests/View/Blade/BladeJsonTest.php create mode 100644 tests/View/Blade/BladeLangTest.php create mode 100644 tests/View/Blade/BladeOverwriteSectionTest.php create mode 100644 tests/View/Blade/BladePhpStatementsTest.php create mode 100644 tests/View/Blade/BladePrependTest.php create mode 100644 tests/View/Blade/BladePropsTest.php create mode 100644 tests/View/Blade/BladePushTest.php create mode 100644 tests/View/Blade/BladeSectionMissingTest.php create mode 100644 tests/View/Blade/BladeSectionTest.php create mode 100644 tests/View/Blade/BladeSessionTest.php create mode 100644 tests/View/Blade/BladeShowTest.php create mode 100644 tests/View/Blade/BladeStackTest.php create mode 100644 tests/View/Blade/BladeStopSectionTest.php create mode 100644 tests/View/Blade/BladeStyleTest.php create mode 100644 tests/View/Blade/BladeUnlessStatementsTest.php create mode 100644 tests/View/Blade/BladeUnsetStatementsTest.php create mode 100644 tests/View/Blade/BladeUseTest.php create mode 100644 tests/View/Blade/BladeVerbatimTest.php create mode 100644 tests/View/Blade/BladeWhileStatementsTest.php create mode 100644 tests/View/Blade/BladeYieldTest.php create mode 100644 tests/View/ComponentTest.php create mode 100644 tests/View/ViewBladeCompilerTest.php create mode 100755 tests/View/ViewCompilerEngineTest.php create mode 100644 tests/View/ViewComponentAttributeBagTest.php create mode 100644 tests/View/ViewComponentTest.php create mode 100755 tests/View/ViewEngineResolverTest.php create mode 100755 tests/View/ViewFactoryTest.php create mode 100755 tests/View/ViewFileViewFinderTest.php create mode 100755 tests/View/ViewPhpEngineTest.php create mode 100755 tests/View/ViewTest.php create mode 100755 tests/View/fixtures/basic.php create mode 100644 tests/View/fixtures/component.php create mode 100644 tests/View/fixtures/http-exception.php create mode 100755 tests/View/fixtures/namespaced/basic.php create mode 100755 tests/View/fixtures/nested/basic.php create mode 100755 tests/View/fixtures/nested/child.php create mode 100644 tests/View/fixtures/regular-exception.php create mode 100644 tests/View/fixtures/section-exception-layout.php create mode 100644 tests/View/fixtures/section-exception.php diff --git a/tests/View/Blade/AbstractBladeTestCase.php b/tests/View/Blade/AbstractBladeTestCase.php new file mode 100644 index 000000000..93e073940 --- /dev/null +++ b/tests/View/Blade/AbstractBladeTestCase.php @@ -0,0 +1,42 @@ +compiler = new BladeCompiler($this->getFiles(), __DIR__); + } + + protected function tearDown(): void + { + Container::setInstance(null); + Component::flushCache(); + Component::forgetComponentsResolver(); + Component::forgetFactory(); + + m::close(); + + parent::tearDown(); + } + + protected function getFiles() + { + return m::mock(Filesystem::class); + } +} diff --git a/tests/View/Blade/BladeAppendTest.php b/tests/View/Blade/BladeAppendTest.php new file mode 100644 index 000000000..9be80cb68 --- /dev/null +++ b/tests/View/Blade/BladeAppendTest.php @@ -0,0 +1,11 @@ +assertSame('appendSection(); ?>', $this->compiler->compileString('@append')); + } +} diff --git a/tests/View/Blade/BladeBoolTest.php b/tests/View/Blade/BladeBoolTest.php new file mode 100644 index 000000000..8fb87f6cb --- /dev/null +++ b/tests/View/Blade/BladeBoolTest.php @@ -0,0 +1,67 @@ +}"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + // For Javascript object{'isBool' : false} + $string = "{'isBool' : @bool(false)}"; + $expected = "{'isBool' : }"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + // For Alpine.js x-show attribute + $string = ""; + $expected = "' />"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + // For Alpine.js x-show attribute + $string = ""; + $expected = "' />"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCompileBool(): void + { + $someViewVarTruthy = 123; + $compiled = $this->compiler->compileString('@bool($someViewVarTruthy)'); + + ob_start(); + eval(substr($compiled, 6, -3)); + $this->assertEquals('true', ob_get_clean()); + + $someViewVarFalsey = '0'; + $compiled = $this->compiler->compileString('@bool($someViewVarFalsey)'); + + ob_start(); + eval(substr($compiled, 6, -3)); + $this->assertEquals('false', ob_get_clean()); + + $anotherSomeViewVarTruthy = new SomeClass(); + $compiled = $this->compiler->compileString('@bool($anotherSomeViewVarTruthy)'); + + ob_start(); + eval(substr($compiled, 6, -3)); + $this->assertEquals('true', ob_get_clean()); + + $anotherSomeViewVarFalsey = null; + $compiled = $this->compiler->compileString('@bool($anotherSomeViewVarFalsey)'); + + ob_start(); + eval(substr($compiled, 6, -3)); + $this->assertEquals('false', ob_get_clean()); + } +} + +class SomeClass +{ + public function someMethod() + { + } +} diff --git a/tests/View/Blade/BladeBreakStatementsTest.php b/tests/View/Blade/BladeBreakStatementsTest.php new file mode 100644 index 000000000..639a17038 --- /dev/null +++ b/tests/View/Blade/BladeBreakStatementsTest.php @@ -0,0 +1,71 @@ + +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithExpressionAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break(TRUE) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break(2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithSpacedArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break( 2 ) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithFaultyArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break(-2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCanStatementsTest.php b/tests/View/Blade/BladeCanStatementsTest.php new file mode 100644 index 000000000..39b74ecb3 --- /dev/null +++ b/tests/View/Blade/BladeCanStatementsTest.php @@ -0,0 +1,21 @@ +check(\'update\', [$post])): ?> +breeze +check(\'delete\', [$post])): ?> +sneeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCananyStatementsTest.php b/tests/View/Blade/BladeCananyStatementsTest.php new file mode 100644 index 000000000..59a67b3b6 --- /dev/null +++ b/tests/View/Blade/BladeCananyStatementsTest.php @@ -0,0 +1,21 @@ +any([\'create\', \'update\'], [$post])): ?> +breeze +any([\'delete\', \'approve\'], [$post])): ?> +sneeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCannotStatementsTest.php b/tests/View/Blade/BladeCannotStatementsTest.php new file mode 100644 index 000000000..bab28e4d0 --- /dev/null +++ b/tests/View/Blade/BladeCannotStatementsTest.php @@ -0,0 +1,21 @@ +denies(\'update\', [$post])): ?> +breeze +denies(\'delete\', [$post])): ?> +sneeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCheckedStatementsTest.php b/tests/View/Blade/BladeCheckedStatementsTest.php new file mode 100644 index 000000000..fd76b3656 --- /dev/null +++ b/tests/View/Blade/BladeCheckedStatementsTest.php @@ -0,0 +1,46 @@ +'; + $expected = "/>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCheckedStatementsAreCompiled() + { + $string = ''; + $expected = "/>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testDisabledStatementsAreCompiled() + { + $string = ''; + $expected = ""; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testRequiredStatementsAreCompiled() + { + $string = ''; + $expected = "/>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testReadonlyStatementsAreCompiled() + { + $string = ''; + $expected = "/>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeClassTest.php b/tests/View/Blade/BladeClassTest.php new file mode 100644 index 000000000..edf1c9cb8 --- /dev/null +++ b/tests/View/Blade/BladeClassTest.php @@ -0,0 +1,14 @@ + true, 'mr-2' => false])>"; + $expected = " true, 'mr-2' => false]); ?>\">"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCommentsTest.php b/tests/View/Blade/BladeCommentsTest.php new file mode 100644 index 000000000..c5613a2c2 --- /dev/null +++ b/tests/View/Blade/BladeCommentsTest.php @@ -0,0 +1,27 @@ +assertEmpty($this->compiler->compileString($string)); + + $string = '{{-- +this is a comment +--}}'; + $this->assertEmpty($this->compiler->compileString($string)); + + $string = sprintf('{{-- this is an %s long comment --}}', str_repeat('extremely ', 1000)); + $this->assertEmpty($this->compiler->compileString($string)); + } + + public function testBladeCodeInsideCommentsIsNotCompiled() + { + $string = '{{-- @foreach() --}}'; + + $this->assertEmpty($this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeComponentFirstTest.php b/tests/View/Blade/BladeComponentFirstTest.php new file mode 100644 index 000000000..c8e20cb7e --- /dev/null +++ b/tests/View/Blade/BladeComponentFirstTest.php @@ -0,0 +1,12 @@ +assertSame('startComponentFirst(["one", "two"]); ?>', $this->compiler->compileString('@componentFirst(["one", "two"])')); + $this->assertSame('startComponentFirst(["one", "two"], ["foo" => "bar"]); ?>', $this->compiler->compileString('@componentFirst(["one", "two"], ["foo" => "bar"])')); + } +} diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php new file mode 100644 index 000000000..5ebfbb104 --- /dev/null +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -0,0 +1,1000 @@ +mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot('foo', null, []) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testInlineSlotsCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot('foo', null, []) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testDynamicSlotsCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot(\$foo, null, []) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testDynamicSlotsCanBeCompiledWithKeyOfObjects() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot(\$foo->name, null, []) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testSlotsWithAttributesCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot('foo', null, ['class' => 'font-bold']) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testInlineSlotsWithAttributesCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot('foo', null, ['class' => 'font-bold']) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testSlotsWithDynamicAttributesCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot('foo', null, ['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$classes)]) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testSlotsWithClassDirectiveCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot('foo', null, ['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(\$classes))]) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testSlotsWithStyleDirectiveCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame( + "@slot('foo', null, ['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(\$styles))]) \n".' @endslot', + str_replace("\r\n", "\n", trim($result)) + ); + } + + public function testBasicComponentParsing() + { + $this->mockViewFactory(); + + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); + + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes(['type' => 'foo','limit' => '5','@click' => 'foo','wire:click' => 'changePlan(\''.e(\$plan).'\')','required' => true,'x-intersect.margin.-50%.0px' => 'visibleSection = \'profile\'']); ?>\n". +"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); + } + + public function testNestedDefaultComponentParsing() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + Container::setInstance($container); + + $result = $this->compiler()->compileTags('
'); + + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('App\View\Components\Card\Card', 'card', []) + +except(\App\View\Components\Card\Card::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); + } + + public function testBasicComponentWithEmptyAttributesParsing() + { + $this->mockViewFactory(); + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); + + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes(['type' => '','limit' => '','@click' => '','required' => true]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); + } + + public function testDataCamelCasing() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => '1']) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testColonData() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testColonDataShortSyntax() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testColonDataWithStaticClassProperty() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testColonDataWithStaticClassPropertyAndMultipleAttributes() + { + $this->mockViewFactory(); + $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + + $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testSelfClosingComponentWithColonDataShortSyntax() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testSelfClosingComponentWithColonDataAndStaticClassPropertyShortSyntax() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testSelfClosingComponentWithColonDataMultipleAttributesAndStaticClassPropertyShortSyntax() + { + $this->mockViewFactory(); + $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + + $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testEscapedColonAttribute() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([':title' => 'user.name']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testColonAttributesIsEscapedIfStrings() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testClassDirective() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testStyleDirective() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testColonNestedComponentParsing() + { + $this->mockViewFactory(); + $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testColonStartingNestedComponentParsing() + { + $this->mockViewFactory(); + $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testSelfClosingComponentsCanBeCompiled() + { + $this->mockViewFactory(); + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); + + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); + } + + public function testClassNamesCanBeGuessed() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + Container::setInstance($container); + + $result = $this->compiler()->guessClassName('alert'); + + $this->assertSame("App\View\Components\Alert", trim($result)); + + Container::setInstance(null); + } + + public function testClassNamesCanBeGuessedWithNamespaces() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + Container::setInstance($container); + + $result = $this->compiler()->guessClassName('base.alert'); + + $this->assertSame("App\View\Components\Base\Alert", trim($result)); + + Container::setInstance(null); + } + + public function testComponentsCanBeCompiledWithHyphenAttributes() + { + $this->mockViewFactory(); + + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes(['class' => 'bar','wire:model' => 'foo','x-on:click' => 'bar','@click' => 'baz']); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testSelfClosingComponentsCanBeCompiledWithDataAndAttributes() + { + $this->mockViewFactory(); + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes(['class' => 'bar','wire:model' => 'foo']); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testComponentCanReceiveAttributeBag() + { + $this->mockViewFactory(); + + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes(['class' => 'bar','attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes),'wire:model' => 'foo']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testSelfClosingComponentCanReceiveAttributeBag() + { + $this->mockViewFactory(); + + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
merge([\'class\' => \'test\']) }} wire:model="foo" />
'); + + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes(['class' => 'bar','attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes->merge(['class' => 'test'])),'wire:model' => 'foo']); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); + } + + public function testComponentsCanHaveAttachedWord() + { + $this->mockViewFactory(); + $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('Words'); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##Words", trim($result)); + } + + public function testSelfClosingComponentsCanHaveAttachedWord() + { + $this->mockViewFactory(); + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('Words'); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##Words', trim($result)); + } + + public function testSelfClosingComponentsCanBeCompiledWithBoundData() + { + $this->mockViewFactory(); + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => \$title]) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes(['class' => 'bar']); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testPairedComponentTags() + { + $this->mockViewFactory(); + $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(' +'); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?> + @endComponentClass##END-COMPONENT-CLASS##", trim($result)); + } + + public function testClasslessComponents() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + $factory->shouldReceive('exists')->once()->andReturn(true); + Container::setInstance($container); + + $result = $this->compiler()->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessComponentsWithIndexView() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $app->shouldReceive('getNamespace')->andReturn('App\\'); + $factory->shouldReceive('exists')->andReturn(false, true); + Container::setInstance($container); + + $result = $this->compiler()->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessComponentsWithComponentView() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $app->shouldReceive('getNamespace')->andReturn('App\\'); + $factory->shouldReceive('exists')->andReturn(false, false, true); + Container::setInstance($container); + + $result = $this->compiler()->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testPackagesClasslessComponents() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $app->shouldReceive('getNamespace')->andReturn('App\\'); + $factory->shouldReceive('exists')->andReturn(true); + Container::setInstance($container); + + $result = $this->compiler()->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessComponentsWithAnonymousComponentNamespace() + { + $container = new Container; + + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + $factory->shouldReceive('exists')->times(4)->andReturnUsing(function ($arg) { + // In our test, we'll do as if the 'public.frontend.anonymous-component' + // view exists and not the others. + return $arg === 'public.frontend.anonymous-component'; + }); + + Container::setInstance($container); + + $blade = m::mock(BladeCompiler::class)->makePartial(); + + $blade->shouldReceive('getAnonymousComponentNamespaces')->once()->andReturn([ + 'frontend' => 'public.frontend', + ]); + + $compiler = $this->compiler([], [], $blade); + + $result = $compiler->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexView() + { + $container = new Container; + + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + $factory->shouldReceive('exists')->times(5)->andReturnUsing(function (string $viewNameBeingCheckedForExistence) { + // In our test, we'll do as if the 'public.frontend.anonymous-component' + // view exists and not the others. + return $viewNameBeingCheckedForExistence === 'admin.auth.components.anonymous-component.index'; + }); + + Container::setInstance($container); + + $blade = m::mock(BladeCompiler::class)->makePartial(); + + $blade->shouldReceive('getAnonymousComponentNamespaces')->once()->andReturn([ + 'admin.auth' => 'admin.auth.components', + ]); + + $compiler = $this->compiler([], [], $blade); + + $result = $compiler->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessComponentsWithAnonymousComponentNamespaceWithComponentView() + { + $container = new Container; + + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + $factory->shouldReceive('exists')->times(6)->andReturnUsing(function (string $viewNameBeingCheckedForExistence) { + // In our test, we'll do as if the 'public.frontend.anonymous-component' + // view exists and not the others. + return $viewNameBeingCheckedForExistence === 'admin.auth.components.anonymous-component.anonymous-component'; + }); + + Container::setInstance($container); + + $blade = m::mock(BladeCompiler::class)->makePartial(); + + $blade->shouldReceive('getAnonymousComponentNamespaces')->once()->andReturn([ + 'admin.auth' => 'admin.auth.components', + ]); + + $compiler = $this->compiler([], [], $blade); + + $result = $compiler->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessComponentsWithAnonymousComponentPath() + { + $container = new Container; + + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + + $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { + return $arg === md5('test-directory').'::panel.index'; + }); + + Container::setInstance($container); + + $blade = m::mock(BladeCompiler::class)->makePartial(); + + $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ + ['path' => 'test-directory', 'prefix' => null, 'prefixHash' => md5('test-directory')], + ]); + + $compiler = $this->compiler([], [], $blade); + + $result = $compiler->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessComponentsWithAnonymousComponentPathComponentName() + { + $container = new Container; + + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + + $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { + return $arg === md5('test-directory').'::panel.panel'; + }); + + Container::setInstance($container); + + $blade = m::mock(BladeCompiler::class)->makePartial(); + + $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ + ['path' => 'test-directory', 'prefix' => null, 'prefixHash' => md5('test-directory')], + ]); + + $compiler = $this->compiler([], [], $blade); + + $result = $compiler->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.panel','data' => []]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testClasslessIndexComponentsWithAnonymousComponentPath() + { + $container = new Container; + + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + + $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { + return $arg === md5('test-directory').'::panel'; + }); + + Container::setInstance($container); + + $blade = m::mock(BladeCompiler::class)->makePartial(); + + $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ + ['path' => 'test-directory', 'prefix' => null, 'prefixHash' => md5('test-directory')], + ]); + + $compiler = $this->compiler([], [], $blade); + + $result = $compiler->compileTags(''); + + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + +withAttributes([]); ?>\n". + '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); + } + + public function testAttributeSanitization() + { + $this->mockViewFactory(); + $class = new class + { + public function __toString() + { + return ''; + } + }; + + $model = new class extends Model {}; + + $this->assertEquals(e(''), BladeCompiler::sanitizeComponentAttribute('')); + $this->assertEquals(e('1'), BladeCompiler::sanitizeComponentAttribute('1')); + $this->assertEquals(1, BladeCompiler::sanitizeComponentAttribute(1)); + $this->assertEquals(e(''), BladeCompiler::sanitizeComponentAttribute($class)); + $this->assertSame($model, BladeCompiler::sanitizeComponentAttribute($model)); + } + + public function testItThrowsAnExceptionForNonExistingAliases() + { + $this->mockViewFactory(false); + + $this->expectException(InvalidArgumentException::class); + + $this->compiler(['alert' => 'foo.bar'])->compileTags(''); + } + + public function testItThrowsAnExceptionForNonExistingClass() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); + $factory->shouldReceive('exists')->times(3)->andReturn(false); + Container::setInstance($container); + + $this->expectException(InvalidArgumentException::class); + + $this->compiler()->compileTags(''); + } + + public function testAttributesTreatedAsPropsAreRemovedFromFinalAttributes() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $container->alias(Factory::class, 'view'); + $app->shouldReceive('getNamespace')->never()->andReturn('App\\'); + $factory->shouldReceive('exists')->never(); + + Container::setInstance($container); + + $attributes = new ComponentAttributeBag(['userId' => 'bar', 'other' => 'ok']); + + $component = m::mock(Component::class); + $component->shouldReceive('withName')->with('profile')->once(); + $component->shouldReceive('shouldRender')->once()->andReturn(true); + $component->shouldReceive('resolveView')->once()->andReturn(''); + $component->shouldReceive('data')->once()->andReturn([]); + $component->shouldReceive('withAttributes')->with(['attributes' => new ComponentAttributeBag(['other' => 'ok'])])->once(); + + Component::resolveComponentsUsing(fn () => $component); + + $__env = m::mock(\Illuminate\View\Factory::class); + $__env->shouldReceive('startComponent')->once(); + $__env->shouldReceive('renderComponent')->once(); + + $template = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); + $template = $this->compiler->compileString($template); + + ob_start(); + eval(" ?> $template assertSame($attributes->get('userId'), 'bar'); + $this->assertSame($attributes->get('other'), 'ok'); + } + + public function testOriginalAttributesAreRestoredAfterRenderingChildComponentWithProps() + { + $container = new Container; + $container->instance(Application::class, $app = m::mock(Application::class)); + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $container->alias(Factory::class, 'view'); + $app->shouldReceive('getNamespace')->never()->andReturn('App\\'); + $factory->shouldReceive('exists')->never(); + + Container::setInstance($container); + + $attributes = new ComponentAttributeBag(['userId' => 'bar', 'other' => 'ok']); + + $containerComponent = m::mock(Component::class); + $containerComponent->shouldReceive('withName')->with('container')->once(); + $containerComponent->shouldReceive('shouldRender')->once()->andReturn(true); + $containerComponent->shouldReceive('resolveView')->once()->andReturn(''); + $containerComponent->shouldReceive('data')->once()->andReturn([]); + $containerComponent->shouldReceive('withAttributes')->once(); + + $profileComponent = m::mock(Component::class); + $profileComponent->shouldReceive('withName')->with('profile')->once(); + $profileComponent->shouldReceive('shouldRender')->once()->andReturn(true); + $profileComponent->shouldReceive('resolveView')->once()->andReturn(''); + $profileComponent->shouldReceive('data')->once()->andReturn([]); + $profileComponent->shouldReceive('withAttributes')->with(['attributes' => new ComponentAttributeBag(['other' => 'ok'])])->once(); + + Component::resolveComponentsUsing(fn ($component) => match ($component) { + TestContainerComponent::class => $containerComponent, + TestProfileComponent::class => $profileComponent, + }); + + $__env = m::mock(\Illuminate\View\Factory::class); + $__env->shouldReceive('startComponent')->twice(); + $__env->shouldReceive('renderComponent')->twice(); + + $template = $this->compiler([ + 'container' => TestContainerComponent::class, + 'profile' => TestProfileComponent::class, + ])->compileTags(''); + $template = $this->compiler->compileString($template); + + ob_start(); + eval(" ?> $template assertSame($attributes->get('userId'), 'bar'); + $this->assertSame($attributes->get('other'), 'ok'); + } + + protected function mockViewFactory($existsSucceeds = true) + { + $container = new Container; + $container->instance(Factory::class, $factory = m::mock(Factory::class)); + $container->alias(Factory::class, 'view'); + $factory->shouldReceive('exists')->andReturn($existsSucceeds); + Container::setInstance($container); + } + + protected function compiler(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null) + { + return new ComponentTagCompiler( + $aliases, $namespaces, $blade + ); + } +} + +class TestAlertComponent extends Component +{ + public $title; + + public function __construct($title = 'foo', $userId = 1) + { + $this->title = $title; + } + + public function render() + { + return 'alert'; + } +} + +class TestProfileComponent extends Component +{ + public $userId; + + public function __construct($userId = 'foo') + { + $this->userId = $userId; + } + + public function render() + { + return 'profile'; + } +} + +class TestInputComponent extends Component +{ + public $userId; + + public function __construct($name, $label, $value) + { + $this->name = $name; + $this->label = $label; + $this->value = $value; + } + + public function render() + { + return 'input'; + } +} + +class TestContainerComponent extends Component +{ + public function render() + { + return 'container'; + } +} + +namespace App\View\Components\Card; + +use Illuminate\View\Component; + +class Card extends Component +{ + public function render() + { + return 'card'; + } +} diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php new file mode 100644 index 000000000..92d89e6ef --- /dev/null +++ b/tests/View/Blade/BladeComponentsTest.php @@ -0,0 +1,85 @@ +assertSame('startComponent(\'foo\', ["foo" => "bar"]); ?>', $this->compiler->compileString('@component(\'foo\', ["foo" => "bar"])')); + $this->assertSame('startComponent(\'foo\'); ?>', $this->compiler->compileString('@component(\'foo\')')); + } + + public function testClassComponentsAreCompiled() + { + $this->assertSame(str_replace("\r\n", "\n", ' + + "bar"] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?> +withName(\'test\'); ?> +shouldRender()): ?> +startComponent($component->resolveView(), $component->data()); ?>'), $this->compiler->compileString('@component(\'Illuminate\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])')); + } + + public function testEndComponentsAreCompiled() + { + $this->compiler->newComponentHash('foo'); + + $this->assertSame('renderComponent(); ?>', $this->compiler->compileString('@endcomponent')); + } + + public function testEndComponentClassesAreCompiled() + { + $this->compiler->newComponentHash('foo'); + + $this->assertSame(str_replace("\r\n", "\n", 'renderComponent(); ?> + + + + + + + + +'), $this->compiler->compileString('@endcomponentClass')); + } + + public function testSlotsAreCompiled() + { + $this->assertSame('slot(\'foo\', null, ["foo" => "bar"]); ?>', $this->compiler->compileString('@slot(\'foo\', null, ["foo" => "bar"])')); + $this->assertSame('slot(\'foo\'); ?>', $this->compiler->compileString('@slot(\'foo\')')); + } + + public function testEndSlotsAreCompiled() + { + $this->assertSame('endSlot(); ?>', $this->compiler->compileString('@endslot')); + } + + public function testPropsAreExtractedFromParentAttributesCorrectlyForClassComponents() + { + $attributes = new ComponentAttributeBag(['foo' => 'baz', 'other' => 'ok']); + + $component = m::mock(Component::class); + $component->shouldReceive('withName', 'test'); + $component->shouldReceive('shouldRender')->andReturn(false); + + Component::resolveComponentsUsing(fn () => $component); + + $template = $this->compiler->compileString('@component(\'Illuminate\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])'); + + ob_start(); + eval(" ?> $template +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithExpressionAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue(TRUE) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue(2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithSpacedArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue( 2 ) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithFaultyArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue(-2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCustomTest.php b/tests/View/Blade/BladeCustomTest.php new file mode 100644 index 000000000..2747b5b69 --- /dev/null +++ b/tests/View/Blade/BladeCustomTest.php @@ -0,0 +1,233 @@ +assertSame(' ', $this->compiler->compileString("@if(\$test) @endif")); + } + + public function testMixingYieldAndEcho() + { + $this->assertSame('yieldContent(\'title\'); ?> - ', $this->compiler->compileString("@yield('title') - {{Config::get('site.title')}}")); + } + + public function testCustomExtensionsAreCompiled() + { + $this->compiler->extend(function ($value) { + return str_replace('foo', 'bar', $value); + }); + $this->assertSame('bar', $this->compiler->compileString('foo')); + } + + public function testCustomStatements() + { + $this->assertCount(0, $this->compiler->getCustomDirectives()); + $this->compiler->directive('customControl', function ($expression) { + return ""; + }); + $this->assertCount(1, $this->compiler->getCustomDirectives()); + + $string = '@if($foo) +@customControl(10, $foo, \'bar\') +@endif'; + $expected = ' + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomShortStatements() + { + $this->compiler->directive('customControl', function ($expression) { + return ''; + }); + + $string = '@customControl'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testValidCustomNames() + { + $this->assertNull($this->compiler->directive('custom', function () { + // + })); + $this->assertNull($this->compiler->directive('custom_custom', function () { + // + })); + $this->assertNull($this->compiler->directive('customCustom', function () { + // + })); + $this->assertNull($this->compiler->directive('custom::custom', function () { + // + })); + } + + public function testInvalidCustomNames() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The directive name [custom-custom] is not valid.'); + $this->compiler->directive('custom-custom', function () { + // + }); + } + + public function testInvalidCustomNames2() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The directive name [custom:custom] is not valid.'); + $this->compiler->directive('custom:custom', function () { + // + }); + } + + public function testCustomExtensionOverwritesCore() + { + $this->compiler->directive('foreach', function ($expression) { + return ''; + }); + + $string = '@foreach'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomConditions() + { + $this->compiler->if('custom', function ($user) { + return true; + }); + + $string = '@custom($user) +@endcustom'; + $expected = ' +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIfElseConditions() + { + $this->compiler->if('custom', function ($anything) { + return true; + }); + + $string = '@custom($user) +@elsecustom($product) +@else +@endcustom'; + $expected = ' + + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomUnlessConditions() + { + $this->compiler->if('custom', function ($anything) { + return true; + }); + + $string = '@unlesscustom($user) +@endcustom'; + $expected = ' +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomConditionsAccepts0AsArgument() + { + $this->compiler->if('custom', function ($number) { + return true; + }); + + $string = '@custom(0) +@elsecustom(0) +@endcustom'; + $expected = ' + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomComponents() + { + $this->compiler->aliasComponent('app.components.alert', 'alert'); + + $string = '@alert +@endalert'; + $expected = 'startComponent(\'app.components.alert\'); ?> +renderComponent(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomComponentsWithSlots() + { + $this->compiler->aliasComponent('app.components.alert', 'alert'); + + $string = '@alert([\'type\' => \'danger\']) +@endalert'; + $expected = 'startComponent(\'app.components.alert\', [\'type\' => \'danger\']); ?> +renderComponent(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomComponentsWithExistingDirective() + { + $this->compiler->aliasComponent('app.components.foreach', 'foreach'); + + $string = '@foreach +@endforeach'; + $expected = 'startComponent(\'app.components.foreach\'); ?> +renderComponent(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludes() + { + $this->compiler->include('app.includes.input', 'input'); + + $string = '@input'; + $expected = 'make(\'app.includes.input\', [], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludesWithData() + { + $this->compiler->include('app.includes.input', 'input'); + + $string = '@input([\'type\' => \'email\'])'; + $expected = 'make(\'app.includes.input\', [\'type\' => \'email\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludesDefaultAlias() + { + $this->compiler->include('app.includes.input'); + + $string = '@input'; + $expected = 'make(\'app.includes.input\', [], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludesWithExistingDirective() + { + $this->compiler->include('app.includes.foreach'); + + $string = '@foreach'; + $expected = 'make(\'app.includes.foreach\', [], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUnescapedNonRegisteredDirective() + { + $string = '@media only screen and (min-width:480px) {'; + $expected = '@media only screen and (min-width:480px) {'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeEchoHandlerTest.php b/tests/View/Blade/BladeEchoHandlerTest.php new file mode 100644 index 000000000..658a8dc5e --- /dev/null +++ b/tests/View/Blade/BladeEchoHandlerTest.php @@ -0,0 +1,126 @@ +compiler->stringable(function (Fluent $object) { + return 'Hello World'; + }); + } + + public function testBladeHandlerCanInterceptRegularEchos() + { + $this->assertSame( + "applyEchoHandler(\$exampleObject)); ?>", + $this->compiler->compileString('{{$exampleObject}}') + ); + } + + public function testBladeHandlerCanInterceptRawEchos() + { + $this->assertSame( + "applyEchoHandler(\$exampleObject); ?>", + $this->compiler->compileString('{!!$exampleObject!!}') + ); + } + + public function testBladeHandlerCanInterceptEscapedEchos() + { + $this->assertSame( + "applyEchoHandler(\$exampleObject)); ?>", + $this->compiler->compileString('{{{$exampleObject}}}') + ); + } + + public function testWhitespaceIsPreservedCorrectly() + { + $this->assertSame( + "applyEchoHandler(\$exampleObject)); ?>\n\n", + $this->compiler->compileString("{{\$exampleObject}}\n") + ); + } + + #[DataProvider('handlerLogicDataProvider')] + public function testHandlerLogicWorksCorrectly($blade) + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The fluent object has been successfully handled!'); + + $this->compiler->stringable(Fluent::class, function ($object) { + throw new Exception('The fluent object has been successfully handled!'); + }); + + app()->instance('blade.compiler', $this->compiler); + + $exampleObject = new Fluent(); + + eval((new Stringable($this->compiler->compileString($blade)))->remove([''])); + } + + public static function handlerLogicDataProvider() + { + return [ + ['{{$exampleObject}}'], + ['{{$exampleObject;}}'], + ['{{{$exampleObject;}}}'], + ['{!!$exampleObject;!!}'], + ]; + } + + #[DataProvider('handlerWorksWithIterableDataProvider')] + public function testHandlerWorksWithIterables($blade, $closure, $expectedOutput) + { + $this->compiler->stringable('iterable', $closure); + + app()->instance('blade.compiler', $this->compiler); + + ob_start(); + eval((new Stringable($this->compiler->compileString($blade)))->remove([''])); + $output = ob_get_contents(); + ob_end_clean(); + + $this->assertSame($expectedOutput, $output); + } + + public static function handlerWorksWithIterableDataProvider() + { + return [ + ['{{[1,"two",3]}}', function (iterable $arr) { + return implode(', ', $arr); + }, '1, two, 3'], + ]; + } + + #[DataProvider('nonStringableDataProvider')] + public function testHandlerWorksWithNonStringables($blade, $expectedOutput) + { + app()->instance('blade.compiler', $this->compiler); + + ob_start(); + eval((new Stringable($this->compiler->compileString($blade)))->remove([''])); + $output = ob_get_contents(); + ob_end_clean(); + + $this->assertSame($expectedOutput, $output); + } + + public static function nonStringableDataProvider() + { + return [ + ['{{"foo" . "bar"}}', 'foobar'], + ['{{ 1 + 2 }}{{ "test"; }}', '3test'], + ['@php($test = "hi"){{ $test }}', 'hi'], + ['{!! " " !!}', ' '], + ]; + } +} diff --git a/tests/View/Blade/BladeEchoTest.php b/tests/View/Blade/BladeEchoTest.php new file mode 100644 index 000000000..7388a7110 --- /dev/null +++ b/tests/View/Blade/BladeEchoTest.php @@ -0,0 +1,66 @@ +assertSame('', $this->compiler->compileString('{!!$name!!}')); + $this->assertSame('', $this->compiler->compileString('{!! $name !!}')); + $this->assertSame('', $this->compiler->compileString('{!! + $name + !!}')); + + $this->assertSame('', $this->compiler->compileString('{{{$name}}}')); + $this->assertSame('', $this->compiler->compileString('{{$name}}')); + $this->assertSame('', $this->compiler->compileString('{{ $name }}')); + $this->assertSame('', $this->compiler->compileString('{{ + $name + }}')); + $this->assertSame("\n\n", $this->compiler->compileString("{{ \$name }}\n")); + $this->assertSame("\r\n\r\n", $this->compiler->compileString("{{ \$name }}\r\n")); + $this->assertSame("\n\n", $this->compiler->compileString("{{ \$name }}\n")); + $this->assertSame("\r\n\r\n", $this->compiler->compileString("{{ \$name }}\r\n")); + + $this->assertSame('', + $this->compiler->compileString('{{ "Hello world or foo" }}')); + $this->assertSame('', + $this->compiler->compileString('{{"Hello world or foo"}}')); + $this->assertSame('', $this->compiler->compileString('{{$foo + $or + $baz}}')); + $this->assertSame('', $this->compiler->compileString('{{ + "Hello world or foo" + }}')); + + $this->assertSame('', + $this->compiler->compileString('{{ \'Hello world or foo\' }}')); + $this->assertSame('', + $this->compiler->compileString('{{\'Hello world or foo\'}}')); + $this->assertSame('', $this->compiler->compileString('{{ + \'Hello world or foo\' + }}')); + + $this->assertSame('', + $this->compiler->compileString('{{ myfunc(\'foo or bar\') }}')); + $this->assertSame('', + $this->compiler->compileString('{{ myfunc("foo or bar") }}')); + $this->assertSame('', + $this->compiler->compileString('{{ myfunc("$name or \'foo\'") }}')); + } + + public function testEscapedWithAtEchosAreCompiled() + { + $this->assertSame('{{$name}}', $this->compiler->compileString('@{{$name}}')); + $this->assertSame('{{ $name }}', $this->compiler->compileString('@{{ $name }}')); + $this->assertSame('{{ + $name + }}', + $this->compiler->compileString('@{{ + $name + }}')); + $this->assertSame('{{ $name }} + ', + $this->compiler->compileString('@{{ $name }} + ')); + } +} diff --git a/tests/View/Blade/BladeElseAuthStatementsTest.php b/tests/View/Blade/BladeElseAuthStatementsTest.php new file mode 100644 index 000000000..18221c121 --- /dev/null +++ b/tests/View/Blade/BladeElseAuthStatementsTest.php @@ -0,0 +1,36 @@ +guard("api")->check()): ?> +breeze +guard("standard")->check()): ?> +wheeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPlainElseAuthStatementsAreCompiled() + { + $string = '@auth("api") +breeze +@elseauth +wheeze +@endauth'; + $expected = 'guard("api")->check()): ?> +breeze +guard()->check()): ?> +wheeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeElseGuestStatementsTest.php b/tests/View/Blade/BladeElseGuestStatementsTest.php new file mode 100644 index 000000000..f381e6048 --- /dev/null +++ b/tests/View/Blade/BladeElseGuestStatementsTest.php @@ -0,0 +1,21 @@ +guard("api")->guest()): ?> +breeze +guard("standard")->guest()): ?> +wheeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeElseIfStatementsTest.php b/tests/View/Blade/BladeElseIfStatementsTest.php new file mode 100644 index 000000000..6dafabea2 --- /dev/null +++ b/tests/View/Blade/BladeElseIfStatementsTest.php @@ -0,0 +1,21 @@ + +breeze + +boom +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeElseStatementsTest.php b/tests/View/Blade/BladeElseStatementsTest.php new file mode 100644 index 000000000..11f5dbae4 --- /dev/null +++ b/tests/View/Blade/BladeElseStatementsTest.php @@ -0,0 +1,36 @@ + +breeze + +boom +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testElseIfStatementsAreCompiled() + { + $string = '@if(name(foo(bar))) +breeze +@elseif(boom(breeze)) +boom +@endif'; + $expected = ' +breeze + +boom +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeEndSectionsTest.php b/tests/View/Blade/BladeEndSectionsTest.php new file mode 100644 index 000000000..16a068c5e --- /dev/null +++ b/tests/View/Blade/BladeEndSectionsTest.php @@ -0,0 +1,11 @@ +assertSame('stopSection(); ?>', $this->compiler->compileString('@endsection')); + } +} diff --git a/tests/View/Blade/BladeEnvironmentStatementsTest.php b/tests/View/Blade/BladeEnvironmentStatementsTest.php new file mode 100644 index 000000000..866b9e4c4 --- /dev/null +++ b/tests/View/Blade/BladeEnvironmentStatementsTest.php @@ -0,0 +1,66 @@ +environment('staging')): ?> +breeze + +boom +"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testEnvStatementsWithMultipleStringParamsAreCompiled() + { + $string = "@env('staging', 'production') +breeze +@else +boom +@endenv"; + $expected = "environment('staging', 'production')): ?> +breeze + +boom +"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testEnvStatementsWithArrayParamAreCompiled() + { + $string = "@env(['staging', 'production']) +breeze +@else +boom +@endenv"; + $expected = "environment(['staging', 'production'])): ?> +breeze + +boom +"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testProductionStatementsAreCompiled() + { + $string = '@production +breeze +@else +boom +@endproduction'; + $expected = "environment('production')): ?> +breeze + +boom +"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeErrorTest.php b/tests/View/Blade/BladeErrorTest.php new file mode 100644 index 000000000..26a7abe58 --- /dev/null +++ b/tests/View/Blade/BladeErrorTest.php @@ -0,0 +1,47 @@ +{{ $message }} +@enderror'; + $expected = ' +getBag($__errorArgs[1] ?? \'default\'); +if ($__bag->has($__errorArgs[0])) : +if (isset($message)) { $__messageOriginal = $message; } +$message = $__bag->first($__errorArgs[0]); ?> + +'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testErrorsWithBagsAreCompiled() + { + $string = ' +@error(\'email\', \'customBag\') + {{ $message }} +@enderror'; + $expected = ' +getBag($__errorArgs[1] ?? \'default\'); +if ($__bag->has($__errorArgs[0])) : +if (isset($message)) { $__messageOriginal = $message; } +$message = $__bag->first($__errorArgs[0]); ?> + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeEscapedTest.php b/tests/View/Blade/BladeEscapedTest.php new file mode 100644 index 000000000..883f33558 --- /dev/null +++ b/tests/View/Blade/BladeEscapedTest.php @@ -0,0 +1,36 @@ +assertSame('@foreach', $this->compiler->compileString('@@foreach')); + $this->assertSame('@verbatim @continue @endverbatim', $this->compiler->compileString('@@verbatim @@continue @@endverbatim')); + $this->assertSame('@foreach($i as $x)', $this->compiler->compileString('@@foreach($i as $x)')); + $this->assertSame('@continue @break', $this->compiler->compileString('@@continue @@break')); + $this->assertSame('@foreach( + $i as $x + )', $this->compiler->compileString('@@foreach( + $i as $x + )')); + } + + public function testNestedEscapes() + { + $template = ' +@foreach($cols as $col) + @@foreach($issues as $issue_45915) + 👋 سلام 👋 + @@endforeach +@endforeach'; + $compiled = ' +addLoop($__currentLoopData); foreach($__currentLoopData as $col): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> + @foreach($issues as $issue_45915) + 👋 سلام 👋 + @endforeach +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertSame($compiled, $this->compiler->compileString($template)); + } +} diff --git a/tests/View/Blade/BladeExpressionTest.php b/tests/View/Blade/BladeExpressionTest.php new file mode 100644 index 000000000..d708cf2d2 --- /dev/null +++ b/tests/View/Blade/BladeExpressionTest.php @@ -0,0 +1,18 @@ +assertSame('get(foo(bar(baz(qux(breeze()))))); ?> space () get(foo(bar)); ?>', $this->compiler->compileString('@lang(foo(bar(baz(qux(breeze()))))) space () @lang(foo(bar))')); + } + + public function testExpressionWithinHTML() + { + $this->assertSame('>', $this->compiler->compileString('')); + $this->assertSame('>', $this->compiler->compileString('')); + $this->assertSame(' get(\'foo\'); ?>>', $this->compiler->compileString('')); + } +} diff --git a/tests/View/Blade/BladeExtendsTest.php b/tests/View/Blade/BladeExtendsTest.php new file mode 100644 index 000000000..a4dcf8c64 --- /dev/null +++ b/tests/View/Blade/BladeExtendsTest.php @@ -0,0 +1,43 @@ +make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@extends(name(foo))'."\n".'test'; + $expected = "test\n".'make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testSequentialCompileStringCalls() + { + $string = '@extends(\'foo\') +test'; + $expected = "test\n".'make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + // use the same compiler instance to compile another template with @extends directive + $string = "@extends(name(foo))\ntest"; + $expected = "test\n".'make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testExtendsFirstAreCompiled() + { + $string = '@extendsFirst([\'foo\', \'milwad\']) +test'; + $expected = "test\n".'first([\'foo\', \'milwad\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@extendsFirst([name(foo), name(milwad)])'."\n".'test'; + $expected = "test\n".'first([name(foo), name(milwad)], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeForStatementsTest.php b/tests/View/Blade/BladeForStatementsTest.php new file mode 100644 index 000000000..d12b39b48 --- /dev/null +++ b/tests/View/Blade/BladeForStatementsTest.php @@ -0,0 +1,32 @@ + +test +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedForStatementsAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +@for ($j = 0; $j < 20; $j++) +test +@endfor +@endfor'; + $expected = ' + +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeForeachStatementsTest.php b/tests/View/Blade/BladeForeachStatementsTest.php new file mode 100644 index 000000000..80ba97367 --- /dev/null +++ b/tests/View/Blade/BladeForeachStatementsTest.php @@ -0,0 +1,121 @@ +getUsers() as $user) +test +@endforeach'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +test +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForeachStatementsAreCompileWithUppercaseSyntax() + { + $string = '@foreach ($this->getUsers() AS $user) +test +@endforeach'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +test +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForeachStatementsAreCompileWithMultipleLine() + { + $string = '@foreach ([ +foo, +bar, +] as $label) +test +@endforeach'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $label): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +test +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedForeachStatementsAreCompiled() + { + $string = '@foreach ($this->getUsers() as $user) +user info +@foreach ($user->tags as $tag) +tag info +@endforeach +@endforeach'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +user info +tags; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tag): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +tag info +popLoop(); $loop = $__env->getLastLoop(); ?> +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testLoopContentHolderIsExtractedFromForeachStatements() + { + $string = '@foreach ($some_uSers1 as $user)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach ($users->get() as $user)'; + $expected = 'get(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach (range(1, 4) as $user)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach ( $users as $user)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach ($tasks as $task)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $task): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "@foreach(resolve('App\\\\DataProviders\\\\'.\$provider)->data() as \$key => \$value) + +@endforeach"; + $expected = "data(); \$__env->addLoop(\$__currentLoopData); foreach(\$__currentLoopData as \$key => \$value): \$__env->incrementLoopIndices(); \$loop = \$__env->getLastLoop(); ?> + > +popLoop(); \$loop = \$__env->getLastLoop(); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + #[DataProvider('invalidForeachStatementsDataProvider')] + public function testForeachStatementsThrowHumanizedMessageWhenInvalidStatement($initialStatement) + { + $this->expectException(ViewCompilationException::class); + $this->expectExceptionMessage('Malformed @foreach statement.'); + $string = "$initialStatement +test +@endforeach"; + $this->compiler->compileString($string); + } + + public static function invalidForeachStatementsDataProvider() + { + return [ + ['@foreach'], + ['@foreach()'], + ['@foreach ()'], + ['@foreach($test)'], + ['@foreach($test as)'], + ['@foreach(as)'], + ['@foreach ( as )'], + ]; + } +} diff --git a/tests/View/Blade/BladeForelseStatementsTest.php b/tests/View/Blade/BladeForelseStatementsTest.php new file mode 100644 index 000000000..67e0cd38f --- /dev/null +++ b/tests/View/Blade/BladeForelseStatementsTest.php @@ -0,0 +1,109 @@ +getUsers() as $user) +breeze +@empty +empty +@endforelse'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForelseStatementsAreCompiledWithUppercaseSyntax() + { + $string = '@forelse ($this->getUsers() AS $user) +breeze +@empty +empty +@endforelse'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForelseStatementsAreCompiledWithMultipleLine() + { + $string = '@forelse ([ +foo, +bar, +] as $label) +breeze +@empty +empty +@endforelse'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $label): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedForelseStatementsAreCompiled() + { + $string = '@forelse ($this->getUsers() as $user) +@forelse ($user->tags as $tag) +breeze +@empty +tag empty +@endforelse +@empty +empty +@endforelse'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +tags; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tag): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_2 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_2): ?> +tag empty + +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + #[DataProvider('invalidForelseStatementsDataProvider')] + public function testForelseStatementsThrowHumanizedMessageWhenInvalidStatement($initialStatement) + { + $this->expectException(ViewCompilationException::class); + $this->expectExceptionMessage('Malformed @forelse statement.'); + $string = "$initialStatement +breeze +@empty +tag empty +@endforelse"; + $this->compiler->compileString($string); + } + + public static function invalidForelseStatementsDataProvider() + { + return [ + ['@forelse'], + ['@forelse()'], + ['@forelse ()'], + ['@forelse($test)'], + ['@forelse($test as)'], + ['@forelse(as)'], + ['@forelse ( as )'], + ]; + } +} diff --git a/tests/View/Blade/BladeFragmentTest.php b/tests/View/Blade/BladeFragmentTest.php new file mode 100644 index 000000000..7aaaf918c --- /dev/null +++ b/tests/View/Blade/BladeFragmentTest.php @@ -0,0 +1,17 @@ +assertSame('startFragment(\'foo\'); ?>', $this->compiler->compileString('@fragment(\'foo\')')); + $this->assertSame('startFragment(name(foo)); ?>', $this->compiler->compileString('@fragment(name(foo))')); + } + + public function testEndFragmentsAreCompiled() + { + $this->assertSame('stopFragment(); ?>', $this->compiler->compileString('@endfragment')); + } +} diff --git a/tests/View/Blade/BladeHasSectionTest.php b/tests/View/Blade/BladeHasSectionTest.php new file mode 100644 index 000000000..0ef592e70 --- /dev/null +++ b/tests/View/Blade/BladeHasSectionTest.php @@ -0,0 +1,17 @@ +yieldContent("section")))): ?> +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeHelpersTest.php b/tests/View/Blade/BladeHelpersTest.php new file mode 100644 index 000000000..8e071c38b --- /dev/null +++ b/tests/View/Blade/BladeHelpersTest.php @@ -0,0 +1,20 @@ +assertSame('', $this->compiler->compileString('@csrf')); + $this->assertSame('', $this->compiler->compileString("@method('patch')")); + $this->assertSame('', $this->compiler->compileString('@dd($var1)')); + $this->assertSame('', $this->compiler->compileString('@dd($var1, $var2)')); + $this->assertSame('', $this->compiler->compileString('@dump($var1, $var2)')); + $this->assertSame('', $this->compiler->compileString('@vite')); + $this->assertSame('', $this->compiler->compileString('@vite()')); + $this->assertSame('', $this->compiler->compileString('@vite(\'resources/js/app.js\')')); + $this->assertSame('', $this->compiler->compileString('@vite([\'resources/js/app.js\'])')); + $this->assertSame('reactRefresh(); ?>', $this->compiler->compileString('@viteReactRefresh')); + } +} diff --git a/tests/View/Blade/BladeIfAuthStatementsTest.php b/tests/View/Blade/BladeIfAuthStatementsTest.php new file mode 100644 index 000000000..d52258678 --- /dev/null +++ b/tests/View/Blade/BladeIfAuthStatementsTest.php @@ -0,0 +1,28 @@ +guard("api")->check()): ?> +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPlainIfStatementsAreCompiled() + { + $string = '@auth +breeze +@endauth'; + $expected = 'guard()->check()): ?> +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIfEmptyStatementsTest.php b/tests/View/Blade/BladeIfEmptyStatementsTest.php new file mode 100644 index 000000000..d18ef7241 --- /dev/null +++ b/tests/View/Blade/BladeIfEmptyStatementsTest.php @@ -0,0 +1,17 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIfGuestStatementsTest.php b/tests/View/Blade/BladeIfGuestStatementsTest.php new file mode 100644 index 000000000..14b105d73 --- /dev/null +++ b/tests/View/Blade/BladeIfGuestStatementsTest.php @@ -0,0 +1,17 @@ +guard("api")->guest()): ?> +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIfIssetStatementsTest.php b/tests/View/Blade/BladeIfIssetStatementsTest.php new file mode 100644 index 000000000..5da3e2fd8 --- /dev/null +++ b/tests/View/Blade/BladeIfIssetStatementsTest.php @@ -0,0 +1,17 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIfStatementsTest.php b/tests/View/Blade/BladeIfStatementsTest.php new file mode 100644 index 000000000..bfa6efa28 --- /dev/null +++ b/tests/View/Blade/BladeIfStatementsTest.php @@ -0,0 +1,56 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testSwitchstatementsAreCompiled() + { + $string = '@switch(true) +@case(1) +foo + +@case(2) +bar +@endswitch + +foo + +@switch(true) +@case(1) +foo + +@case(2) +bar +@endswitch'; + $expected = ' +foo + + +bar + + +foo + + +foo + + +bar +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIncludesTest.php b/tests/View/Blade/BladeIncludesTest.php new file mode 100644 index 000000000..6c96c3d54 --- /dev/null +++ b/tests/View/Blade/BladeIncludesTest.php @@ -0,0 +1,49 @@ +assertSame('renderEach(\'foo\', \'bar\'); ?>', $this->compiler->compileString('@each(\'foo\', \'bar\')')); + $this->assertSame('renderEach(\'foo\', \'(bar))\'); ?>', $this->compiler->compileString('@each(\'foo\', \'(bar))\')')); + $this->assertSame('renderEach(name(foo)); ?>', $this->compiler->compileString('@each(name(foo))')); + } + + public function testIncludesAreCompiled() + { + $this->assertSame('make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@include(\'foo\')')); + $this->assertSame('make(\'foo\', [\'((\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@include(\'foo\', [\'((\'])')); + $this->assertSame('make(\'foo\', [\'((a)\' => \'((a)\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@include(\'foo\', [\'((a)\' => \'((a)\'])')); + $this->assertSame('make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@include(name(foo))')); + } + + public function testIncludeIfsAreCompiled() + { + $this->assertSame('exists(\'foo\')) echo $__env->make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@includeIf(\'foo\')')); + $this->assertSame('exists(name(foo))) echo $__env->make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@includeIf(name(foo))')); + } + + public function testIncludeWhensAreCompiled() + { + $this->assertSame('renderWhen(true, \'foo\', ["foo" => "bar"], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1])); ?>', $this->compiler->compileString('@includeWhen(true, \'foo\', ["foo" => "bar"])')); + $this->assertSame('renderWhen(true, \'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1])); ?>', $this->compiler->compileString('@includeWhen(true, \'foo\')')); + } + + public function testIncludeUnlessesAreCompiled() + { + $this->assertSame('renderUnless(true, \'foo\', ["foo" => "bar"], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1])); ?>', $this->compiler->compileString('@includeUnless(true, \'foo\', ["foo" => "bar"])')); + $this->assertSame('renderUnless(true, \'foo\', ["foo" => "bar_))-))>"], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1])); ?>', $this->compiler->compileString('@includeUnless(true, \'foo\', ["foo" => "bar_))-))>"])')); + $this->assertSame('renderUnless($undefined ?? true, \'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1])); ?>', $this->compiler->compileString('@includeUnless($undefined ?? true, \'foo\')')); + } + + public function testIncludeFirstsAreCompiled() + { + $this->assertSame('first(["one", "two"], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"])')); + $this->assertSame('first(["one", "two"], ["foo" => "bar"], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"], ["foo" => "bar"])')); + $this->assertSame('first(["issue", "#45424)"], ["foo()" => "bar)-))"], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@includeFirst(["issue", "#45424)"], ["foo()" => "bar)-))"])')); + $this->assertSame('first(["issue", "#45424)"], ["foo" => "bar(-(("], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@includeFirst(["issue", "#45424)"], ["foo" => "bar(-(("])')); + $this->assertSame('first(["issue", "#45424)"], [(string) "foo()" => "bar(-(("], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@includeFirst(["issue", "#45424)"], [(string) "foo()" => "bar(-(("])')); + } +} diff --git a/tests/View/Blade/BladeInjectTest.php b/tests/View/Blade/BladeInjectTest.php new file mode 100644 index 000000000..07ffd19f0 --- /dev/null +++ b/tests/View/Blade/BladeInjectTest.php @@ -0,0 +1,34 @@ + bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testDependenciesInjectedAsStringsAreCompiledWhenInjectedWithDoubleQuotes() + { + $string = 'Foo @inject("baz", "SomeNamespace\SomeClass") bar'; + $expected = 'Foo bar'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testDependenciesAreCompiled() + { + $string = "Foo @inject('baz', SomeNamespace\SomeClass::class) bar"; + $expected = "Foo bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testDependenciesAreCompiledWithDoubleQuotes() + { + $string = 'Foo @inject("baz", SomeNamespace\SomeClass::class) bar'; + $expected = "Foo bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeJsTest.php b/tests/View/Blade/BladeJsTest.php new file mode 100644 index 000000000..be63c8f19 --- /dev/null +++ b/tests/View/Blade/BladeJsTest.php @@ -0,0 +1,30 @@ +'; + $expected = '
'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testJsonFlagsCanBeSet() + { + $string = '
'; + $expected = '
'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testEncodingDepthCanBeSet() + { + $string = '
'; + $expected = '
'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeJsonTest.php b/tests/View/Blade/BladeJsonTest.php new file mode 100644 index 000000000..bdfab95ef --- /dev/null +++ b/tests/View/Blade/BladeJsonTest.php @@ -0,0 +1,22 @@ +;'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testEncodingOptionsCanBeOverwritten() + { + $string = 'var foo = @json($var, JSON_HEX_TAG);'; + $expected = 'var foo = ;'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeLangTest.php b/tests/View/Blade/BladeLangTest.php new file mode 100644 index 000000000..49f9ab281 --- /dev/null +++ b/tests/View/Blade/BladeLangTest.php @@ -0,0 +1,19 @@ +get(function_call('foo(blah)')); ?> bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testLanguageAndChoicesAreCompiled() + { + $this->assertSame('get(\'foo\'); ?>', $this->compiler->compileString("@lang('foo')")); + $this->assertSame('choice(\'foo\', 1); ?>', $this->compiler->compileString("@choice('foo', 1)")); + } +} diff --git a/tests/View/Blade/BladeOverwriteSectionTest.php b/tests/View/Blade/BladeOverwriteSectionTest.php new file mode 100644 index 000000000..1e67623ca --- /dev/null +++ b/tests/View/Blade/BladeOverwriteSectionTest.php @@ -0,0 +1,11 @@ +assertSame('stopSection(true); ?>', $this->compiler->compileString('@overwrite')); + } +} diff --git a/tests/View/Blade/BladePhpStatementsTest.php b/tests/View/Blade/BladePhpStatementsTest.php new file mode 100644 index 000000000..e0235ece3 --- /dev/null +++ b/tests/View/Blade/BladePhpStatementsTest.php @@ -0,0 +1,181 @@ +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testStringWithParenthesisWithEndPHP() + { + $string = "@php(\$data = ['related_to' => 'issue#45388'];) {{ \$data }} @endphp"; + $expected = " 'issue#45388'];) {{ \$data }} ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPhpStatementsWithoutExpressionAreIgnored() + { + $string = '@php'; + $expected = '@php'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '{{ "Ignore: @php" }}'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPhpStatementsDontParseBladeCode() + { + $string = '@php echo "{{ This is a blade tag }}" @endphp'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testVerbatimAndPhpStatementsDontGetMixedUp() + { + $string = "@verbatim {{ Hello, I'm not blade! }}" + ."\n@php echo 'And I'm not PHP!' @endphp" + ."\n@endverbatim {{ 'I am Blade' }}" + ."\n@php echo 'I am PHP {{ not Blade }}' @endphp"; + + $expected = " {{ Hello, I'm not blade! }}" + ."\n@php echo 'And I'm not PHP!' @endphp" + ."\n " + ."\n\n"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testStringWithOpeningParenthesisCanBeCompiled() + { + $string = "@php(\$data = ['single' => ':(('])"; + $expected = " ':((']); ?>"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "@php(\$data = ['single' => (string)':(('])"; + $expected = " (string)':((']); ?>"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "@php(\$data = ['single' => '(()(('])"; + $expected = " '(()((']); ?>"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testStringWithParenthesisCanBeCompiled() + { + $string = "@php(\$data = ['single' => ')'])"; + $expected = " ')']); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "@php(\$data = ['(multiple)-))' => '((-))'])"; + $expected = " '((-))']); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "@php(\$data = [(int)'(multiple)-))' => (bool)'((casty))'])"; + $expected = " (bool)'((casty))']); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $this->assertSame('renderEach(\'foo\', \'b)a)r\'); ?>', $this->compiler->compileString('@each(\'foo\', \'b)a)r\')')); + $this->assertSame('make(\'test_for\', [\'issue))\' => \'(issue#45424))\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>', $this->compiler->compileString('@include(\'test_for\', [\'issue))\' => \'(issue#45424))\'])')); + $this->assertSame('( make(\'test_for\', [\'not_too_much))\' => \'(issue#45424))\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>))', $this->compiler->compileString('( @include(\'test_for\', [\'not_too_much))\' => \'(issue#45424))\'])))')); + } + + public function testStringWithEmptyStringDataValue() + { + $string = "@php(\$data = ['test' => ''])"; + + $expected = " '']); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "@php(\$data = ['test' => \"\"])"; + + $expected = " \"\"]); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testStringWithEscapingDataValue() + { + $string = "@php(\$data = ['test' => 'won\\'t break'])"; + + $expected = " 'won\\'t break']); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "@php(\$data = ['test' => \"\\\"escaped\\\"\"])"; + + $expected = " \"\\\"escaped\\\"\"]); ?>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUnclosedParenthesisForBladeTags() + { + $string = ""; + $expected = "\"(['(']>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = ""; + $expected = "\"(['']>"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = ""; + $expected = ""; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = ""; + $expected = ""; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedTagCalls() + { + $string = " @empty(\$v)])>"; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = " @empty(\$v)])>"; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = " @empty(\$v), 't' => @empty(\$v1)])>"; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = " @empty(\$v), 't' => @empty(\$v1)])>"; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = " @empty(\$v), 't' => @empty(\$v1), 'r' => @empty(\$v2)])>"; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = " @empty(\$v), 't))' => @empty(\$v1), 'r' => @empty(\$v2)])>"; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = " @empty(\$v), 't' => @empty(\$v1), 'r' => @empty(\$v2), 'l' => 'l'])> @empty(\$v)])>"; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testItDoesNotCompileInvalidSyntax() + { + $template = " ()])>"; + $this->assertEquals($template, $this->compiler->compileString($template)); + } +} diff --git a/tests/View/Blade/BladePrependTest.php b/tests/View/Blade/BladePrependTest.php new file mode 100644 index 000000000..11bc1fa10 --- /dev/null +++ b/tests/View/Blade/BladePrependTest.php @@ -0,0 +1,50 @@ +startPrepend(\'foo\'); ?> +bar +stopPrepend(); ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPrependOnceIsCompiled() + { + $string = '@prependOnce(\'foo\', \'bar\') +test +@endPrependOnce'; + + $expected = 'hasRenderedOnce(\'bar\')): $__env->markAsRenderedOnce(\'bar\'); +$__env->startPrepend(\'foo\'); ?> +test +stopPrepend(); endif; ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPrependOnceIsCompiledWhenIdIsMissing() + { + Str::createUuidsUsing(fn () => 'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'); + + $string = '@prependOnce(\'foo\') +test +@endPrependOnce'; + + $expected = 'hasRenderedOnce(\'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f\')): $__env->markAsRenderedOnce(\'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f\'); +$__env->startPrepend(\'foo\'); ?> +test +stopPrepend(); endif; ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladePropsTest.php b/tests/View/Blade/BladePropsTest.php new file mode 100644 index 000000000..e9d258e22 --- /dev/null +++ b/tests/View/Blade/BladePropsTest.php @@ -0,0 +1,63 @@ +assertSame(' true, \'two\' => \'string\'])); + +foreach ($attributes->all() as $__key => $__value) { + if (in_array($__key, $__propNames)) { + $$__key = $$__key ?? $__value; + } else { + $__newAttributes[$__key] = $__value; + } +} + +$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes); + +unset($__propNames); +unset($__newAttributes); + +foreach (array_filter(([\'one\' => true, \'two\' => \'string\']), \'is_string\', ARRAY_FILTER_USE_KEY) as $__key => $__value) { + $$__key = $$__key ?? $__value; +} + +$__defined_vars = get_defined_vars(); + +foreach ($attributes->all() as $__key => $__value) { + if (array_key_exists($__key, $__defined_vars)) unset($$__key); +} + +unset($__defined_vars); ?>', $this->compiler->compileString('@props([\'one\' => true, \'two\' => \'string\'])')); + } + + public function testPropsAreExtractedFromParentAttributesCorrectly() + { + $test1 = $test2 = $test4 = null; + + $attributes = new ComponentAttributeBag(['test1' => 'value1', 'test2' => 'value2', 'test3' => 'value3']); + + $template = $this->compiler->compileString('@props([\'test1\' => \'default\', \'test2\', \'test4\' => \'default\'])'); + + ob_start(); + eval(" ?> $template assertSame($test1, 'value1'); + $this->assertSame($test2, 'value2'); + $this->assertFalse(isset($test3)); + $this->assertSame($test4, 'default'); + + $this->assertNull($attributes->get('test1')); + $this->assertNull($attributes->get('test2')); + $this->assertSame($attributes->get('test3'), 'value3'); + } +} diff --git a/tests/View/Blade/BladePushTest.php b/tests/View/Blade/BladePushTest.php new file mode 100644 index 000000000..84f3d3f33 --- /dev/null +++ b/tests/View/Blade/BladePushTest.php @@ -0,0 +1,92 @@ +startPush(\'foo\'); ?> +test +stopPush(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPushIsCompiledWithParenthesis() + { + $string = '@push(\'foo):))\') +test +@endpush'; + $expected = 'startPush(\'foo):))\'); ?> +test +stopPush(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPushOnceIsCompiled() + { + $string = '@pushOnce(\'foo\', \'bar\') +test +@endPushOnce'; + + $expected = 'hasRenderedOnce(\'bar\')): $__env->markAsRenderedOnce(\'bar\'); +$__env->startPush(\'foo\'); ?> +test +stopPush(); endif; ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPushOnceIsCompiledWhenIdIsMissing() + { + Str::createUuidsUsing(fn () => 'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'); + + $string = '@pushOnce(\'foo\') +test +@endPushOnce'; + + $expected = 'hasRenderedOnce(\'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f\')): $__env->markAsRenderedOnce(\'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f\'); +$__env->startPush(\'foo\'); ?> +test +stopPush(); endif; ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPushIfIsCompiled() + { + $string = '@pushIf(true, \'foo\') +test +@endPushIf'; + $expected = 'startPush( \'foo\'); ?> +test +stopPush(); endif; ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPushIfElseIsCompiled() + { + $string = '@pushIf(true, \'stack\') +if +@elsePushIf(false, \'stack\') +elseif +@elsePush(\'stack\') +else +@endPushIf'; + $expected = 'startPush( \'stack\'); ?> +if +stopPush(); elseif(false): $__env->startPush( \'stack\'); ?> +elseif +stopPush(); else: $__env->startPush(\'stack\'); ?> +else +stopPush(); endif; ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeSectionMissingTest.php b/tests/View/Blade/BladeSectionMissingTest.php new file mode 100644 index 000000000..fb600a1a3 --- /dev/null +++ b/tests/View/Blade/BladeSectionMissingTest.php @@ -0,0 +1,17 @@ +yieldContent("section")))): ?> +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeSectionTest.php b/tests/View/Blade/BladeSectionTest.php new file mode 100644 index 000000000..31ee49011 --- /dev/null +++ b/tests/View/Blade/BladeSectionTest.php @@ -0,0 +1,13 @@ +assertSame('startSection(\'foo\'); ?>', $this->compiler->compileString('@section(\'foo\')')); + $this->assertSame('startSection(\'issue#18317 :))\'); ?>', $this->compiler->compileString('@section(\'issue#18317 :))\')')); + $this->assertSame('startSection(name(foo)); ?>', $this->compiler->compileString('@section(name(foo))')); + } +} diff --git a/tests/View/Blade/BladeSessionTest.php b/tests/View/Blade/BladeSessionTest.php new file mode 100644 index 000000000..d3d553e0a --- /dev/null +++ b/tests/View/Blade/BladeSessionTest.php @@ -0,0 +1,27 @@ +{{ $value }} +@endsession'; + $expected = ' +has($__sessionArgs[0])) : +if (isset($value)) { $__sessionPrevious[] = $value; } +$value = session()->get($__sessionArgs[0]); ?> + +'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeShowTest.php b/tests/View/Blade/BladeShowTest.php new file mode 100644 index 000000000..5f5521b2f --- /dev/null +++ b/tests/View/Blade/BladeShowTest.php @@ -0,0 +1,11 @@ +assertSame('yieldSection(); ?>', $this->compiler->compileString('@show')); + } +} diff --git a/tests/View/Blade/BladeStackTest.php b/tests/View/Blade/BladeStackTest.php new file mode 100644 index 000000000..a63c04e2f --- /dev/null +++ b/tests/View/Blade/BladeStackTest.php @@ -0,0 +1,17 @@ +yieldPushContent(\'foo\'); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@stack(\'foo))\')'; + $expected = 'yieldPushContent(\'foo))\'); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeStopSectionTest.php b/tests/View/Blade/BladeStopSectionTest.php new file mode 100644 index 000000000..9bf544dd2 --- /dev/null +++ b/tests/View/Blade/BladeStopSectionTest.php @@ -0,0 +1,11 @@ +assertSame('stopSection(); ?>', $this->compiler->compileString('@stop')); + } +} diff --git a/tests/View/Blade/BladeStyleTest.php b/tests/View/Blade/BladeStyleTest.php new file mode 100644 index 000000000..01e8c2eb1 --- /dev/null +++ b/tests/View/Blade/BladeStyleTest.php @@ -0,0 +1,14 @@ + true, 'margin-top: 10px' => false])>"; + $expected = " true, 'margin-top: 10px' => false]) ?>\">"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeUnlessStatementsTest.php b/tests/View/Blade/BladeUnlessStatementsTest.php new file mode 100644 index 000000000..55c27eec1 --- /dev/null +++ b/tests/View/Blade/BladeUnlessStatementsTest.php @@ -0,0 +1,17 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeUnsetStatementsTest.php b/tests/View/Blade/BladeUnsetStatementsTest.php new file mode 100644 index 000000000..289ef3cf2 --- /dev/null +++ b/tests/View/Blade/BladeUnsetStatementsTest.php @@ -0,0 +1,17 @@ +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@unset ($unset)))'; + $expected = '))'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeUseTest.php b/tests/View/Blade/BladeUseTest.php new file mode 100644 index 000000000..8e72c3215 --- /dev/null +++ b/tests/View/Blade/BladeUseTest.php @@ -0,0 +1,34 @@ + bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithoutAsAreCompiled() + { + $string = "Foo @use('SomeNamespace\SomeClass') bar"; + $expected = "Foo bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithBackslashAtBeginningAreCompiled() + { + $string = "Foo @use('\SomeNamespace\SomeClass') bar"; + $expected = "Foo bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithBackslashAtBeginningAndAliasedAreCompiled() + { + $string = "Foo @use('\SomeNamespace\SomeClass', 'Foo') bar"; + $expected = "Foo bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeVerbatimTest.php b/tests/View/Blade/BladeVerbatimTest.php new file mode 100644 index 000000000..e072c3420 --- /dev/null +++ b/tests/View/Blade/BladeVerbatimTest.php @@ -0,0 +1,104 @@ +assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testVerbatimBlocksWithMultipleLinesAreCompiled() + { + $string = 'Some text +@verbatim + {{ $a }} + @if($b) + {{ $b }} + @endif +@endverbatim'; + $expected = 'Some text + + {{ $a }} + @if($b) + {{ $b }} + @endif +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testMultipleVerbatimBlocksAreCompiled() + { + $string = '@verbatim {{ $a }} @endverbatim {{ $b }} @verbatim {{ $c }} @endverbatim'; + $expected = ' {{ $a }} {{ $c }} '; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testRawBlocksAreRenderedInTheRightOrder() + { + $string = '@php echo "#1"; @endphp @verbatim {{ #2 }} @endverbatim @verbatim {{ #3 }} @endverbatim @php echo "#4"; @endphp'; + + $expected = ' {{ #2 }} {{ #3 }} '; + + $this->assertSame($expected, $this->compiler->compileString($string)); + } + + public function testMultilineTemplatesWithRawBlocksAreRenderedInTheRightOrder() + { + $string = '{{ $first }} +@php + echo $second; +@endphp +@if ($conditional) + {{ $third }} +@endif +@include("users") +@verbatim + {{ $fourth }} @include("test") +@endverbatim +@php echo $fifth; @endphp'; + + $expected = ' + + + + + + +make("users", array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?> + + {{ $fourth }} @include("test") + +'; + + $this->assertSame($expected, $this->compiler->compileString($string)); + } + + public function testRawBlocksDontGetMixedUpWhenSomeAreRemovedByBladeComments() + { + $string = '{{-- @verbatim Block #1 @endverbatim --}} @php "Block #2" @endphp'; + $expected = ' '; + + $this->assertSame($expected, $this->compiler->compileString($string)); + } + + public function testNewlinesAreInsertedCorrectlyAfterEcho() + { + $string = "test @verbatim\nhello world\n@endverbatim"; + $expected = "test \nhello world\n"; + $this->assertSame($expected, $this->compiler->compileString($string)); + + $string = "{{ 1 }}\nhello world\n"; + $expected = "\n\nhello world\n"; + $this->assertSame($expected, $this->compiler->compileString($string)); + + $string = "{{ 1 }}@verbatim\nhello world\n@endverbatim"; + $expected = "\n\nhello world\n"; + $this->assertSame($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeWhileStatementsTest.php b/tests/View/Blade/BladeWhileStatementsTest.php new file mode 100644 index 000000000..38ace2a32 --- /dev/null +++ b/tests/View/Blade/BladeWhileStatementsTest.php @@ -0,0 +1,32 @@ + +test +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedWhileStatementsAreCompiled() + { + $string = '@while ($foo) +@while ($bar) +test +@endwhile +@endwhile'; + $expected = ' + +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeYieldTest.php b/tests/View/Blade/BladeYieldTest.php new file mode 100644 index 000000000..a9c1676a9 --- /dev/null +++ b/tests/View/Blade/BladeYieldTest.php @@ -0,0 +1,13 @@ +assertSame('yieldContent(\'foo\'); ?>', $this->compiler->compileString('@yield(\'foo\')')); + $this->assertSame('yieldContent(\'foo\', \'bar\'); ?>', $this->compiler->compileString('@yield(\'foo\', \'bar\')')); + $this->assertSame('yieldContent(name(foo)); ?>', $this->compiler->compileString('@yield(name(foo))')); + } +} diff --git a/tests/View/ComponentTest.php b/tests/View/ComponentTest.php new file mode 100644 index 000000000..4a7a59253 --- /dev/null +++ b/tests/View/ComponentTest.php @@ -0,0 +1,502 @@ +config = m::mock(Config::class); + + $container = new Container; + + $this->viewFactory = m::mock(Factory::class); + + $container->instance('view', $this->viewFactory); + $container->alias('view', FactoryContract::class); + $container->instance('config', $this->config); + + Container::setInstance($container); + Facade::setFacadeApplication($container); + } + + protected function tearDown(): void + { + m::close(); + + Facade::clearResolvedInstances(); + Facade::setFacadeApplication(null); + Container::setInstance(null); + Component::flushCache(); + Component::forgetFactory(); + + parent::tearDown(); + } + + public function testInlineViewsGetCreated() + { + $this->config->shouldReceive('get')->once()->with('view.compiled')->andReturn('/tmp'); + $this->viewFactory->shouldReceive('exists')->once()->andReturn(false); + $this->viewFactory->shouldReceive('addNamespace')->once()->with('__components', '/tmp'); + + $component = new TestInlineViewComponent; + $this->assertSame('__components::57b7a54afa0eb51fd9b88eec031c9e9e', $component->resolveView()); + } + + public function testRegularViewsGetReturnedUsingViewHelper() + { + $view = m::mock(View::class); + $this->viewFactory->shouldReceive('make')->once()->with('alert', [], [])->andReturn($view); + + $component = new TestRegularViewComponentUsingViewHelper; + + $this->assertSame($view, $component->resolveView()); + } + + public function testRenderingStringClosureFromComponent() + { + $this->config->shouldReceive('get')->once()->with('view.compiled')->andReturn('/tmp'); + $this->viewFactory->shouldReceive('exists')->once()->andReturn(false); + $this->viewFactory->shouldReceive('addNamespace')->once()->with('__components', '/tmp'); + + $component = new class() extends Component + { + protected $title; + + public function __construct($title = 'World') + { + $this->title = $title; + } + + public function render() + { + return function (array $data) { + return "

Hello {$this->title}

"; + }; + } + }; + + $closure = $component->resolveView(); + + $viewPath = $closure([]); + + $this->viewFactory->shouldReceive('make')->with($viewPath, [], [])->andReturn('

Hello World

'); + + $this->assertInstanceOf(Closure::class, $closure); + $this->assertSame('__components::9cc08f5001b343c093ee1a396da820dc', $viewPath); + + $hash = str_replace('__components::', '', $viewPath); + $this->assertSame('

Hello World

', file_get_contents("/tmp/{$hash}.blade.php")); + } + + public function testRegularViewsGetReturnedUsingViewMethod() + { + $view = m::mock(View::class); + $this->viewFactory->shouldReceive('make')->once()->with('alert', [], [])->andReturn($view); + + $component = new TestRegularViewComponentUsingViewMethod; + + $this->assertSame($view, $component->resolveView()); + } + + public function testRegularViewNamesGetReturned() + { + $this->viewFactory->shouldReceive('exists')->once()->andReturn(true); + $this->viewFactory->shouldReceive('addNamespace')->never(); + + $component = new TestRegularViewNameViewComponent; + + $this->assertSame('alert', $component->resolveView()); + } + + public function testHtmlableGetReturned() + { + $component = new TestHtmlableReturningViewComponent; + + $view = $component->resolveView(); + + $this->assertInstanceOf(Htmlable::class, $view); + $this->assertSame('

Hello foo

', $view->toHtml()); + } + + public function testResolveWithUnresolvableDependency() + { + $this->expectException(BindingResolutionException::class); + $this->expectExceptionMessage('Unresolvable dependency resolving'); + + TestInlineViewComponentWhereRenderDependsOnProps::resolve([]); + } + + public function testResolveDependenciesWithoutContainer() + { + $component = TestInlineViewComponentWhereRenderDependsOnProps::resolve(['content' => 'foo']); + $this->assertSame('foo', $component->render()); + + $component = new class extends Component + { + public $content; + + public function __construct($a = null, $b = null) + { + $this->content = $a.$b; + } + + public function render() + { + return $this->content; + } + }; + + $component = $component::resolve(['a' => 'a', 'b' => 'b']); + $component = $component::resolve(['b' => 'b', 'a' => 'a']); + $this->assertSame('ab', $component->render()); + } + + public function testResolveDependenciesWithContainerIfNecessary() + { + $component = TestInlineViewComponentWithContainerDependencies::resolve([]); + $this->assertSame($this->viewFactory, $component->dependency); + + $component = TestInlineViewComponentWithContainerDependenciesAndProps::resolve(['content' => 'foo']); + $this->assertSame($this->viewFactory, $component->dependency); + $this->assertSame('foo', $component->render()); + } + + public function testResolveComponentsUsing() + { + $component = new TestInlineViewComponent; + + Component::resolveComponentsUsing(function ($class, $data) use ($component) { + $this->assertSame(Component::class, $class, 'It takes the component class name as the first parameter.'); + $this->assertSame(['foo' => 'bar'], $data, 'It takes the given data as the second parameter.'); + + return $component; + }); + + $this->assertSame($component, Component::resolve(['foo' => 'bar'])); + } + + public function testBladeViewCacheWithRegularViewNameViewComponent() + { + $component = new TestRegularViewNameViewComponent; + + $this->viewFactory->shouldReceive('exists')->twice()->andReturn(true); + + $this->assertSame('alert', $component->resolveView()); + $this->assertSame('alert', $component->resolveView()); + $this->assertSame('alert', $component->resolveView()); + $this->assertSame('alert', $component->resolveView()); + + $cache = (fn () => $component::$bladeViewCache)->call($component); + $this->assertSame([$component::class.'::alert' => 'alert'], $cache); + + $component::flushCache(); + + $cache = (fn () => $component::$bladeViewCache)->call($component); + $this->assertSame([], $cache); + + $this->assertSame('alert', $component->resolveView()); + $this->assertSame('alert', $component->resolveView()); + $this->assertSame('alert', $component->resolveView()); + $this->assertSame('alert', $component->resolveView()); + } + + public function testBladeViewCacheWithInlineViewComponent() + { + $component = new TestInlineViewComponent; + + $this->viewFactory->shouldReceive('exists')->twice()->andReturn(false); + + $this->config->shouldReceive('get')->twice()->with('view.compiled')->andReturn('/tmp'); + + $this->viewFactory->shouldReceive('addNamespace') + ->with('__components', '/tmp') + ->twice(); + + $compiledViewName = '__components::57b7a54afa0eb51fd9b88eec031c9e9e'; + $contents = '::Hello {{ $title }}'; + $cacheKey = $component::class.$contents; + + $this->assertSame($compiledViewName, $component->resolveView()); + $this->assertSame($compiledViewName, $component->resolveView()); + $this->assertSame($compiledViewName, $component->resolveView()); + $this->assertSame($compiledViewName, $component->resolveView()); + + $cache = (fn () => $component::$bladeViewCache)->call($component); + $this->assertSame([$cacheKey => $compiledViewName], $cache); + + $component::flushCache(); + + $cache = (fn () => $component::$bladeViewCache)->call($component); + $this->assertSame([], $cache); + + $this->assertSame($compiledViewName, $component->resolveView()); + $this->assertSame($compiledViewName, $component->resolveView()); + $this->assertSame($compiledViewName, $component->resolveView()); + $this->assertSame($compiledViewName, $component->resolveView()); + } + + public function testBladeViewCacheWithInlineViewComponentWhereRenderDependsOnProps() + { + $componentA = new TestInlineViewComponentWhereRenderDependsOnProps('A'); + $componentB = new TestInlineViewComponentWhereRenderDependsOnProps('B'); + + $this->viewFactory->shouldReceive('exists')->twice()->andReturn(false); + + $this->config->shouldReceive('get')->twice()->with('view.compiled')->andReturn('/tmp'); + + $this->viewFactory->shouldReceive('addNamespace') + ->with('__components', '/tmp') + ->twice(); + + $compiledViewNameA = '__components::9b0498cbe3839becd0d496e05c553485'; + $compiledViewNameB = '__components::9d1b9bc4078a3e7274d3766ca02423f3'; + $cacheAKey = $componentA::class.'::A'; + $cacheBKey = $componentB::class.'::B'; + + $this->assertSame($compiledViewNameA, $componentA->resolveView()); + $this->assertSame($compiledViewNameA, $componentA->resolveView()); + $this->assertSame($compiledViewNameB, $componentB->resolveView()); + $this->assertSame($compiledViewNameB, $componentB->resolveView()); + + $cacheA = (fn () => $componentA::$bladeViewCache)->call($componentA); + $cacheB = (fn () => $componentB::$bladeViewCache)->call($componentB); + $this->assertSame($cacheA, $cacheB); + $this->assertSame([ + $cacheAKey => $compiledViewNameA, + $cacheBKey => $compiledViewNameB, + ], $cacheA); + + $componentA::flushCache(); + + $cacheA = (fn () => $componentA::$bladeViewCache)->call($componentA); + $cacheB = (fn () => $componentB::$bladeViewCache)->call($componentB); + $this->assertSame($cacheA, $cacheB); + $this->assertSame([], $cacheA); + } + + public function testFactoryGetsSharedBetweenComponents() + { + $regular = new TestRegularViewNameViewComponent; + $inline = new TestInlineViewComponent; + + $getFactory = fn ($component) => (fn () => $component->factory())->call($component); + + $this->assertSame($this->viewFactory, $getFactory($regular)); + + Container::getInstance()->instance('view', 'foo'); + $this->assertSame($this->viewFactory, $getFactory($inline)); + + Component::forgetFactory(); + $this->assertNotSame($this->viewFactory, $getFactory($inline)); + } + + public function testComponentSlotIsEmpty() + { + $slot = new ComponentSlot(); + + $this->assertTrue((bool) $slot->isEmpty()); + } + + public function testComponentSlotSanitizedEmpty() + { + // default sanitizer should remove all html tags + $slot = new ComponentSlot(''); + + $linebreakingSlot = new ComponentSlot("\n \t"); + + $moreComplexSlot = new ComponentSlot(''); + + $this->assertFalse((bool) $slot->hasActualContent()); + $this->assertFalse((bool) $linebreakingSlot->hasActualContent('trim')); + $this->assertFalse((bool) $moreComplexSlot->hasActualContent()); + } + + public function testComponentSlotSanitizedNotEmpty() + { + // default sanitizer should remove all html tags + $slot = new ComponentSlot('not empty'); + + $linebreakingSlot = new ComponentSlot("\ntest \t"); + + $moreComplexSlot = new ComponentSlot('beforeafter'); + + $this->assertTrue((bool) $slot->hasActualContent()); + $this->assertTrue((bool) $linebreakingSlot->hasActualContent('trim')); + $this->assertTrue((bool) $moreComplexSlot->hasActualContent()); + } + + public function testComponentSlotIsNotEmpty() + { + $slot = new ComponentSlot('test'); + + $anotherSlot = new ComponentSlot('test'); + + $moreComplexSlot = new ComponentSlot('test'); + + $this->assertTrue((bool) $slot->hasActualContent()); + $this->assertTrue((bool) $anotherSlot->hasActualContent()); + $this->assertTrue((bool) $moreComplexSlot->hasActualContent()); + } +} + +class TestInlineViewComponent extends Component +{ + public $title; + + public function __construct($title = 'foo') + { + $this->title = $title; + } + + public function render() + { + return 'Hello {{ $title }}'; + } +} + +class TestInlineViewComponentWithContainerDependencies extends Component +{ + public $dependency; + + public function __construct(FactoryContract $dependency) + { + $this->dependency = $dependency; + } + + public function render() + { + return ''; + } +} + +class TestInlineViewComponentWithContainerDependenciesAndProps extends Component +{ + public $content; + + public $dependency; + + public function __construct(FactoryContract $dependency, $content) + { + $this->content = $content; + $this->dependency = $dependency; + } + + public function render() + { + return $this->content; + } +} + +class TestInlineViewComponentWithoutDependencies extends Component +{ + public function render() + { + return 'alert'; + } +} + +class TestInlineViewComponentWhereRenderDependsOnProps extends Component +{ + public $content; + + public function __construct($content) + { + $this->content = $content; + } + + public function render() + { + return $this->content; + } +} + +class TestRegularViewComponentUsingViewHelper extends Component +{ + public $title; + + public function __construct($title = 'foo') + { + $this->title = $title; + } + + public function render() + { + return view('alert'); + } +} + +class TestRegularViewComponentUsingViewMethod extends Component +{ + public $title; + + public function __construct($title = 'foo') + { + $this->title = $title; + } + + public function render() + { + return $this->view('alert'); + } +} + +class TestRegularViewNameViewComponent extends Component +{ + public $title; + + public function __construct($title = 'foo') + { + $this->title = $title; + } + + public function render() + { + return 'alert'; + } +} + +class TestHtmlableReturningViewComponent extends Component +{ + protected $title; + + public function __construct($title = 'foo') + { + $this->title = $title; + } + + public function render() + { + return new HtmlString("

Hello {$this->title}

"); + } +} diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php new file mode 100644 index 000000000..c3955945e --- /dev/null +++ b/tests/View/ViewBladeCompilerTest.php @@ -0,0 +1,270 @@ +getFiles(), __DIR__); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); + $this->assertTrue($compiler->isExpired('foo')); + } + + public function testCannotConstructWithBadCachePath() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Please provide a valid cache path.'); + + new BladeCompiler($this->getFiles(), null); + } + + public function testIsExpiredReturnsTrueWhenModificationTimesWarrant() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(true); + $files->shouldReceive('lastModified')->once()->with('foo')->andReturn(100); + $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(0); + $this->assertTrue($compiler->isExpired('foo')); + } + + public function testIsExpiredReturnsFalseWhenUseCacheIsTrueAndNoFileModification() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(true); + $files->shouldReceive('lastModified')->once()->with('foo')->andReturn(0); + $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(100); + $this->assertFalse($compiler->isExpired('foo')); + } + + public function testIsExpiredReturnsTrueWhenUseCacheIsFalse() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__, $basePath = '', $useCache = false); + $this->assertTrue($compiler->isExpired('foo')); + } + + public function testCompilePathIsProperlyCreated() + { + $compiler = new BladeCompiler($this->getFiles(), __DIR__); + $this->assertEquals(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', $compiler->getCompiledPath('foo')); + } + + public function testCompileCompilesFileAndReturnsContents() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + $compiler->compile('foo'); + } + + public function testCompileCompilesFileAndReturnsContentsCreatingDirectory() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(false); + $files->shouldReceive('makeDirectory')->once()->with(__DIR__, 0777, true, true); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + $compiler->compile('foo'); + } + + public function testCompileCompilesAndGetThePath() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + $compiler->compile('foo'); + $this->assertSame('foo', $compiler->getPath()); + } + + public function testCompileSetAndGetThePath() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $compiler->setPath('foo'); + $this->assertSame('foo', $compiler->getPath()); + } + + public function testCompileWithPathSetBefore() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + // set path before compilation + $compiler->setPath('foo'); + // trigger compilation with $path + $compiler->compile(); + $this->assertSame('foo', $compiler->getPath()); + } + + public function testRawTagsCanBeSetToLegacyValues() + { + $compiler = new BladeCompiler($this->getFiles(), __DIR__); + $compiler->setEchoFormat('%s'); + + $this->assertSame('', $compiler->compileString('{{{ $name }}}')); + $this->assertSame('', $compiler->compileString('{{ $name }}')); + $this->assertSame('', $compiler->compileString('{{ + $name + }}')); + } + + /** + * @param string $content + * @param string $compiled + */ + #[DataProvider('appendViewPathDataProvider')] + public function testIncludePathToTemplate($content, $compiled) + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('foo')->andReturn($content); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', $compiled); + + $compiler->compile('foo'); + } + + /** + * @return array + */ + public static function appendViewPathDataProvider() + { + return [ + 'No PHP blocks' => [ + 'Hello World', + 'Hello World', + ], + 'Single PHP block without closing ?>' => [ + '', + ], + 'Ending PHP block.' => [ + 'Hello world', + 'Hello world', + ], + 'Ending PHP block without closing ?>' => [ + 'Hello world', + ], + 'PHP block between content.' => [ + 'Hello worldHi There', + 'Hello worldHi There', + ], + 'Multiple PHP blocks.' => [ + 'Hello worldHi ThereHello Again', + 'Hello worldHi ThereHello Again', + ], + 'Multiple PHP blocks without closing ?>' => [ + 'Hello worldHi ThereHi There', + ], + 'Short open echo tag' => [ + 'Hello world', + ], + 'Echo XML declaration' => [ + '\';', + '\'; ?>', + ], + ]; + } + + public function testDontIncludeEmptyPath() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('')->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World'); + $compiler->setPath(''); + $compiler->compile(); + } + + public function testDontIncludeNullPath() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with(null)->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World'); + $compiler->setPath(null); + $compiler->compile(); + } + + public function testShouldStartFromStrictTypesDeclaration() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $strictTypeDecl = "assertSame(substr($compiler->compileString("getFiles(), __DIR__); + + $compiler->component('App\Foo\Bar'); + $this->assertEquals(['bar' => 'App\Foo\Bar'], $compiler->getClassComponentAliases()); + + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->component('App\Foo\Bar', null, 'prefix'); + $this->assertEquals(['prefix-bar' => 'App\Foo\Bar'], $compiler->getClassComponentAliases()); + + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->component('App\View\Components\Forms\Input'); + $this->assertEquals(['forms:input' => 'App\View\Components\Forms\Input'], $compiler->getClassComponentAliases()); + + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->component('App\View\Components\Forms\Input', null, 'prefix'); + $this->assertEquals(['prefix-forms:input' => 'App\View\Components\Forms\Input'], $compiler->getClassComponentAliases()); + } + + public function testAnonymousComponentNamespacesCanBeStored() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->anonymousComponentNamespace(' public/frontend ', 'frontend'); + $this->assertEquals(['frontend' => 'public.frontend'], $compiler->getAnonymousComponentNamespaces()); + + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->anonymousComponentNamespace('public/frontend/', 'frontend'); + $this->assertEquals(['frontend' => 'public.frontend'], $compiler->getAnonymousComponentNamespaces()); + + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->anonymousComponentNamespace('/admin/components', 'admin'); + $this->assertEquals(['admin' => 'admin.components'], $compiler->getAnonymousComponentNamespaces()); + + // Test directory is automatically inferred from the prefix if not given. + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->anonymousComponentNamespace('frontend'); + $this->assertEquals(['frontend' => 'frontend'], $compiler->getAnonymousComponentNamespaces()); + + // Test that the prefix can also contain dots. + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + + $compiler->anonymousComponentNamespace('frontend/auth', 'frontend.auth'); + $this->assertEquals(['frontend.auth' => 'frontend.auth'], $compiler->getAnonymousComponentNamespaces()); + } + + protected function getFiles() + { + return m::mock(Filesystem::class); + } +} diff --git a/tests/View/ViewCompilerEngineTest.php b/tests/View/ViewCompilerEngineTest.php new file mode 100755 index 000000000..9d3b80d2f --- /dev/null +++ b/tests/View/ViewCompilerEngineTest.php @@ -0,0 +1,302 @@ +getEngine(); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('isExpired')->once()->with(__DIR__.'/fixtures/foo.php')->andReturn(true); + $engine->getCompiler()->shouldReceive('compile')->once()->with(__DIR__.'/fixtures/foo.php'); + $results = $engine->get(__DIR__.'/fixtures/foo.php'); + + $this->assertSame('Hello World +', $results); + } + + public function testViewsAreNotRecompiledIfTheyAreNotExpired() + { + $engine = $this->getEngine(); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false); + $engine->getCompiler()->shouldReceive('compile')->never(); + $results = $engine->get(__DIR__.'/fixtures/foo.php'); + + $this->assertSame('Hello World +', $results); + } + + public function testRegularExceptionsAreReThrownAsViewExceptions() + { + $engine = $this->getEngine(); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/regular-exception.php'); + $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false); + + $this->expectException(ViewException::class); + $this->expectExceptionMessage('regular exception message'); + + $engine->get(__DIR__.'/fixtures/foo.php'); + } + + public function testHttpExceptionsAreNotReThrownAsViewExceptions() + { + $engine = $this->getEngine(); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/http-exception.php'); + $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false); + + $this->expectException(HttpException::class); + $this->expectExceptionMessage('http exception message'); + + $engine->get(__DIR__.'/fixtures/foo.php'); + } + + public function testThatViewsAreNotAskTwiceIfTheyAreExpired() + { + $engine = $this->getEngine(); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false); + $engine->getCompiler()->shouldReceive('compile')->never(); + + $engine->get(__DIR__.'/fixtures/foo.php'); + $engine->get(__DIR__.'/fixtures/foo.php'); + $engine->get(__DIR__.'/fixtures/foo.php'); + + $engine->forgetCompiledOrNotExpired(); + + $engine->get(__DIR__.'/fixtures/foo.php'); + } + + public function testViewsAreRecompiledWhenCompiledViewIsMissingViaFileNotFoundException() + { + $compiled = __DIR__.'/fixtures/basic.php'; + $path = __DIR__.'/fixtures/foo.php'; + + $files = m::mock(Filesystem::class); + $engine = $this->getEngine($files); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andReturn('compiled-content'); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andThrow(new FileNotFoundException( + "File does not exist at path {$path}." + )); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andReturn('compiled-content'); + + $engine->getCompiler() + ->shouldReceive('getCompiledPath') + ->times(3) + ->with($path) + ->andReturn($compiled); + + $engine->getCompiler() + ->shouldReceive('isExpired') + ->once() + ->andReturn(true); + + $engine->getCompiler() + ->shouldReceive('compile') + ->twice() + ->with($path); + + $engine->get($path); + $engine->get($path); + } + + public function testViewsAreRecompiledWhenCompiledViewIsMissingViaRequireException() + { + $compiled = __DIR__.'/fixtures/basic.php'; + $path = __DIR__.'/fixtures/foo.php'; + + $files = m::mock(Filesystem::class); + $engine = $this->getEngine($files); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andReturn('compiled-content'); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andThrow(new ErrorException( + "require({$path}): Failed to open stream: No such file or directory", + )); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andReturn('compiled-content'); + + $engine->getCompiler() + ->shouldReceive('getCompiledPath') + ->times(3) + ->with($path) + ->andReturn($compiled); + + $engine->getCompiler() + ->shouldReceive('isExpired') + ->once() + ->andReturn(true); + + $engine->getCompiler() + ->shouldReceive('compile') + ->twice() + ->with($path); + + $engine->get($path); + $engine->get($path); + } + + public function testViewsAreRecompiledJustOnceWhenCompiledViewIsMissing() + { + $compiled = __DIR__.'/fixtures/basic.php'; + $path = __DIR__.'/fixtures/foo.php'; + + $files = m::mock(Filesystem::class); + $engine = $this->getEngine($files); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andReturn('compiled-content'); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andThrow(new FileNotFoundException( + "File does not exist at path {$path}." + )); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andThrow(new FileNotFoundException( + "File does not exist at path {$path}." + )); + + $engine->getCompiler() + ->shouldReceive('getCompiledPath') + ->times(3) + ->with($path) + ->andReturn($compiled); + + $engine->getCompiler() + ->shouldReceive('isExpired') + ->once() + ->andReturn(true); + + $engine->getCompiler() + ->shouldReceive('compile') + ->twice() + ->with($path); + + $engine->get($path); + + $this->expectException(ViewException::class); + $this->expectExceptionMessage("File does not exist at path {$path}."); + $engine->get($path); + } + + public function testViewsAreNotRecompiledOnRegularViewException() + { + $compiled = __DIR__.'/fixtures/basic.php'; + $path = __DIR__.'/fixtures/foo.php'; + + $files = m::mock(Filesystem::class); + $engine = $this->getEngine($files); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andThrow(new Exception( + 'Just an regular error...' + )); + + $engine->getCompiler() + ->shouldReceive('isExpired') + ->once() + ->andReturn(false); + + $engine->getCompiler() + ->shouldReceive('compile') + ->never(); + + $engine->getCompiler() + ->shouldReceive('getCompiledPath') + ->once() + ->with($path) + ->andReturn($compiled); + + $this->expectException(ViewException::class); + $this->expectExceptionMessage('Just an regular error...'); + $engine->get($path); + } + + public function testViewsAreNotRecompiledIfTheyWereJustCompiled() + { + $compiled = __DIR__.'/fixtures/basic.php'; + $path = __DIR__.'/fixtures/foo.php'; + + $files = m::mock(Filesystem::class); + $engine = $this->getEngine($files); + + $files->shouldReceive('getRequire') + ->once() + ->with($compiled, []) + ->andThrow(new FileNotFoundException( + "File does not exist at path {$path}." + )); + + $engine->getCompiler() + ->shouldReceive('isExpired') + ->once() + ->andReturn(true); + + $engine->getCompiler() + ->shouldReceive('compile') + ->once() + ->with($path); + + $engine->getCompiler() + ->shouldReceive('getCompiledPath') + ->once() + ->with($path) + ->andReturn($compiled); + + $this->expectException(ViewException::class); + $this->expectExceptionMessage("File does not exist at path {$path}."); + $engine->get($path); + } + + protected function getEngine($filesystem = null) + { + return new CompilerEngine(m::mock(CompilerInterface::class), $filesystem ?: new Filesystem); + } +} diff --git a/tests/View/ViewComponentAttributeBagTest.php b/tests/View/ViewComponentAttributeBagTest.php new file mode 100644 index 000000000..82fbd8687 --- /dev/null +++ b/tests/View/ViewComponentAttributeBagTest.php @@ -0,0 +1,156 @@ + 'font-bold', 'name' => 'test']); + + $this->assertSame('class="font-bold"', (string) $bag->whereStartsWith('class')); + $this->assertSame('font-bold', (string) $bag->whereStartsWith('class')->first()); + $this->assertSame('name="test"', (string) $bag->whereDoesntStartWith('class')); + $this->assertSame('test', (string) $bag->whereDoesntStartWith('class')->first()); + $this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag->merge(['class' => 'mt-4'])); + $this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag->merge(['class' => 'mt-4', 'name' => 'foo'])); + $this->assertSame('class="mt-4 font-bold" id="bar" name="test"', (string) $bag->merge(['class' => 'mt-4', 'id' => 'bar'])); + $this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag(['class' => 'mt-4'])); + $this->assertSame('class="mt-4 font-bold"', (string) $bag->only('class')->merge(['class' => 'mt-4'])); + $this->assertSame('name="test" class="font-bold"', (string) $bag->merge(['name' => 'default'])); + $this->assertSame('class="font-bold" name="test"', (string) $bag->merge([])); + $this->assertSame('class="mt-4 font-bold"', (string) $bag->merge(['class' => 'mt-4'])->only('class')); + $this->assertSame('class="mt-4 font-bold"', (string) $bag->only('class')(['class' => 'mt-4'])); + $this->assertSame('font-bold', $bag->get('class')); + $this->assertSame('bar', $bag->get('foo', 'bar')); + $this->assertSame('font-bold', $bag['class']); + $this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag->class('mt-4')); + $this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag->class(['mt-4'])); + $this->assertSame('class="mt-4 ml-2 font-bold" name="test"', (string) $bag->class(['mt-4', 'ml-2' => true, 'mr-2' => false])); + + $bag = new ComponentAttributeBag(['class' => 'font-bold', 'name' => 'test', 'style' => 'margin-top: 10px']); + $this->assertSame('class="mt-4 ml-2 font-bold" style="margin-top: 10px;" name="test"', (string) $bag->class(['mt-4', 'ml-2' => true, 'mr-2' => false])); + $this->assertSame('style="margin-top: 4px; margin-left: 10px; margin-top: 10px;" class="font-bold" name="test"', (string) $bag->style(['margin-top: 4px', 'margin-left: 10px;'])); + + $bag = new ComponentAttributeBag(['class' => 'font-bold', 'name' => 'test', 'style' => 'margin-top: 10px; font-weight: bold']); + $this->assertSame('class="mt-4 ml-2 font-bold" style="margin-top: 10px; font-weight: bold;" name="test"', (string) $bag->class(['mt-4', 'ml-2' => true, 'mr-2' => false])); + $this->assertSame('style="margin-top: 4px; margin-left: 10px; margin-top: 10px; font-weight: bold;" class="font-bold" name="test"', (string) $bag->style(['margin-top: 4px', 'margin-left: 10px;'])); + + $bag = new ComponentAttributeBag([]); + + $this->assertSame('class="mt-4"', (string) $bag->merge(['class' => 'mt-4'])); + + $bag = new ComponentAttributeBag([ + 'test-string' => 'ok', + 'test-null' => null, + 'test-false' => false, + 'test-true' => true, + 'test-0' => 0, + 'test-0-string' => '0', + 'test-empty-string' => '', + ]); + + $this->assertSame('test-string="ok" test-true="test-true" test-0="0" test-0-string="0" test-empty-string=""', (string) $bag); + $this->assertSame('test-string="ok" test-true="test-true" test-0="0" test-0-string="0" test-empty-string=""', (string) $bag->merge()); + + $bag = (new ComponentAttributeBag) + ->merge([ + 'test-escaped' => '', + ]); + + $this->assertSame('test-escaped="<tag attr="attr">"', (string) $bag); + + $bag = (new ComponentAttributeBag) + ->merge([ + 'test-string' => 'ok', + 'test-null' => null, + 'test-false' => false, + 'test-true' => true, + 'test-0' => 0, + 'test-0-string' => '0', + 'test-empty-string' => '', + ]); + + $this->assertSame('test-string="ok" test-true="test-true" test-0="0" test-0-string="0" test-empty-string=""', (string) $bag); + + $bag = (new ComponentAttributeBag) + ->merge([ + 'test-extract-1' => 'extracted-1', + 'test-extract-2' => 'extracted-2', + 'test-discard-1' => 'discarded-1', + 'test-discard-2' => 'discarded-2', + ]); + + $this->assertSame('test-extract-1="extracted-1" test-extract-2="extracted-2"', (string) $bag->exceptProps([ + 'test-discard-1', + 'test-discard-2' => 'defaultValue', + ])); + + $bag = (new ComponentAttributeBag) + ->merge([ + 'test-extract-1' => 'extracted-1', + 'test-extract-2' => 'extracted-2', + 'test-discard-1' => 'discarded-1', + 'test-discard-2' => 'discarded-2', + ]); + + $this->assertSame('test-extract-1="extracted-1" test-extract-2="extracted-2"', (string) $bag->onlyProps([ + 'test-extract-1', + 'test-extract-2' => 'defaultValue', + ])); + } + + public function testItMakesAnExceptionForAlpineXdata() + { + $bag = new ComponentAttributeBag([ + 'required' => true, + 'x-data' => true, + ]); + + $this->assertSame('required="required" x-data=""', (string) $bag); + } + + public function testItMakesAnExceptionForLivewireWireAttributes() + { + $bag = new ComponentAttributeBag([ + 'wire:loading' => true, + 'wire:loading.remove' => true, + 'wire:poll' => true, + ]); + + $this->assertSame('wire:loading="" wire:loading.remove="" wire:poll=""', (string) $bag); + } + + public function testAttributeExistence() + { + $bag = new ComponentAttributeBag(['name' => 'test']); + + $this->assertTrue((bool) $bag->has('name')); + $this->assertTrue((bool) $bag->has(['name'])); + $this->assertTrue((bool) $bag->hasAny(['class', 'name'])); + $this->assertTrue((bool) $bag->hasAny('class', 'name')); + $this->assertFalse((bool) $bag->missing('name')); + $this->assertFalse((bool) $bag->has('class')); + $this->assertFalse((bool) $bag->has(['class'])); + $this->assertFalse((bool) $bag->has(['name', 'class'])); + $this->assertFalse((bool) $bag->has('name', 'class')); + $this->assertTrue((bool) $bag->missing('class')); + } + + public function testAttributeIsEmpty() + { + $bag = new ComponentAttributeBag([]); + + $this->assertTrue((bool) $bag->isEmpty()); + } + + public function testAttributeIsNotEmpty() + { + $bag = new ComponentAttributeBag(['name' => 'test']); + + $this->assertTrue((bool) $bag->isNotEmpty()); + } +} diff --git a/tests/View/ViewComponentTest.php b/tests/View/ViewComponentTest.php new file mode 100644 index 000000000..c21573d77 --- /dev/null +++ b/tests/View/ViewComponentTest.php @@ -0,0 +1,258 @@ +data(); + + $this->assertEquals(10, $variables['votes']); + $this->assertSame('world', $variables['hello']()); + $this->assertSame('taylor', $variables['hello']('taylor')); + } + + public function testIgnoredMethodsAreNotExposedToViewData() + { + $component = new class extends Component + { + protected $except = ['goodbye']; + + public function render() + { + return 'test'; + } + + public function hello() + { + return 'hello world'; + } + + public function goodbye() + { + return 'goodbye'; + } + }; + + $data = $component->data(); + + $this->assertArrayHasKey('hello', $data); + $this->assertArrayNotHasKey('goodbye', $data); + + $reflectionMethod = new ReflectionMethod($component, 'ignoredMethods'); + + $ignoredMethods = $reflectionMethod->invoke($component); + + foreach ($ignoredMethods as $method) { + $this->assertArrayNotHasKey($method, $data); + } + } + + public function testAttributeParentInheritance(): void + { + $component = new TestViewComponent; + $attributes = new ComponentAttributeBag(['class' => 'bar', 'type' => 'button']); + + $component->withAttributes(['class' => 'foo', 'attributes' => $attributes]); + + $this->assertSame('class="foo bar" type="button"', (string) $component->attributes); + + // Test overriding parent class attributes + $component->withAttributes(['class' => 'override', 'type' => 'submit']); + $this->assertSame('class="override" type="submit"', (string) $component->attributes); + } + + public function testSlotAttributeParentInheritance(): void + { + $attributes = new ComponentAttributeBag(['class' => 'bar', 'type' => 'button']); + + $slot = new ComponentSlot('test', [ + 'class' => 'foo', + 'attributes' => $attributes, + ]); + + $this->assertSame('class="foo bar" type="button"', (string) $slot->attributes); + + // Test overriding parent class attributes + $slot->withAttributes(['class' => 'override', 'type' => 'submit']); + $this->assertSame('class="override" type="submit"', (string) $slot->attributes); + } + + public function testPublicMethodsWithNoArgsAreConvertedToStringableCallablesInvokedAndNotCached() + { + $component = new TestSampleViewComponent; + + $this->assertEquals(0, $component->counter); + $this->assertEquals(0, TestSampleViewComponent::$publicStaticCounter); + $variables = $component->data(); + $this->assertEquals(0, $component->counter); + $this->assertEquals(0, TestSampleViewComponent::$publicStaticCounter); + + $this->assertSame('noArgs val', $variables['noArgs']()); + $this->assertSame('noArgs val', (string) $variables['noArgs']); + $this->assertEquals(0, $variables['counter']); + + // make sure non-public members are not invoked nor counted. + $this->assertEquals(2, $component->counter); + $this->assertArrayHasKey('publicHello', $variables); + $this->assertArrayNotHasKey('protectedHello', $variables); + $this->assertArrayNotHasKey('privateHello', $variables); + + $this->assertArrayNotHasKey('publicStaticCounter', $variables); + $this->assertArrayNotHasKey('protectedCounter', $variables); + $this->assertArrayNotHasKey('privateCounter', $variables); + + // test each time we invoke data(), the non-argument methods aren't invoked + $this->assertEquals(2, $component->counter); + $component->data(); + $this->assertEquals(2, $component->counter); + $component->data(); + $this->assertEquals(2, $component->counter); + } + + public function testItIgnoresExceptedMethodsAndProperties() + { + $component = new TestExceptedViewComponent; + $variables = $component->data(); + + // Ignored methods (with no args) are not invoked behind the scenes. + $this->assertSame('Otwell', $component->taylor); + + $this->assertArrayNotHasKey('hello', $variables); + $this->assertArrayNotHasKey('hello2', $variables); + $this->assertArrayNotHasKey('taylor', $variables); + } + + public function testMethodsOverridePropertyValues() + { + $component = new TestHelloPropertyHelloMethodComponent; + $variables = $component->data(); + $this->assertArrayHasKey('hello', $variables); + $this->assertSame('world', $variables['hello']()); + + // protected methods do not override public properties. + $this->assertArrayHasKey('world', $variables); + $this->assertSame('world property', $variables['world']); + } +} + +class TestViewComponent extends Component +{ + public $votes = 10; + + public function render() + { + return 'test'; + } + + public function hello($string = 'world') + { + return $string; + } +} + +class TestSampleViewComponent extends Component +{ + public $counter = 0; + + public static $publicStaticCounter = 0; + + protected $protectedCounter = 0; + + private $privateCounter = 0; + + public function render() + { + return 'test'; + } + + public function publicHello($string = 'world') + { + $this->counter = 100; + + return $string; + } + + public function noArgs() + { + $this->counter++; + + return 'noArgs val'; + } + + protected function protectedHello() + { + $this->counter++; + } + + private function privateHello() + { + $this->counter++; + } +} + +class TestExceptedViewComponent extends Component +{ + protected $except = ['hello', 'hello2', 'taylor']; + + public $taylor = 'Otwell'; + + public function hello($string = 'world') + { + return $string; + } + + public function hello2() + { + return $this->taylor = ''; + } + + public function render() + { + return 'test'; + } +} + +class TestHelloPropertyHelloMethodComponent extends Component +{ + public function render() + { + return 'test'; + } + + public $hello = 'hello property'; + + public $world = 'world property'; + + public function hello($string = 'world') + { + return $string; + } + + protected function world($string = 'world') + { + return $string; + } +} + +class TestDefaultAttributesComponent extends Component +{ + public function __construct() + { + $this->withAttributes(['class' => 'text-red-500']); + } + + public function render() + { + return $this->attributes->get('id'); + } +} diff --git a/tests/View/ViewEngineResolverTest.php b/tests/View/ViewEngineResolverTest.php new file mode 100755 index 000000000..e81785e32 --- /dev/null +++ b/tests/View/ViewEngineResolverTest.php @@ -0,0 +1,30 @@ +register('foo', function () { + return new stdClass; + }); + $result = $resolver->resolve('foo'); + + $this->assertEquals(spl_object_hash($result), spl_object_hash($resolver->resolve('foo'))); + } + + public function testResolverThrowsExceptionOnUnknownEngine() + { + $this->expectException(InvalidArgumentException::class); + + $resolver = new EngineResolver; + $resolver->resolve('foo'); + } +} diff --git a/tests/View/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php new file mode 100755 index 000000000..5465c78af --- /dev/null +++ b/tests/View/ViewFactoryTest.php @@ -0,0 +1,1101 @@ +getFactory(); + $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class)); + $factory->getFinder()->shouldReceive('addExtension')->once()->with('php'); + $factory->setDispatcher(new Dispatcher); + $factory->creator('view', function ($view) { + $_SERVER['__test.view'] = $view; + }); + $factory->addExtension('php', 'php'); + $view = $factory->make('view', ['foo' => 'bar'], ['baz' => 'boom']); + + $this->assertSame($engine, $view->getEngine()); + $this->assertSame($_SERVER['__test.view'], $view); + + unset($_SERVER['__test.view']); + } + + public function testExistsPassesAndFailsViews() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->once()->with('foo')->andThrow(InvalidArgumentException::class); + $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andReturn('path.php'); + + $this->assertFalse($factory->exists('foo')); + $this->assertTrue($factory->exists('bar')); + } + + public function testRenderingOnceChecks() + { + $factory = $this->getFactory(); + $this->assertFalse($factory->hasRenderedOnce('foo')); + $factory->markAsRenderedOnce('foo'); + $this->assertTrue($factory->hasRenderedOnce('foo')); + $factory->flushState(); + $this->assertFalse($factory->hasRenderedOnce('foo')); + } + + public function testFirstCreatesNewViewInstanceWithProperPath() + { + unset($_SERVER['__test.view']); + + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->twice()->with('view')->andReturn('path.php'); + $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class); + $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class)); + $factory->getFinder()->shouldReceive('addExtension')->once()->with('php'); + $factory->setDispatcher(new Dispatcher); + $factory->creator('view', function ($view) { + $_SERVER['__test.view'] = $view; + }); + $factory->addExtension('php', 'php'); + $view = $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']); + + $this->assertInstanceOf(ViewContract::class, $view); + $this->assertSame($engine, $view->getEngine()); + $this->assertSame($_SERVER['__test.view'], $view); + + unset($_SERVER['__test.view']); + } + + public function testFirstThrowsInvalidArgumentExceptionIfNoneFound() + { + $this->expectException(InvalidArgumentException::class); + + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->once()->with('view')->andThrow(InvalidArgumentException::class); + $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class); + $factory->getEngineResolver()->shouldReceive('resolve')->with('php')->andReturn($engine = m::mock(Engine::class)); + $factory->getFinder()->shouldReceive('addExtension')->with('php'); + $factory->addExtension('php', 'php'); + $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']); + } + + public function testRenderEachCreatesViewForEachItemInArray() + { + $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs()); + $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock(stdClass::class)); + $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock(stdClass::class)); + $mockView1->shouldReceive('render')->once()->andReturn('dayle'); + $mockView2->shouldReceive('render')->once()->andReturn('rees'); + + $result = $factory->renderEach('foo', ['bar' => 'baz', 'breeze' => 'boom'], 'value'); + + $this->assertSame('daylerees', $result); + } + + public function testEmptyViewsCanBeReturnedFromRenderEach() + { + $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs()); + $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock(stdClass::class)); + $mockView->shouldReceive('render')->once()->andReturn('empty'); + + $this->assertSame('empty', $factory->renderEach('view', [], 'iterator', 'foo')); + } + + public function testRawStringsMayBeReturnedFromRenderEach() + { + $this->assertSame('foo', $this->getFactory()->renderEach('foo', [], 'item', 'raw|foo')); + } + + public function testEnvironmentAddsExtensionWithCustomResolver() + { + $factory = $this->getFactory(); + + $resolver = function () { + // + }; + + $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo'); + $factory->getEngineResolver()->shouldReceive('register')->once()->with('bar', $resolver); + $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.foo'); + $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('bar')->andReturn($engine = m::mock(Engine::class)); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); + + $factory->addExtension('foo', 'bar', $resolver); + + $view = $factory->make('view', ['data']); + $this->assertSame($engine, $view->getEngine()); + } + + public function testAddingExtensionPrependsNotAppends() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo'); + + $factory->addExtension('foo', 'bar'); + + $extensions = $factory->getExtensions(); + $this->assertSame('bar', reset($extensions)); + $this->assertSame('foo', key($extensions)); + } + + public function testPrependedExtensionOverridesExistingExtensions() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo'); + $factory->getFinder()->shouldReceive('addExtension')->once()->with('baz'); + + $factory->addExtension('foo', 'bar'); + $factory->addExtension('baz', 'bar'); + + $extensions = $factory->getExtensions(); + $this->assertSame('bar', reset($extensions)); + $this->assertSame('baz', key($extensions)); + } + + public function testCallCreatorsDoesDispatchEventsWhenIsNecessary() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('creating: name', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('creating: name', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('name'); + + $factory->creator('name', fn () => true); + + $factory->callCreator($view); + } + + public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNamespacedWildcards() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('creating: namespaced::*', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('creating: namespaced::my-package-view', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('namespaced::my-package-view'); + + $factory->creator('namespaced::*', fn () => true); + + $factory->callCreator($view); + } + + public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNamespacedNestedWildcards() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('creating: namespaced::*', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('creating: welcome', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('creating: namespaced::my-package-view', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('namespaced::my-package-view'); + + $factory->creator(['namespaced::*', 'welcome'], fn () => true); + + $factory->callCreator($view); + } + + public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingWildcards() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('creating: *', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('creating: name', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('name'); + + $factory->creator('*', fn () => true); + + $factory->callCreator($view); + } + + public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNormalizedNames() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('creating: components.button', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('creating: components/button', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name') + ->once() + ->andReturn('components/button'); + + $factory->creator('components.button', fn () => true); + + $factory->callCreator($view); + } + + public function testCallComposerDoesDispatchEventsWhenIsNecessary() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('composing: name', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('composing: name', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('name'); + + $factory->composer('name', fn () => true); + + $factory->callComposer($view); + } + + public function testCallComposerDoesDispatchEventsWhenIsNecessaryAndUsingTheArrayFormat() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('composing: name', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('composing: name', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('name'); + + $factory->composer(['name'], fn () => true); + + $factory->callComposer($view); + } + + public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingNamespacedWildcards() + { + $factory = $this->getFactory(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('composing: namespaced::*', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('composing: namespaced::my-package-view', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('namespaced::my-package-view'); + + $factory->composer('namespaced::*', fn () => true); + + $factory->callComposer($view); + } + + public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingNamespacedNestedWildcards() + { + $factory = $this->getFactory(); + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('composing: namespaced::*', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('composing: welcome', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('composing: namespaced::my-package-view', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('namespaced::my-package-view'); + + $factory->composer(['namespaced::*', 'welcome'], fn () => true); + + $factory->callComposer($view); + } + + public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingWildcards() + { + $factory = $this->getFactory(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('composing: *', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('composing: name', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('name'); + + $factory->composer('*', fn () => true); + + $factory->callComposer($view); + } + + public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingNormalizedNames() + { + $factory = $this->getFactory(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + + $factory->getDispatcher() + ->shouldReceive('listen') + ->with('composing: components.button', m::type(Closure::class)) + ->once(); + + $factory->getDispatcher() + ->shouldReceive('dispatch') + ->with('composing: components/button', m::type('array')) + ->once(); + + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('components/button'); + + $factory->composer('components.button', fn () => true); + + $factory->callComposer($view); + } + + public function testComposersAreProperlyRegistered() + { + $factory = $this->getFactory(); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); + $callback = $factory->composer('foo', function () { + return 'bar'; + }); + $callback = $callback[0]; + + $this->assertSame('bar', $callback()); + } + + public function testComposersCanBeMassRegistered() + { + $factory = $this->getFactory(); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: bar', m::type(Closure::class)); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: qux', m::type(Closure::class)); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); + $composers = $factory->composers([ + 'foo' => 'bar', + 'baz@baz' => ['qux', 'foo'], + ]); + + $this->assertCount(3, $composers); + $reflections = [ + new ReflectionFunction($composers[0]), + new ReflectionFunction($composers[1]), + ]; + $this->assertEquals(['class' => 'foo', 'method' => 'compose'], $reflections[0]->getStaticVariables()); + $this->assertEquals(['class' => 'baz', 'method' => 'baz'], $reflections[1]->getStaticVariables()); + } + + public function testClassCallbacks() + { + $factory = $this->getFactory(); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); + $factory->setContainer($container = m::mock(Container::class)); + $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class)); + $composer->shouldReceive('compose')->once()->with('view')->andReturn('composed'); + $callback = $factory->composer('foo', 'FooComposer'); + $callback = $callback[0]; + + $this->assertSame('composed', $callback('view')); + } + + public function testClassCallbacksWithMethods() + { + $factory = $this->getFactory(); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); + $factory->setContainer($container = m::mock(Container::class)); + $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class)); + $composer->shouldReceive('doComposer')->once()->with('view')->andReturn('composed'); + $callback = $factory->composer('foo', 'FooComposer@doComposer'); + $callback = $callback[0]; + + $this->assertSame('composed', $callback('view')); + } + + public function testCallComposerCallsProperEvent() + { + $factory = $this->getFactory(); + $view = m::mock(View::class); + $dispatcher = m::mock(DispatcherContract::class); + $factory->setDispatcher($dispatcher); + + $dispatcher->shouldReceive('listen', m::any())->once(); + + $view->shouldReceive('name')->once()->andReturn('name'); + + $factory->composer('name', fn () => true); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher()->shouldReceive('dispatch')->once()->with('composing: name', [$view]); + + $factory->callComposer($view); + } + + public function testComposersAreRegisteredWithSlashAndDot() + { + $factory = $this->getFactory(); + $factory->getDispatcher()->shouldReceive('listen')->with('composing: foo.bar', m::any())->twice(); + $factory->composer('foo.bar', ''); + $factory->composer('foo/bar', ''); + } + + public function testRenderCountHandling() + { + $factory = $this->getFactory(); + $factory->incrementRender(); + $this->assertFalse($factory->doneRendering()); + $factory->decrementRender(); + $this->assertTrue($factory->doneRendering()); + } + + public function testYieldDefault() + { + $factory = $this->getFactory(); + $this->assertSame('hi', $factory->yieldContent('foo', 'hi')); + } + + public function testYieldDefaultIsEscaped() + { + $factory = $this->getFactory(); + $this->assertSame('<p>hi</p>', $factory->yieldContent('foo', '

hi

')); + } + + public function testYieldDefaultViewIsNotEscapedTwice() + { + $factory = $this->getFactory(); + $view = m::mock(View::class); + $view->shouldReceive('__toString')->once()->andReturn('

hi

<p>already escaped</p>'); + $this->assertSame('

hi

<p>already escaped</p>', $factory->yieldContent('foo', $view)); + } + + public function testBasicFragmentHandling() + { + $factory = $this->getFactory(); + $factory->startFragment('foo'); + echo 'hi'; + $this->assertSame('hi', $factory->stopFragment()); + } + + public function testBasicSectionHandling() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi'; + $factory->stopSection(); + $this->assertSame('hi', $factory->yieldContent('foo')); + } + + public function testBasicSectionDefault() + { + $factory = $this->getFactory(); + $factory->startSection('foo', 'hi'); + $this->assertSame('hi', $factory->yieldContent('foo')); + } + + public function testBasicSectionDefaultIsEscaped() + { + $factory = $this->getFactory(); + $factory->startSection('foo', '

hi

'); + $this->assertSame('<p>hi</p>', $factory->yieldContent('foo')); + } + + public function testBasicSectionDefaultViewIsNotEscapedTwice() + { + $factory = $this->getFactory(); + $view = m::mock(View::class); + $view->shouldReceive('__toString')->once()->andReturn('

hi

<p>already escaped</p>'); + $factory->startSection('foo', $view); + $this->assertSame('

hi

<p>already escaped</p>', $factory->yieldContent('foo')); + } + + public function testSectionExtending() + { + $placeholder = Factory::parentPlaceholder('foo'); + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi '.$placeholder; + $factory->stopSection(); + $factory->startSection('foo'); + echo 'there'; + $factory->stopSection(); + $this->assertSame('hi there', $factory->yieldContent('foo')); + } + + public function testSectionMultipleExtending() + { + $placeholder = Factory::parentPlaceholder('foo'); + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hello '.$placeholder.' nice to see you '.$placeholder; + $factory->stopSection(); + $factory->startSection('foo'); + echo 'my '.$placeholder; + $factory->stopSection(); + $factory->startSection('foo'); + echo 'friend'; + $factory->stopSection(); + $this->assertSame('hello my friend nice to see you my friend', $factory->yieldContent('foo')); + } + + public function testComponentHandling() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); + $factory->startComponent('component', ['name' => 'Taylor']); + $factory->slot('title'); + $factory->slot('website', 'laravel.com', []); + echo 'title
'; + $factory->endSlot(); + echo 'component'; + $contents = $factory->renderComponent(); + $this->assertSame('title
component Taylor laravel.com', $contents); + } + + public function testComponentHandlingUsingViewObject() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); + $factory->startComponent($factory->make('component'), ['name' => 'Taylor']); + $factory->slot('title'); + $factory->slot('website', 'laravel.com', []); + echo 'title
'; + $factory->endSlot(); + echo 'component'; + $contents = $factory->renderComponent(); + $this->assertSame('title
component Taylor laravel.com', $contents); + } + + public function testComponentHandlingUsingClosure() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); + $factory->startComponent(function ($data) use ($factory) { + $this->assertArrayHasKey('name', $data); + $this->assertSame($data['name'], 'Taylor'); + + return $factory->make('component'); + }, ['name' => 'Taylor']); + $factory->slot('title'); + $factory->slot('website', 'laravel.com', []); + echo 'title
'; + $factory->endSlot(); + echo 'component'; + $contents = $factory->renderComponent(); + $this->assertSame('title
component Taylor laravel.com', $contents); + } + + public function testComponentHandlingUsingHtmlable() + { + $factory = $this->getFactory(); + $factory->startComponent(new HtmlString('laravel.com')); + $contents = $factory->renderComponent(); + $this->assertSame('laravel.com', $contents); + } + + public function testTranslation() + { + $container = new Container; + $container->instance('translator', $translator = m::mock(stdClass::class)); + $translator->shouldReceive('get')->with('Foo', ['name' => 'taylor'])->andReturn('Bar'); + $factory = $this->getFactory(); + $factory->setContainer($container); + $factory->startTranslation(['name' => 'taylor']); + echo 'Foo'; + $string = $factory->renderTranslation(); + + $this->assertSame('Bar', $string); + } + + public function testSingleStackPush() + { + $factory = $this->getFactory(); + $factory->startPush('foo'); + echo 'hi'; + $factory->stopPush(); + $this->assertSame('hi', $factory->yieldPushContent('foo')); + } + + public function testMultipleStackPush() + { + $factory = $this->getFactory(); + $factory->startPush('foo'); + echo 'hi'; + $factory->stopPush(); + $factory->startPush('foo'); + echo ', Hello!'; + $factory->stopPush(); + $this->assertSame('hi, Hello!', $factory->yieldPushContent('foo')); + } + + public function testSingleStackPrepend() + { + $factory = $this->getFactory(); + $factory->startPrepend('foo'); + echo 'hi'; + $factory->stopPrepend(); + $this->assertSame('hi', $factory->yieldPushContent('foo')); + } + + public function testMultipleStackPrepend() + { + $factory = $this->getFactory(); + $factory->startPrepend('foo'); + echo ', Hello!'; + $factory->stopPrepend(); + $factory->startPrepend('foo'); + echo 'hi'; + $factory->stopPrepend(); + $this->assertSame('hi, Hello!', $factory->yieldPushContent('foo')); + } + + public function testSessionAppending() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi'; + $factory->appendSection(); + $factory->startSection('foo'); + echo 'there'; + $factory->appendSection(); + $this->assertSame('hithere', $factory->yieldContent('foo')); + } + + public function testYieldSectionStopsAndYields() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi'; + $this->assertSame('hi', $factory->yieldSection()); + } + + public function testInjectStartsSectionWithContent() + { + $factory = $this->getFactory(); + $factory->inject('foo', 'hi'); + $this->assertSame('hi', $factory->yieldContent('foo')); + } + + public function testEmptyStringIsReturnedForNonSections() + { + $factory = $this->getFactory(); + $this->assertEmpty($factory->yieldContent('foo')); + } + + public function testSectionFlushing() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi'; + $factory->stopSection(); + + $this->assertCount(1, $factory->getSections()); + + $factory->flushSections(); + + $this->assertCount(0, $factory->getSections()); + } + + public function testHasSection() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi'; + $factory->stopSection(); + + $this->assertTrue($factory->hasSection('foo')); + $this->assertFalse($factory->hasSection('bar')); + } + + public function testSectionMissing() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hello world'; + $factory->stopSection(); + + $this->assertTrue($factory->sectionMissing('bar')); + $this->assertFalse($factory->sectionMissing('foo')); + } + + public function testGetSection() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi'; + $factory->stopSection(); + + $this->assertSame('hi', $factory->getSection('foo')); + $this->assertNull($factory->getSection('bar')); + $this->assertSame('default', $factory->getSection('bar', 'default')); + } + + public function testMakeWithSlashAndDot() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->twice()->with('foo.bar')->andReturn('path.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class)); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); + $factory->make('foo/bar'); + $factory->make('foo.bar'); + } + + public function testNamespacedViewNamesAreNormalizedProperly() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->twice()->with('vendor/package::foo.bar')->andReturn('path.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class)); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); + $factory->make('vendor/package::foo/bar'); + $factory->make('vendor/package::foo.bar'); + } + + public function testExceptionIsThrownForUnknownExtension() + { + $this->expectException(InvalidArgumentException::class); + + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('view.foo'); + $factory->make('view'); + } + + public function testExceptionsInSectionsAreThrown() + { + $this->expectException(ErrorException::class); + $this->expectExceptionMessage('section exception message'); + + $engine = new CompilerEngine(m::mock(CompilerInterface::class), new Filesystem); + $engine->getCompiler()->shouldReceive('getCompiledPath')->andReturnUsing(function ($path) { + return $path; + }); + $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false); + $factory = $this->getFactory(); + $factory->getEngineResolver()->shouldReceive('resolve')->twice()->andReturn($engine); + $factory->getFinder()->shouldReceive('find')->once()->with('layout')->andReturn(__DIR__.'/fixtures/section-exception-layout.php'); + $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn(__DIR__.'/fixtures/section-exception.php'); + $factory->getDispatcher()->shouldReceive('hasListeners')->times(4); // 2 "creating" + 2 "composing"... + + $factory->make('view')->render(); + } + + public function testExtraStopSectionCallThrowsException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot end a section without first starting one.'); + + $factory = $this->getFactory(); + $factory->startSection('foo'); + $factory->stopSection(); + + $factory->stopSection(); + } + + public function testExtraAppendSectionCallThrowsException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot end a section without first starting one.'); + + $factory = $this->getFactory(); + $factory->startSection('foo'); + $factory->stopSection(); + + $factory->appendSection(); + } + + public function testAddingLoops() + { + $factory = $this->getFactory(); + + $factory->addLoop([1, 2, 3]); + + $expectedLoop = [ + 'iteration' => 0, + 'index' => 0, + 'remaining' => 3, + 'count' => 3, + 'first' => true, + 'last' => false, + 'odd' => false, + 'even' => true, + 'depth' => 1, + 'parent' => null, + ]; + + $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + + $factory->addLoop([1, 2, 3, 4]); + + $secondExpectedLoop = [ + 'iteration' => 0, + 'index' => 0, + 'remaining' => 4, + 'count' => 4, + 'first' => true, + 'last' => false, + 'odd' => false, + 'even' => true, + 'depth' => 2, + 'parent' => (object) $expectedLoop, + ]; + $this->assertEquals([$expectedLoop, $secondExpectedLoop], $factory->getLoopStack()); + + $factory->popLoop(); + + $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + } + + public function testAddingLoopDoesNotCloseGenerator() + { + $factory = $this->getFactory(); + + $data = (new class + { + public function generate() + { + for ($count = 0; $count < 3; $count++) { + yield ['a', 'b']; + } + } + })->generate(); + + $factory->addLoop($data); + + foreach ($data as $chunk) { + $this->assertEquals(['a', 'b'], $chunk); + } + } + + public function testAddingUncountableLoop() + { + $factory = $this->getFactory(); + + $factory->addLoop(''); + + $expectedLoop = [ + 'iteration' => 0, + 'index' => 0, + 'remaining' => null, + 'count' => null, + 'first' => true, + 'last' => null, + 'odd' => false, + 'even' => true, + 'depth' => 1, + 'parent' => null, + ]; + + $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + } + + public function testAddingLazyCollection() + { + $factory = $this->getFactory(); + + $factory->addLoop(new LazyCollection(function () { + $this->fail('LazyCollection\'s generator should not have been called'); + })); + + $expectedLoop = [ + 'iteration' => 0, + 'index' => 0, + 'remaining' => null, + 'count' => null, + 'first' => true, + 'last' => null, + 'odd' => false, + 'even' => true, + 'depth' => 1, + 'parent' => null, + ]; + + $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + } + + public function testIncrementingLoopIndices() + { + $factory = $this->getFactory(); + + $factory->addLoop([1, 2, 3, 4]); + + $factory->incrementLoopIndices(); + + $this->assertEquals(1, $factory->getLoopStack()[0]['iteration']); + $this->assertEquals(0, $factory->getLoopStack()[0]['index']); + $this->assertEquals(3, $factory->getLoopStack()[0]['remaining']); + $this->assertTrue($factory->getLoopStack()[0]['odd']); + $this->assertFalse($factory->getLoopStack()[0]['even']); + + $factory->incrementLoopIndices(); + + $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']); + $this->assertEquals(1, $factory->getLoopStack()[0]['index']); + $this->assertEquals(2, $factory->getLoopStack()[0]['remaining']); + $this->assertFalse($factory->getLoopStack()[0]['odd']); + $this->assertTrue($factory->getLoopStack()[0]['even']); + } + + public function testReachingEndOfLoop() + { + $factory = $this->getFactory(); + + $factory->addLoop([1, 2]); + + $factory->incrementLoopIndices(); + + $factory->incrementLoopIndices(); + + $this->assertTrue($factory->getLoopStack()[0]['last']); + } + + public function testIncrementingLoopIndicesOfUncountable() + { + $factory = $this->getFactory(); + + $factory->addLoop(''); + + $factory->incrementLoopIndices(); + + $factory->incrementLoopIndices(); + + $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']); + $this->assertEquals(1, $factory->getLoopStack()[0]['index']); + $this->assertFalse($factory->getLoopStack()[0]['first']); + $this->assertNull($factory->getLoopStack()[0]['remaining']); + $this->assertNull($factory->getLoopStack()[0]['last']); + } + + public function testMacro() + { + $factory = $this->getFactory(); + $factory->macro('getFoo', function () { + return 'Hello World'; + }); + $this->assertSame('Hello World', $factory->getFoo()); + } + + protected function getFactory() + { + return new Factory( + m::mock(EngineResolver::class), + m::mock(ViewFinderInterface::class), + m::mock(DispatcherContract::class) + ); + } + + protected function getFactoryArgs() + { + return [ + m::mock(EngineResolver::class), + m::mock(ViewFinderInterface::class), + m::mock(DispatcherContract::class), + ]; + } +} diff --git a/tests/View/ViewFileViewFinderTest.php b/tests/View/ViewFileViewFinderTest.php new file mode 100755 index 000000000..92f8e954b --- /dev/null +++ b/tests/View/ViewFileViewFinderTest.php @@ -0,0 +1,170 @@ +getFinder(); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(true); + + $this->assertEquals(__DIR__.'/foo.blade.php', $finder->find('foo')); + } + + public function testCascadingFileLoading() + { + $finder = $this->getFinder(); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(true); + + $this->assertEquals(__DIR__.'/foo.php', $finder->find('foo')); + } + + public function testDirectoryCascadingFileLoading() + { + $finder = $this->getFinder(); + $finder->addLocation(__DIR__.'/nested'); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.css')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.html')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/nested/foo.blade.php')->andReturn(true); + + $this->assertEquals(__DIR__.'/nested/foo.blade.php', $finder->find('foo')); + } + + public function testNamespacedBasicFileLoading() + { + $finder = $this->getFinder(); + $finder->addNamespace('foo', __DIR__.'/foo'); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(true); + + $this->assertEquals(__DIR__.'/foo/bar/baz.blade.php', $finder->find('foo::bar.baz')); + } + + public function testCascadingNamespacedFileLoading() + { + $finder = $this->getFinder(); + $finder->addNamespace('foo', __DIR__.'/foo'); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.php')->andReturn(true); + + $this->assertEquals(__DIR__.'/foo/bar/baz.php', $finder->find('foo::bar.baz')); + } + + public function testDirectoryCascadingNamespacedFileLoading() + { + $finder = $this->getFinder(); + $finder->addNamespace('foo', [__DIR__.'/foo', __DIR__.'/bar']); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.css')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.html')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/bar/bar/baz.blade.php')->andReturn(true); + + $this->assertEquals(__DIR__.'/bar/bar/baz.blade.php', $finder->find('foo::bar.baz')); + } + + public function testExceptionThrownWhenViewNotFound() + { + $this->expectException(InvalidArgumentException::class); + + $finder = $this->getFinder(); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.css')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.html')->andReturn(false); + + $finder->find('foo'); + } + + public function testExceptionThrownOnInvalidViewName() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No hint path defined for [name].'); + + $finder = $this->getFinder(); + $finder->find('name::'); + } + + public function testExceptionThrownWhenNoHintPathIsRegistered() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No hint path defined for [name].'); + + $finder = $this->getFinder(); + $finder->find('name::foo'); + } + + public function testAddingExtensionPrependsNotAppends() + { + $finder = $this->getFinder(); + $finder->addExtension('baz'); + $extensions = $finder->getExtensions(); + $this->assertSame('baz', reset($extensions)); + } + + public function testAddingExtensionsReplacesOldOnes() + { + $finder = $this->getFinder(); + $finder->addExtension('baz'); + $finder->addExtension('baz'); + + $this->assertCount(5, $finder->getExtensions()); + } + + public function testPassingViewWithHintReturnsTrue() + { + $finder = $this->getFinder(); + + $this->assertTrue($finder->hasHintInformation('hint::foo.bar')); + } + + public function testPassingViewWithoutHintReturnsFalse() + { + $finder = $this->getFinder(); + + $this->assertFalse($finder->hasHintInformation('foo.bar')); + } + + public function testPassingViewWithFalseHintReturnsFalse() + { + $finder = $this->getFinder(); + + $this->assertFalse($finder->hasHintInformation('::foo.bar')); + } + + public static function pathsProvider() + { + return [ + ['incorrect_path', 'incorrect_path'], + ]; + } + + #[DataProvider('pathsProvider')] + public function testNormalizedPaths($originalPath, $exceptedPath) + { + $finder = $this->getFinder(); + $finder->prependLocation($originalPath); + $normalizedPath = $finder->getPaths()[0]; + $this->assertSame($exceptedPath, $normalizedPath); + } + + protected function getFinder() + { + return new FileViewFinder(m::mock(Filesystem::class), [__DIR__]); + } +} diff --git a/tests/View/ViewPhpEngineTest.php b/tests/View/ViewPhpEngineTest.php new file mode 100755 index 000000000..0f4b1de29 --- /dev/null +++ b/tests/View/ViewPhpEngineTest.php @@ -0,0 +1,17 @@ +assertSame('Hello World +', $engine->get(__DIR__.'/fixtures/basic.php')); + } +} diff --git a/tests/View/ViewTest.php b/tests/View/ViewTest.php new file mode 100755 index 000000000..308e2e81e --- /dev/null +++ b/tests/View/ViewTest.php @@ -0,0 +1,251 @@ +getView(); + $view->with('foo', 'bar'); + $view->with(['baz' => 'boom']); + $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData()); + + $view = $this->getView(); + $view->withFoo('bar')->withBaz('boom'); + $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData()); + } + + public function testRenderProperlyRendersView() + { + $view = $this->getView(['foo' => 'bar']); + $view->getFactory()->shouldReceive('incrementRender')->once()->ordered(); + $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view); + $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); + $view->getEngine()->shouldReceive('get')->once()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents'); + $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); + + $callback = function (View $rendered, $contents) use ($view) { + $this->assertEquals($view, $rendered); + $this->assertSame('contents', $contents); + }; + + $this->assertSame('contents', $view->render($callback)); + } + + public function testRenderHandlingCallbackReturnValues() + { + $view = $this->getView(); + $view->getFactory()->shouldReceive('incrementRender'); + $view->getFactory()->shouldReceive('callComposer'); + $view->getFactory()->shouldReceive('getShared')->andReturn(['shared' => 'foo']); + $view->getEngine()->shouldReceive('get')->andReturn('contents'); + $view->getFactory()->shouldReceive('decrementRender'); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering'); + + $this->assertSame('new contents', $view->render(function () { + return 'new contents'; + })); + + $this->assertEmpty($view->render(function () { + return ''; + })); + + $this->assertSame('contents', $view->render(function () { + // + })); + } + + public function testRenderSectionsReturnsEnvironmentSections() + { + $view = m::mock(View::class.'[render]', [ + m::mock(Factory::class), + m::mock(Engine::class), + 'view', + 'path', + [], + ]); + + $view->shouldReceive('render')->with(m::type(Closure::class))->once()->andReturn($sections = ['foo' => 'bar']); + + $this->assertEquals($sections, $view->renderSections()); + } + + public function testSectionsAreNotFlushedWhenNotDoneRendering() + { + $view = $this->getView(['foo' => 'bar']); + $view->getFactory()->shouldReceive('incrementRender')->twice(); + $view->getFactory()->shouldReceive('callComposer')->twice()->with($view); + $view->getFactory()->shouldReceive('getShared')->twice()->andReturn(['shared' => 'foo']); + $view->getEngine()->shouldReceive('get')->twice()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents'); + $view->getFactory()->shouldReceive('decrementRender')->twice(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->twice(); + + $this->assertSame('contents', $view->render()); + $this->assertSame('contents', (string) $view); + } + + public function testViewNestBindsASubView() + { + $view = $this->getView(); + $view->getFactory()->shouldReceive('make')->once()->with('foo', ['data']); + $result = $view->nest('key', 'foo', ['data']); + + $this->assertInstanceOf(View::class, $result); + } + + public function testViewAcceptsArrayableImplementations() + { + $arrayable = m::mock(Arrayable::class); + $arrayable->shouldReceive('toArray')->once()->andReturn(['foo' => 'bar', 'baz' => ['qux', 'corge']]); + + $view = $this->getView($arrayable); + + $this->assertSame('bar', $view->foo); + $this->assertEquals(['qux', 'corge'], $view->baz); + } + + public function testViewGettersSetters() + { + $view = $this->getView(['foo' => 'bar']); + $this->assertSame('view', $view->name()); + $this->assertSame('path', $view->getPath()); + $data = $view->getData(); + $this->assertSame('bar', $data['foo']); + $view->setPath('newPath'); + $this->assertSame('newPath', $view->getPath()); + } + + public function testViewArrayAccess() + { + $view = $this->getView(['foo' => 'bar']); + $this->assertInstanceOf(ArrayAccess::class, $view); + $this->assertTrue($view->offsetExists('foo')); + $this->assertSame('bar', $view->offsetGet('foo')); + $view->offsetSet('foo', 'baz'); + $this->assertSame('baz', $view->offsetGet('foo')); + $view->offsetUnset('foo'); + $this->assertFalse($view->offsetExists('foo')); + } + + public function testViewConstructedWithObjectData() + { + $view = $this->getView(new DataObjectStub); + $this->assertInstanceOf(ArrayAccess::class, $view); + $this->assertTrue($view->offsetExists('foo')); + $this->assertSame('bar', $view->offsetGet('foo')); + $view->offsetSet('foo', 'baz'); + $this->assertSame('baz', $view->offsetGet('foo')); + $view->offsetUnset('foo'); + $this->assertFalse($view->offsetExists('foo')); + } + + public function testViewMagicMethods() + { + $view = $this->getView(['foo' => 'bar']); + $this->assertTrue(isset($view->foo)); + $this->assertSame('bar', $view->foo); + $view->foo = 'baz'; + $this->assertSame('baz', $view->foo); + $this->assertEquals($view['foo'], $view->foo); + unset($view->foo); + $this->assertFalse(isset($view->foo)); + $this->assertFalse($view->offsetExists('foo')); + } + + public function testViewBadMethod() + { + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage('Method Illuminate\View\View::badMethodCall does not exist.'); + + $view = $this->getView(); + $view->badMethodCall(); + } + + public function testViewGatherDataWithRenderable() + { + $view = $this->getView(); + $view->getFactory()->shouldReceive('incrementRender')->once()->ordered(); + $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view); + $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); + $view->getEngine()->shouldReceive('get')->once()->andReturn('contents'); + $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); + + $view->renderable = m::mock(Renderable::class); + $view->renderable->shouldReceive('render')->once()->andReturn('text'); + $this->assertSame('contents', $view->render()); + } + + public function testViewRenderSections() + { + $view = $this->getView(); + $view->getFactory()->shouldReceive('incrementRender')->once()->ordered(); + $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view); + $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); + $view->getEngine()->shouldReceive('get')->once()->andReturn('contents'); + $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); + + $view->getFactory()->shouldReceive('getSections')->once()->andReturn(['foo', 'bar']); + $sections = $view->renderSections(); + $this->assertSame('foo', $sections[0]); + $this->assertSame('bar', $sections[1]); + } + + public function testWithErrors() + { + $view = $this->getView(); + $errors = ['foo' => 'bar', 'qu' => 'ux']; + $this->assertSame($view, $view->withErrors($errors)); + $this->assertInstanceOf(ViewErrorBag::class, $view->errors); + $foo = $view->errors->get('foo'); + $this->assertSame('bar', $foo[0]); + $qu = $view->errors->get('qu'); + $this->assertSame('ux', $qu[0]); + $data = ['foo' => 'baz']; + $this->assertSame($view, $view->withErrors(new MessageBag($data))); + $foo = $view->errors->get('foo'); + $this->assertSame('baz', $foo[0]); + $foo = $view->errors->getBag('default')->get('foo'); + $this->assertSame('baz', $foo[0]); + $this->assertSame($view, $view->withErrors(new MessageBag($data), 'login')); + $foo = $view->errors->getBag('login')->get('foo'); + $this->assertSame('baz', $foo[0]); + } + + protected function getView($data = []) + { + return new View( + m::mock(Factory::class), + m::mock(Engine::class), + 'view', + 'path', + $data + ); + } +} + +class DataObjectStub +{ + public $foo = 'bar'; +} diff --git a/tests/View/fixtures/basic.php b/tests/View/fixtures/basic.php new file mode 100755 index 000000000..57abe576b --- /dev/null +++ b/tests/View/fixtures/basic.php @@ -0,0 +1,2 @@ + +Hello World diff --git a/tests/View/fixtures/component.php b/tests/View/fixtures/component.php new file mode 100644 index 000000000..4af443db8 --- /dev/null +++ b/tests/View/fixtures/component.php @@ -0,0 +1 @@ + diff --git a/tests/View/fixtures/http-exception.php b/tests/View/fixtures/http-exception.php new file mode 100644 index 000000000..4e239b49a --- /dev/null +++ b/tests/View/fixtures/http-exception.php @@ -0,0 +1,5 @@ + diff --git a/tests/View/fixtures/regular-exception.php b/tests/View/fixtures/regular-exception.php new file mode 100644 index 000000000..230e90754 --- /dev/null +++ b/tests/View/fixtures/regular-exception.php @@ -0,0 +1,3 @@ +yieldContent('content'); diff --git a/tests/View/fixtures/section-exception.php b/tests/View/fixtures/section-exception.php new file mode 100644 index 000000000..ce553295e --- /dev/null +++ b/tests/View/fixtures/section-exception.php @@ -0,0 +1,4 @@ +make('layout', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?> +startSection('content'); ?> + +stopSection(); ?> From 290239689bb6d4fe70421be1ec981ed4fceec68c Mon Sep 17 00:00:00 2001 From: Yu Shing Date: Wed, 15 Oct 2025 11:27:47 +0800 Subject: [PATCH 05/21] feat: use Hypervel namespace --- tests/View/Blade/AbstractBladeTestCase.php | 12 +- tests/View/Blade/BladeAppendTest.php | 2 +- tests/View/Blade/BladeBoolTest.php | 2 +- tests/View/Blade/BladeBreakStatementsTest.php | 2 +- tests/View/Blade/BladeCanStatementsTest.php | 6 +- .../View/Blade/BladeCananyStatementsTest.php | 6 +- .../View/Blade/BladeCannotStatementsTest.php | 6 +- .../View/Blade/BladeCheckedStatementsTest.php | 2 +- tests/View/Blade/BladeClassTest.php | 4 +- tests/View/Blade/BladeCommentsTest.php | 2 +- tests/View/Blade/BladeComponentFirstTest.php | 2 +- .../Blade/BladeComponentTagCompilerTest.php | 286 +++++++++--------- tests/View/Blade/BladeComponentsTest.php | 12 +- .../Blade/BladeContinueStatementsTest.php | 2 +- tests/View/Blade/BladeCustomTest.php | 14 +- tests/View/Blade/BladeEchoHandlerTest.php | 6 +- tests/View/Blade/BladeEchoTest.php | 2 +- .../Blade/BladeElseAuthStatementsTest.php | 2 +- .../Blade/BladeElseGuestStatementsTest.php | 2 +- .../View/Blade/BladeElseIfStatementsTest.php | 2 +- tests/View/Blade/BladeElseStatementsTest.php | 2 +- tests/View/Blade/BladeEndSectionsTest.php | 2 +- .../Blade/BladeEnvironmentStatementsTest.php | 2 +- tests/View/Blade/BladeErrorTest.php | 2 +- tests/View/Blade/BladeEscapedTest.php | 2 +- tests/View/Blade/BladeExpressionTest.php | 2 +- tests/View/Blade/BladeExtendsTest.php | 2 +- tests/View/Blade/BladeForStatementsTest.php | 2 +- .../View/Blade/BladeForeachStatementsTest.php | 4 +- .../View/Blade/BladeForelseStatementsTest.php | 4 +- tests/View/Blade/BladeFragmentTest.php | 2 +- tests/View/Blade/BladeHasSectionTest.php | 2 +- tests/View/Blade/BladeHelpersTest.php | 12 +- .../View/Blade/BladeIfAuthStatementsTest.php | 2 +- .../View/Blade/BladeIfEmptyStatementsTest.php | 2 +- .../View/Blade/BladeIfGuestStatementsTest.php | 2 +- .../View/Blade/BladeIfIssetStatementsTest.php | 2 +- tests/View/Blade/BladeIfStatementsTest.php | 2 +- tests/View/Blade/BladeIncludesTest.php | 2 +- tests/View/Blade/BladeInjectTest.php | 2 +- tests/View/Blade/BladeJsTest.php | 8 +- tests/View/Blade/BladeJsonTest.php | 2 +- tests/View/Blade/BladeLangTest.php | 2 +- .../View/Blade/BladeOverwriteSectionTest.php | 2 +- tests/View/Blade/BladePhpStatementsTest.php | 20 +- tests/View/Blade/BladePrependTest.php | 4 +- tests/View/Blade/BladePropsTest.php | 10 +- tests/View/Blade/BladePushTest.php | 4 +- tests/View/Blade/BladeSectionMissingTest.php | 2 +- tests/View/Blade/BladeSectionTest.php | 2 +- tests/View/Blade/BladeSessionTest.php | 2 +- tests/View/Blade/BladeShowTest.php | 2 +- tests/View/Blade/BladeStackTest.php | 2 +- tests/View/Blade/BladeStopSectionTest.php | 2 +- tests/View/Blade/BladeStyleTest.php | 4 +- .../View/Blade/BladeUnlessStatementsTest.php | 2 +- tests/View/Blade/BladeUnsetStatementsTest.php | 2 +- tests/View/Blade/BladeUseTest.php | 2 +- tests/View/Blade/BladeVerbatimTest.php | 2 +- tests/View/Blade/BladeWhileStatementsTest.php | 2 +- tests/View/Blade/BladeYieldTest.php | 2 +- tests/View/ComponentTest.php | 24 +- tests/View/ViewBladeCompilerTest.php | 6 +- tests/View/ViewCompilerEngineTest.php | 12 +- tests/View/ViewComponentAttributeBagTest.php | 4 +- tests/View/ViewComponentTest.php | 8 +- tests/View/ViewEngineResolverTest.php | 4 +- tests/View/ViewFactoryTest.php | 32 +- tests/View/ViewFileViewFinderTest.php | 6 +- tests/View/ViewPhpEngineTest.php | 6 +- tests/View/ViewTest.php | 18 +- 71 files changed, 314 insertions(+), 314 deletions(-) diff --git a/tests/View/Blade/AbstractBladeTestCase.php b/tests/View/Blade/AbstractBladeTestCase.php index 93e073940..aef40b939 100644 --- a/tests/View/Blade/AbstractBladeTestCase.php +++ b/tests/View/Blade/AbstractBladeTestCase.php @@ -1,18 +1,18 @@ check(\'update\', [$post])): ?> + $expected = 'check(\'update\', [$post])): ?> breeze -check(\'delete\', [$post])): ?> +check(\'delete\', [$post])): ?> sneeze '; $this->assertEquals($expected, $this->compiler->compileString($string)); diff --git a/tests/View/Blade/BladeCananyStatementsTest.php b/tests/View/Blade/BladeCananyStatementsTest.php index 59a67b3b6..d6a89136d 100644 --- a/tests/View/Blade/BladeCananyStatementsTest.php +++ b/tests/View/Blade/BladeCananyStatementsTest.php @@ -1,6 +1,6 @@ any([\'create\', \'update\'], [$post])): ?> + $expected = 'any([\'create\', \'update\'], [$post])): ?> breeze -any([\'delete\', \'approve\'], [$post])): ?> +any([\'delete\', \'approve\'], [$post])): ?> sneeze '; $this->assertEquals($expected, $this->compiler->compileString($string)); diff --git a/tests/View/Blade/BladeCannotStatementsTest.php b/tests/View/Blade/BladeCannotStatementsTest.php index bab28e4d0..207d34b95 100644 --- a/tests/View/Blade/BladeCannotStatementsTest.php +++ b/tests/View/Blade/BladeCannotStatementsTest.php @@ -1,6 +1,6 @@ denies(\'update\', [$post])): ?> + $expected = 'denies(\'update\', [$post])): ?> breeze -denies(\'delete\', [$post])): ?> +denies(\'delete\', [$post])): ?> sneeze '; $this->assertEquals($expected, $this->compiler->compileString($string)); diff --git a/tests/View/Blade/BladeCheckedStatementsTest.php b/tests/View/Blade/BladeCheckedStatementsTest.php index fd76b3656..e54a37aa2 100644 --- a/tests/View/Blade/BladeCheckedStatementsTest.php +++ b/tests/View/Blade/BladeCheckedStatementsTest.php @@ -1,6 +1,6 @@ true, 'mr-2' => false])>"; - $expected = " true, 'mr-2' => false]); ?>\">"; + $expected = " true, 'mr-2' => false]); ?>\">"; $this->assertEquals($expected, $this->compiler->compileString($string)); } diff --git a/tests/View/Blade/BladeCommentsTest.php b/tests/View/Blade/BladeCommentsTest.php index c5613a2c2..f5f6da058 100644 --- a/tests/View/Blade/BladeCommentsTest.php +++ b/tests/View/Blade/BladeCommentsTest.php @@ -1,6 +1,6 @@ '); $this->assertSame( - "@slot('foo', null, ['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$classes)]) \n".' @endslot', + "@slot('foo', null, ['class' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$classes)]) \n".' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -106,7 +106,7 @@ public function testSlotsWithClassDirectiveCanBeCompiled() '); $this->assertSame( - "@slot('foo', null, ['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(\$classes))]) \n".' @endslot', + "@slot('foo', null, ['class' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssClasses(\$classes))]) \n".' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -118,7 +118,7 @@ public function testSlotsWithStyleDirectiveCanBeCompiled() '); $this->assertSame( - "@slot('foo', null, ['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(\$styles))]) \n".' @endslot', + "@slot('foo', null, ['style' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssStyles(\$styles))]) \n".' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -129,14 +129,14 @@ public function testBasicComponentParsing() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['type' => 'foo','limit' => '5','@click' => 'foo','wire:click' => 'changePlan(\''.e(\$plan).'\')','required' => true,'x-intersect.margin.-50%.0px' => 'visibleSection = \'profile\'']); ?>\n". -"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> +"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); @@ -153,7 +153,7 @@ public function testNestedDefaultComponentParsing() $result = $this->compiler()->compileTags('
'); $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('App\View\Components\Card\Card', 'card', []) - + except(\App\View\Components\Card\Card::ignoredParameterNames()); ?> withAttributes([]); ?>\n". @@ -165,9 +165,9 @@ public function testBasicComponentWithEmptyAttributesParsing() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['type' => '','limit' => '','@click' => '','required' => true]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); @@ -178,9 +178,9 @@ public function testDataCamelCasing() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => '1']) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => '1']) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -190,9 +190,9 @@ public function testColonData() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -202,9 +202,9 @@ public function testColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -214,9 +214,9 @@ public function testColonDataWithStaticClassProperty() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -226,17 +226,17 @@ public function testColonDataWithStaticClassPropertyAndMultipleAttributes() $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) - -except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) + +except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) - -except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) + +except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -246,9 +246,9 @@ public function testSelfClosingComponentWithColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -259,9 +259,9 @@ public function testSelfClosingComponentWithColonDataAndStaticClassPropertyShort $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -272,18 +272,18 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) - -except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) + +except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) - -except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) + +except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -294,9 +294,9 @@ public function testEscapedColonAttribute() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([':title' => 'user.name']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -306,11 +306,11 @@ public function testColonAttributesIsEscapedIfStrings() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['src' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testClassDirective() @@ -318,11 +318,11 @@ public function testClassDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['class' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testStyleDirective() @@ -330,11 +330,11 @@ public function testStyleDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['style' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testColonNestedComponentParsing() @@ -342,9 +342,9 @@ public function testColonNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -354,9 +354,9 @@ public function testColonStartingNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -366,9 +366,9 @@ public function testSelfClosingComponentsCanBeCompiled() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); @@ -408,9 +408,9 @@ public function testComponentsCanBeCompiledWithHyphenAttributes() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar','wire:model' => 'foo','x-on:click' => 'bar','@click' => 'baz']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -421,9 +421,9 @@ public function testSelfClosingComponentsCanBeCompiledWithDataAndAttributes() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar','wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -435,11 +435,11 @@ public function testComponentCanReceiveAttributeBag() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['class' => 'bar','attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes),'wire:model' => 'foo']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['class' => 'bar','attributes' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes),'wire:model' => 'foo']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testSelfClosingComponentCanReceiveAttributeBag() @@ -448,11 +448,11 @@ public function testSelfClosingComponentCanReceiveAttributeBag() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
merge([\'class\' => \'test\']) }} wire:model="foo" />
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes(['class' => 'bar','attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes->merge(['class' => 'test'])),'wire:model' => 'foo']); ?>\n". +withAttributes(['class' => 'bar','attributes' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes->merge(['class' => 'test'])),'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); } @@ -461,9 +461,9 @@ public function testComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) + +except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##Words", trim($result)); } @@ -473,9 +473,9 @@ public function testSelfClosingComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##Words', trim($result)); @@ -486,9 +486,9 @@ public function testSelfClosingComponentsCanBeCompiledWithBoundData() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => \$title]) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => \$title]) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -500,9 +500,9 @@ public function testPairedComponentTags() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(' '); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) + +except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); @@ -519,11 +519,11 @@ public function testClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -538,11 +538,11 @@ public function testClasslessComponentsWithIndexView() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -557,11 +557,11 @@ public function testClasslessComponentsWithComponentView() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -576,11 +576,11 @@ public function testPackagesClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -610,11 +610,11 @@ public function testClasslessComponentsWithAnonymousComponentNamespace() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -644,11 +644,11 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -678,11 +678,11 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithCompon $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -711,9 +711,9 @@ public function testClasslessComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -744,9 +744,9 @@ public function testClasslessComponentsWithAnonymousComponentPathComponentName() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.panel','data' => []]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.panel','data' => []]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -777,9 +777,9 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) - -except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) + +except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -850,7 +850,7 @@ public function testAttributesTreatedAsPropsAreRemovedFromFinalAttributes() Component::resolveComponentsUsing(fn () => $component); - $__env = m::mock(\Illuminate\View\Factory::class); + $__env = m::mock(\Hypervel\View\Factory::class); $__env->shouldReceive('startComponent')->once(); $__env->shouldReceive('renderComponent')->once(); @@ -897,7 +897,7 @@ public function testOriginalAttributesAreRestoredAfterRenderingChildComponentWit TestProfileComponent::class => $profileComponent, }); - $__env = m::mock(\Illuminate\View\Factory::class); + $__env = m::mock(\Hypervel\View\Factory::class); $__env->shouldReceive('startComponent')->twice(); $__env->shouldReceive('renderComponent')->twice(); @@ -989,7 +989,7 @@ public function render() namespace App\View\Components\Card; -use Illuminate\View\Component; +use Hypervel\View\Component; class Card extends Component { diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php index 92d89e6ef..d7185cce3 100644 --- a/tests/View/Blade/BladeComponentsTest.php +++ b/tests/View/Blade/BladeComponentsTest.php @@ -1,9 +1,9 @@ assertSame(str_replace("\r\n", "\n", ' - "bar"] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?> + "bar"] + (isset($attributes) && $attributes instanceof Hypervel\View\ComponentAttributeBag ? $attributes->all() : [])); ?> withName(\'test\'); ?> shouldRender()): ?> -startComponent($component->resolveView(), $component->data()); ?>'), $this->compiler->compileString('@component(\'Illuminate\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])')); +startComponent($component->resolveView(), $component->data()); ?>'), $this->compiler->compileString('@component(\'Hypervel\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])')); } public function testEndComponentsAreCompiled() @@ -68,7 +68,7 @@ public function testPropsAreExtractedFromParentAttributesCorrectlyForClassCompon Component::resolveComponentsUsing(fn () => $component); - $template = $this->compiler->compileString('@component(\'Illuminate\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])'); + $template = $this->compiler->compileString('@component(\'Hypervel\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])'); ob_start(); eval(" ?> $template + $expected = ' '; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -120,8 +120,8 @@ public function testCustomIfElseConditions() @elsecustom($product) @else @endcustom'; - $expected = ' - + $expected = ' + '; $this->assertEquals($expected, $this->compiler->compileString($string)); @@ -135,7 +135,7 @@ public function testCustomUnlessConditions() $string = '@unlesscustom($user) @endcustom'; - $expected = ' + $expected = ' '; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -149,8 +149,8 @@ public function testCustomConditionsAccepts0AsArgument() $string = '@custom(0) @elsecustom(0) @endcustom'; - $expected = ' - + $expected = ' + '; $this->assertEquals($expected, $this->compiler->compileString($string)); } diff --git a/tests/View/Blade/BladeEchoHandlerTest.php b/tests/View/Blade/BladeEchoHandlerTest.php index 658a8dc5e..cfdbafcb2 100644 --- a/tests/View/Blade/BladeEchoHandlerTest.php +++ b/tests/View/Blade/BladeEchoHandlerTest.php @@ -1,10 +1,10 @@ assertSame('', $this->compiler->compileString('@dd($var1)')); $this->assertSame('', $this->compiler->compileString('@dd($var1, $var2)')); $this->assertSame('', $this->compiler->compileString('@dump($var1, $var2)')); - $this->assertSame('', $this->compiler->compileString('@vite')); - $this->assertSame('', $this->compiler->compileString('@vite()')); - $this->assertSame('', $this->compiler->compileString('@vite(\'resources/js/app.js\')')); - $this->assertSame('', $this->compiler->compileString('@vite([\'resources/js/app.js\'])')); - $this->assertSame('reactRefresh(); ?>', $this->compiler->compileString('@viteReactRefresh')); + $this->assertSame('', $this->compiler->compileString('@vite')); + $this->assertSame('', $this->compiler->compileString('@vite()')); + $this->assertSame('', $this->compiler->compileString('@vite(\'resources/js/app.js\')')); + $this->assertSame('', $this->compiler->compileString('@vite([\'resources/js/app.js\'])')); + $this->assertSame('reactRefresh(); ?>', $this->compiler->compileString('@viteReactRefresh')); } } diff --git a/tests/View/Blade/BladeIfAuthStatementsTest.php b/tests/View/Blade/BladeIfAuthStatementsTest.php index d52258678..a55627a14 100644 --- a/tests/View/Blade/BladeIfAuthStatementsTest.php +++ b/tests/View/Blade/BladeIfAuthStatementsTest.php @@ -1,6 +1,6 @@
'; - $expected = '
'; + $expected = '
'; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -15,7 +15,7 @@ public function testStatementIsCompiledWithoutAnyOptions() public function testJsonFlagsCanBeSet() { $string = '
'; - $expected = '
'; + $expected = '
'; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -23,7 +23,7 @@ public function testJsonFlagsCanBeSet() public function testEncodingDepthCanBeSet() { $string = '
'; - $expected = '
'; + $expected = '
'; $this->assertEquals($expected, $this->compiler->compileString($string)); } diff --git a/tests/View/Blade/BladeJsonTest.php b/tests/View/Blade/BladeJsonTest.php index bdfab95ef..78b087055 100644 --- a/tests/View/Blade/BladeJsonTest.php +++ b/tests/View/Blade/BladeJsonTest.php @@ -1,6 +1,6 @@ "; - $expected = "\"(['(']>"; + $expected = "\"(['(']>"; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = ""; - $expected = "\"(['']>"; + $expected = "\"(['']>"; $this->assertEquals($expected, $this->compiler->compileString($string)); @@ -145,31 +145,31 @@ public function testUnclosedParenthesisForBladeTags() public function testNestedTagCalls() { $string = " @empty(\$v)])>"; - $expected = ''; + $expected = ''; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = " @empty(\$v)])>"; - $expected = ''; + $expected = ''; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = " @empty(\$v), 't' => @empty(\$v1)])>"; - $expected = ''; + $expected = ''; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = " @empty(\$v), 't' => @empty(\$v1)])>"; - $expected = ''; + $expected = ''; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = " @empty(\$v), 't' => @empty(\$v1), 'r' => @empty(\$v2)])>"; - $expected = ''; + $expected = ''; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = " @empty(\$v), 't))' => @empty(\$v1), 'r' => @empty(\$v2)])>"; - $expected = ''; + $expected = ''; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = " @empty(\$v), 't' => @empty(\$v1), 'r' => @empty(\$v2), 'l' => 'l'])> @empty(\$v)])>"; - $expected = ''; + $expected = ''; $this->assertEquals($expected, $this->compiler->compileString($string)); } diff --git a/tests/View/Blade/BladePrependTest.php b/tests/View/Blade/BladePrependTest.php index 11bc1fa10..0780ba89d 100644 --- a/tests/View/Blade/BladePrependTest.php +++ b/tests/View/Blade/BladePrependTest.php @@ -1,8 +1,8 @@ assertSame('assertSame(' true, \'two\' => \'string\'])); +$__propNames = \Hypervel\View\ComponentAttributeBag::extractPropNames(([\'one\' => true, \'two\' => \'string\'])); foreach ($attributes->all() as $__key => $__value) { if (in_array($__key, $__propNames)) { @@ -21,7 +21,7 @@ public function testPropsAreCompiled() } } -$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes); +$attributes = new \Hypervel\View\ComponentAttributeBag($__newAttributes); unset($__propNames); unset($__newAttributes); diff --git a/tests/View/Blade/BladePushTest.php b/tests/View/Blade/BladePushTest.php index 84f3d3f33..aac7c1e31 100644 --- a/tests/View/Blade/BladePushTest.php +++ b/tests/View/Blade/BladePushTest.php @@ -1,8 +1,8 @@ true, 'margin-top: 10px' => false])>"; - $expected = " true, 'margin-top: 10px' => false]) ?>\">"; + $expected = " true, 'margin-top: 10px' => false]) ?>\">"; $this->assertEquals($expected, $this->compiler->compileString($string)); } diff --git a/tests/View/Blade/BladeUnlessStatementsTest.php b/tests/View/Blade/BladeUnlessStatementsTest.php index 55c27eec1..3e3c48270 100644 --- a/tests/View/Blade/BladeUnlessStatementsTest.php +++ b/tests/View/Blade/BladeUnlessStatementsTest.php @@ -1,6 +1,6 @@ expectException(BadMethodCallException::class); - $this->expectExceptionMessage('Method Illuminate\View\View::badMethodCall does not exist.'); + $this->expectExceptionMessage('Method Hypervel\View\View::badMethodCall does not exist.'); $view = $this->getView(); $view->badMethodCall(); From 32d63b5cd99680114ee6f9c8f0ffddd21ebb45c0 Mon Sep 17 00:00:00 2001 From: Yu Shing Date: Wed, 15 Oct 2025 14:34:46 +0800 Subject: [PATCH 06/21] feat: use strict type --- src/view/src/AnonymousComponent.php | 2 ++ src/view/src/AppendableAttributeValue.php | 2 ++ src/view/src/Compilers/BladeCompiler.php | 2 ++ src/view/src/Compilers/Compiler.php | 2 ++ src/view/src/Compilers/CompilerInterface.php | 2 ++ src/view/src/Compilers/ComponentTagCompiler.php | 2 ++ src/view/src/Compilers/Concerns/CompilesAuthorizations.php | 2 ++ src/view/src/Compilers/Concerns/CompilesClasses.php | 2 ++ src/view/src/Compilers/Concerns/CompilesComments.php | 2 ++ src/view/src/Compilers/Concerns/CompilesComponents.php | 2 ++ src/view/src/Compilers/Concerns/CompilesConditionals.php | 2 ++ src/view/src/Compilers/Concerns/CompilesEchos.php | 2 ++ src/view/src/Compilers/Concerns/CompilesErrors.php | 2 ++ src/view/src/Compilers/Concerns/CompilesFragments.php | 2 ++ src/view/src/Compilers/Concerns/CompilesHelpers.php | 2 ++ src/view/src/Compilers/Concerns/CompilesIncludes.php | 2 ++ src/view/src/Compilers/Concerns/CompilesInjections.php | 2 ++ src/view/src/Compilers/Concerns/CompilesJs.php | 2 ++ src/view/src/Compilers/Concerns/CompilesJson.php | 2 ++ src/view/src/Compilers/Concerns/CompilesLayouts.php | 2 ++ src/view/src/Compilers/Concerns/CompilesLoops.php | 2 ++ src/view/src/Compilers/Concerns/CompilesRawPhp.php | 2 ++ src/view/src/Compilers/Concerns/CompilesSessions.php | 2 ++ src/view/src/Compilers/Concerns/CompilesStacks.php | 2 ++ src/view/src/Compilers/Concerns/CompilesStyles.php | 2 ++ src/view/src/Compilers/Concerns/CompilesTranslations.php | 2 ++ src/view/src/Compilers/Concerns/CompilesUseStatements.php | 2 ++ src/view/src/Component.php | 2 ++ src/view/src/ComponentAttributeBag.php | 4 +++- src/view/src/ComponentSlot.php | 4 +++- src/view/src/Concerns/ManagesComponents.php | 2 ++ src/view/src/Concerns/ManagesEvents.php | 2 ++ src/view/src/Concerns/ManagesFragments.php | 2 ++ src/view/src/Concerns/ManagesLayouts.php | 2 ++ src/view/src/Concerns/ManagesLoops.php | 2 ++ src/view/src/Concerns/ManagesStacks.php | 2 ++ src/view/src/Concerns/ManagesTranslations.php | 2 ++ src/view/src/DynamicComponent.php | 4 +++- src/view/src/Engines/CompilerEngine.php | 4 +++- src/view/src/Engines/Engine.php | 2 ++ src/view/src/Engines/EngineResolver.php | 2 ++ src/view/src/Engines/FileEngine.php | 4 +++- src/view/src/Engines/PhpEngine.php | 4 +++- src/view/src/Factory.php | 2 ++ src/view/src/FileViewFinder.php | 4 +++- src/view/src/InvokableComponentVariable.php | 4 +++- src/view/src/Middleware/ShareErrorsFromSession.php | 2 ++ src/view/src/View.php | 2 ++ src/view/src/ViewException.php | 2 ++ src/view/src/ViewFinderInterface.php | 2 ++ src/view/src/ViewName.php | 2 ++ src/view/src/ViewServiceProvider.php | 2 ++ 52 files changed, 112 insertions(+), 8 deletions(-) diff --git a/src/view/src/AnonymousComponent.php b/src/view/src/AnonymousComponent.php index cd287883f..3d70e92fb 100644 --- a/src/view/src/AnonymousComponent.php +++ b/src/view/src/AnonymousComponent.php @@ -1,5 +1,7 @@ setAttributes($attributes); } diff --git a/src/view/src/ComponentSlot.php b/src/view/src/ComponentSlot.php index 468f36feb..8e3abcd91 100644 --- a/src/view/src/ComponentSlot.php +++ b/src/view/src/ComponentSlot.php @@ -1,5 +1,7 @@ contents = $contents; diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php index f12e62534..5941fd556 100644 --- a/src/view/src/Concerns/ManagesComponents.php +++ b/src/view/src/Concerns/ManagesComponents.php @@ -1,5 +1,7 @@ component = $component; } diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index cc113f5fb..b05196094 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -1,5 +1,7 @@ files = $files; } diff --git a/src/view/src/Engines/PhpEngine.php b/src/view/src/Engines/PhpEngine.php index 39805ff0d..f5b0fce6a 100755 --- a/src/view/src/Engines/PhpEngine.php +++ b/src/view/src/Engines/PhpEngine.php @@ -1,5 +1,7 @@ files = $files; } diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index c66c3f4b6..eea81be81 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -1,5 +1,7 @@ files = $files; $this->paths = array_map([$this, 'resolvePath'], $paths); diff --git a/src/view/src/InvokableComponentVariable.php b/src/view/src/InvokableComponentVariable.php index 3bce7b1c6..27dc8f57a 100644 --- a/src/view/src/InvokableComponentVariable.php +++ b/src/view/src/InvokableComponentVariable.php @@ -1,5 +1,7 @@ callable = $callable; } diff --git a/src/view/src/Middleware/ShareErrorsFromSession.php b/src/view/src/Middleware/ShareErrorsFromSession.php index 8ad8d3ee4..3bfa5e4e6 100644 --- a/src/view/src/Middleware/ShareErrorsFromSession.php +++ b/src/view/src/Middleware/ShareErrorsFromSession.php @@ -1,5 +1,7 @@ Date: Wed, 15 Oct 2025 14:55:21 +0800 Subject: [PATCH 07/21] feat: add view package in composer --- composer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 0b355e450..dc57c16d0 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,8 @@ "Hypervel\\Translation\\": "src/translation/src/", "Hypervel\\Validation\\": "src/validation/src/", "Hypervel\\Permission\\": "src/permission/src/", - "Hypervel\\Sentry\\": "src/sentry/src/" + "Hypervel\\Sentry\\": "src/sentry/src/", + "Hypervel\\View\\": "src/view/src/" }, "files": [ "src/auth/src/Functions.php", @@ -179,7 +180,8 @@ "hypervel/translation": "self.version", "hypervel/validation": "self.version", "hypervel/permission": "self.version", - "hypervel/sentry": "self.version" + "hypervel/sentry": "self.version", + "hypervel/view": "self.version" }, "suggest": { "hyperf/redis": "Required to use redis driver. (^3.1).", From 327d7fb9c10d404e73ca36f0340d1a25ad27f8fd Mon Sep 17 00:00:00 2001 From: Yu Shing Date: Wed, 15 Oct 2025 14:57:22 +0800 Subject: [PATCH 08/21] feat: add contracts --- src/view/src/Compilers/BladeCompiler.php | 4 +- .../src/Compilers/ComponentTagCompiler.php | 2 +- .../src/Compilers/Concerns/CompilesLoops.php | 2 +- src/view/src/Component.php | 2 +- src/view/src/Concerns/ManagesComponents.php | 2 +- src/view/src/Concerns/ManagesEvents.php | 2 +- src/view/src/Concerns/ManagesLayouts.php | 2 +- src/view/src/Contracts/Engine.php | 13 +++ src/view/src/Contracts/Factory.php | 81 +++++++++++++++++++ src/view/src/Contracts/View.php | 33 ++++++++ .../Contracts/ViewCompilationException.php | 12 +++ src/view/src/Engines/FileEngine.php | 2 +- src/view/src/Engines/PhpEngine.php | 2 +- src/view/src/Factory.php | 4 +- .../src/Middleware/ShareErrorsFromSession.php | 2 +- src/view/src/View.php | 4 +- 16 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 src/view/src/Contracts/Engine.php create mode 100644 src/view/src/Contracts/Factory.php create mode 100644 src/view/src/Contracts/View.php create mode 100644 src/view/src/Contracts/ViewCompilationException.php diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php index fcefaca38..f326e2e59 100644 --- a/src/view/src/Compilers/BladeCompiler.php +++ b/src/view/src/Compilers/BladeCompiler.php @@ -6,8 +6,8 @@ use Hypervel\Container\Container; use Hypervel\Contracts\Support\Htmlable; -use Hypervel\Contracts\View\Factory as ViewFactory; -use Hypervel\Contracts\View\View; +use Hypervel\View\Contracts\Factory as ViewFactory; +use Hypervel\View\Contracts\View; use Hypervel\Support\Arr; use Hypervel\Support\Collection; use Hypervel\Support\Str; diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php index be2745476..28955ecc1 100644 --- a/src/view/src/Compilers/ComponentTagCompiler.php +++ b/src/view/src/Compilers/ComponentTagCompiler.php @@ -6,7 +6,7 @@ use Hypervel\Container\Container; use Hypervel\Contracts\Foundation\Application; -use Hypervel\Contracts\View\Factory; +use Hypervel\View\Contracts\Factory; use Hypervel\Filesystem\Filesystem; use Hypervel\Support\Collection; use Hypervel\Support\Str; diff --git a/src/view/src/Compilers/Concerns/CompilesLoops.php b/src/view/src/Compilers/Concerns/CompilesLoops.php index 60b9c700c..ba2a19f95 100644 --- a/src/view/src/Compilers/Concerns/CompilesLoops.php +++ b/src/view/src/Compilers/Concerns/CompilesLoops.php @@ -4,7 +4,7 @@ namespace Hypervel\View\Compilers\Concerns; -use Hypervel\Contracts\View\ViewCompilationException; +use Hypervel\View\Contracts\ViewCompilationException; trait CompilesLoops { diff --git a/src/view/src/Component.php b/src/view/src/Component.php index 9b6a11bb4..4ee9a3879 100644 --- a/src/view/src/Component.php +++ b/src/view/src/Component.php @@ -7,7 +7,7 @@ use Closure; use Hypervel\Container\Container; use Hypervel\Contracts\Support\Htmlable; -use Hypervel\Contracts\View\View as ViewContract; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Collection; use ReflectionClass; use ReflectionMethod; diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php index 5941fd556..ea6f76fb5 100644 --- a/src/view/src/Concerns/ManagesComponents.php +++ b/src/view/src/Concerns/ManagesComponents.php @@ -5,7 +5,7 @@ namespace Hypervel\View\Concerns; use Hypervel\Contracts\Support\Htmlable; -use Hypervel\Contracts\View\View; +use Hypervel\View\Contracts\View; use Hypervel\Support\Arr; use Hypervel\View\ComponentSlot; diff --git a/src/view/src/Concerns/ManagesEvents.php b/src/view/src/Concerns/ManagesEvents.php index 69e12e08b..8dec6589f 100644 --- a/src/view/src/Concerns/ManagesEvents.php +++ b/src/view/src/Concerns/ManagesEvents.php @@ -5,7 +5,7 @@ namespace Hypervel\View\Concerns; use Closure; -use Hypervel\Contracts\View\View as ViewContract; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Str; trait ManagesEvents diff --git a/src/view/src/Concerns/ManagesLayouts.php b/src/view/src/Concerns/ManagesLayouts.php index 87ed6abe0..694789251 100644 --- a/src/view/src/Concerns/ManagesLayouts.php +++ b/src/view/src/Concerns/ManagesLayouts.php @@ -4,7 +4,7 @@ namespace Hypervel\View\Concerns; -use Hypervel\Contracts\View\View; +use Hypervel\View\Contracts\View; use Hypervel\Support\Str; use InvalidArgumentException; diff --git a/src/view/src/Contracts/Engine.php b/src/view/src/Contracts/Engine.php new file mode 100644 index 000000000..3790136ff --- /dev/null +++ b/src/view/src/Contracts/Engine.php @@ -0,0 +1,13 @@ + Date: Fri, 31 Oct 2025 11:56:13 +0800 Subject: [PATCH 09/21] style: remove useless type hint --- src/view/src/Compilers/CompilerInterface.php | 9 --------- src/view/src/Engines/FileEngine.php | 9 --------- src/view/src/Engines/PhpEngine.php | 19 +------------------ 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/view/src/Compilers/CompilerInterface.php b/src/view/src/Compilers/CompilerInterface.php index 6d34ae866..1dde84357 100755 --- a/src/view/src/Compilers/CompilerInterface.php +++ b/src/view/src/Compilers/CompilerInterface.php @@ -8,25 +8,16 @@ 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); } diff --git a/src/view/src/Engines/FileEngine.php b/src/view/src/Engines/FileEngine.php index 0ff65c4c9..e53824739 100644 --- a/src/view/src/Engines/FileEngine.php +++ b/src/view/src/Engines/FileEngine.php @@ -11,16 +11,11 @@ class FileEngine implements Engine { /** * The filesystem instance. - * - * @var \Hypervel\Filesystem\Filesystem */ protected Filesystem $files; /** * Create a new file engine instance. - * - * @param \Hypervel\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { @@ -29,10 +24,6 @@ public function __construct(Filesystem $files) /** * Get the evaluated contents of the view. - * - * @param string $path - * @param array $data - * @return string */ public function get(string $path, array $data = []): string { diff --git a/src/view/src/Engines/PhpEngine.php b/src/view/src/Engines/PhpEngine.php index 0151f08de..bd97804f9 100755 --- a/src/view/src/Engines/PhpEngine.php +++ b/src/view/src/Engines/PhpEngine.php @@ -12,16 +12,11 @@ class PhpEngine implements Engine { /** * The filesystem instance. - * - * @var \Hypervel\Filesystem\Filesystem */ protected Filesystem $files; /** * Create a new file engine instance. - * - * @param \Hypervel\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { @@ -30,10 +25,6 @@ public function __construct(Filesystem $files) /** * Get the evaluated contents of the view. - * - * @param string $path - * @param array $data - * @return string */ public function get(string $path, array $data = []): string { @@ -42,10 +33,6 @@ public function get(string $path, array $data = []): string /** * Get the evaluated contents of the view at the given path. - * - * @param string $path - * @param array $data - * @return string */ protected function evaluatePath(string $path, array $data): string { @@ -68,11 +55,7 @@ protected function evaluatePath(string $path, array $data): string /** * Handle a view exception. * - * @param \Throwable $e - * @param int $obLevel - * @return void - * - * @throws \Throwable + * @throws Throwable */ protected function handleViewException(Throwable $e, int $obLevel): void { From 8286d7ad0c101887adefe569a472075425edddf9 Mon Sep 17 00:00:00 2001 From: bluehaha Date: Fri, 31 Oct 2025 11:56:33 +0800 Subject: [PATCH 10/21] test: beautify test --- tests/View/ViewCompilerEngineTest.php | 3 +-- tests/View/ViewPhpEngineTest.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/View/ViewCompilerEngineTest.php b/tests/View/ViewCompilerEngineTest.php index a23f7fb33..6406ee508 100755 --- a/tests/View/ViewCompilerEngineTest.php +++ b/tests/View/ViewCompilerEngineTest.php @@ -28,8 +28,7 @@ public function testViewsMayBeRecompiledAndRendered() $engine->getCompiler()->shouldReceive('compile')->once()->with(__DIR__.'/fixtures/foo.php'); $results = $engine->get(__DIR__.'/fixtures/foo.php'); - $this->assertSame('Hello World -', $results); + $this->assertSame('Hello World' . PHP_EOL, $results); } public function testViewsAreNotRecompiledIfTheyAreNotExpired() diff --git a/tests/View/ViewPhpEngineTest.php b/tests/View/ViewPhpEngineTest.php index d3ede4d5c..11e342273 100755 --- a/tests/View/ViewPhpEngineTest.php +++ b/tests/View/ViewPhpEngineTest.php @@ -11,7 +11,6 @@ class ViewPhpEngineTest extends TestCase public function testViewsMayBeProperlyRendered() { $engine = new PhpEngine(new Filesystem); - $this->assertSame('Hello World -', $engine->get(__DIR__.'/fixtures/basic.php')); + $this->assertSame('Hello World' . PHP_EOL, $engine->get(__DIR__.'/fixtures/basic.php')); } } From d4103ac28b4d2435e584e9455b696ed46ab77912 Mon Sep 17 00:00:00 2001 From: bluehaha Date: Wed, 5 Nov 2025 14:39:18 +0800 Subject: [PATCH 11/21] fix: fix namespace --- src/view/src/Compilers/BladeCompiler.php | 2 +- src/view/src/Compilers/Concerns/CompilesComponents.php | 2 +- src/view/src/ComponentAttributeBag.php | 2 +- src/view/src/ComponentSlot.php | 2 +- src/view/src/Concerns/ManagesComponents.php | 2 +- src/view/src/Factory.php | 2 +- src/view/src/InvokableComponentVariable.php | 2 +- src/view/src/View.php | 8 ++++---- tests/View/Blade/BladeForeachStatementsTest.php | 2 +- tests/View/Blade/BladeForelseStatementsTest.php | 2 +- tests/View/ComponentTest.php | 4 ++-- tests/View/ViewFactoryTest.php | 4 ++-- tests/View/ViewTest.php | 6 +++--- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php index f326e2e59..6c0b8ffc1 100644 --- a/src/view/src/Compilers/BladeCompiler.php +++ b/src/view/src/Compilers/BladeCompiler.php @@ -5,7 +5,7 @@ namespace Hypervel\View\Compilers; use Hypervel\Container\Container; -use Hypervel\Contracts\Support\Htmlable; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Contracts\Factory as ViewFactory; use Hypervel\View\Contracts\View; use Hypervel\Support\Arr; diff --git a/src/view/src/Compilers/Concerns/CompilesComponents.php b/src/view/src/Compilers/Concerns/CompilesComponents.php index 9059bf19f..cabcac9a5 100644 --- a/src/view/src/Compilers/Concerns/CompilesComponents.php +++ b/src/view/src/Compilers/Concerns/CompilesComponents.php @@ -4,7 +4,7 @@ namespace Hypervel\View\Compilers\Concerns; -use Hypervel\Contracts\Support\CanBeEscapedWhenCastToString; +use Hypervel\Support\Contracts\CanBeEscapedWhenCastToString; use Hypervel\Support\Str; use Hypervel\View\AnonymousComponent; use Hypervel\View\ComponentAttributeBag; diff --git a/src/view/src/ComponentAttributeBag.php b/src/view/src/ComponentAttributeBag.php index ac190b69c..a2b20090c 100644 --- a/src/view/src/ComponentAttributeBag.php +++ b/src/view/src/ComponentAttributeBag.php @@ -6,7 +6,7 @@ use ArrayAccess; use ArrayIterator; -use Hypervel\Contracts\Support\Htmlable; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\Support\Arr; use Hypervel\Support\Collection; use Hypervel\Support\HtmlString; diff --git a/src/view/src/ComponentSlot.php b/src/view/src/ComponentSlot.php index 8e3abcd91..43a09b7eb 100644 --- a/src/view/src/ComponentSlot.php +++ b/src/view/src/ComponentSlot.php @@ -4,7 +4,7 @@ namespace Hypervel\View; -use Hypervel\Contracts\Support\Htmlable; +use Hypervel\Support\Contracts\Htmlable; use InvalidArgumentException; use Stringable; diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php index ea6f76fb5..2bd4f7079 100644 --- a/src/view/src/Concerns/ManagesComponents.php +++ b/src/view/src/Concerns/ManagesComponents.php @@ -4,7 +4,7 @@ namespace Hypervel\View\Concerns; -use Hypervel\Contracts\Support\Htmlable; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Contracts\View; use Hypervel\Support\Arr; use Hypervel\View\ComponentSlot; diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index c08986754..e89bb2e8c 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -6,7 +6,7 @@ use Hypervel\Contracts\Container\Container; use Hypervel\Contracts\Events\Dispatcher; -use Hypervel\Contracts\Support\Arrayable; +use Hypervel\Support\Contracts\Arrayable; use Hypervel\View\Contracts\Engine; use Hypervel\View\Contracts\Factory as FactoryContract; use Hypervel\Support\Arr; diff --git a/src/view/src/InvokableComponentVariable.php b/src/view/src/InvokableComponentVariable.php index 27dc8f57a..a2efd8849 100644 --- a/src/view/src/InvokableComponentVariable.php +++ b/src/view/src/InvokableComponentVariable.php @@ -6,7 +6,7 @@ use ArrayIterator; use Closure; -use Hypervel\Contracts\Support\DeferringDisplayableValue; +use Hypervel\Support\Contracts\DeferringDisplayableValue; use Hypervel\Support\Enumerable; use IteratorAggregate; use Stringable; diff --git a/src/view/src/View.php b/src/view/src/View.php index ecb5bf2d4..e680c0142 100755 --- a/src/view/src/View.php +++ b/src/view/src/View.php @@ -6,10 +6,10 @@ use ArrayAccess; use BadMethodCallException; -use Hypervel\Contracts\Support\Arrayable; -use Hypervel\Contracts\Support\Htmlable; -use Hypervel\Contracts\Support\MessageProvider; -use Hypervel\Contracts\Support\Renderable; +use Hypervel\Support\Contracts\Arrayable; +use Hypervel\Support\Contracts\Htmlable; +use Hypervel\Support\Contracts\MessageProvider; +use Hypervel\Support\Contracts\Renderable; use Hypervel\View\Contracts\Engine; use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Collection; diff --git a/tests/View/Blade/BladeForeachStatementsTest.php b/tests/View/Blade/BladeForeachStatementsTest.php index a7540457a..a6b9e4eb9 100644 --- a/tests/View/Blade/BladeForeachStatementsTest.php +++ b/tests/View/Blade/BladeForeachStatementsTest.php @@ -2,7 +2,7 @@ namespace Hypervel\Tests\View\Blade; -use Hypervel\Contracts\View\ViewCompilationException; +use Hypervel\View\Contracts\ViewCompilationException; use PHPUnit\Framework\Attributes\DataProvider; class BladeForeachStatementsTest extends AbstractBladeTestCase diff --git a/tests/View/Blade/BladeForelseStatementsTest.php b/tests/View/Blade/BladeForelseStatementsTest.php index af78ed79c..9108ad3d5 100644 --- a/tests/View/Blade/BladeForelseStatementsTest.php +++ b/tests/View/Blade/BladeForelseStatementsTest.php @@ -2,7 +2,7 @@ namespace Hypervel\Tests\View\Blade; -use Hypervel\Contracts\View\ViewCompilationException; +use Hypervel\View\Contracts\ViewCompilationException; use PHPUnit\Framework\Attributes\DataProvider; class BladeForelseStatementsTest extends AbstractBladeTestCase diff --git a/tests/View/ComponentTest.php b/tests/View/ComponentTest.php index ac2af1268..0ba7e7ce9 100644 --- a/tests/View/ComponentTest.php +++ b/tests/View/ComponentTest.php @@ -6,8 +6,8 @@ use Hypervel\Config\Repository as Config; use Hypervel\Container\Container; use Hypervel\Contracts\Container\BindingResolutionException; -use Hypervel\Contracts\Support\Htmlable; -use Hypervel\Contracts\View\Factory as FactoryContract; +use Hypervel\Support\Contracts\Htmlable; +use Hypervel\View\Contracts\Factory as FactoryContract; use Hypervel\Support\Facades\Facade; use Hypervel\Support\HtmlString; use Hypervel\View\Component; diff --git a/tests/View/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php index f22cdbf0f..5b24d132a 100755 --- a/tests/View/ViewFactoryTest.php +++ b/tests/View/ViewFactoryTest.php @@ -6,8 +6,8 @@ use ErrorException; use Hypervel\Container\Container; use Hypervel\Contracts\Events\Dispatcher as DispatcherContract; -use Hypervel\Contracts\View\Engine; -use Hypervel\Contracts\View\View as ViewContract; +use Hypervel\View\Contracts\Engine; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Events\Dispatcher; use Hypervel\Filesystem\Filesystem; use Hypervel\Support\HtmlString; diff --git a/tests/View/ViewTest.php b/tests/View/ViewTest.php index 061fd934e..e7bd2147b 100755 --- a/tests/View/ViewTest.php +++ b/tests/View/ViewTest.php @@ -5,9 +5,9 @@ use ArrayAccess; use BadMethodCallException; use Closure; -use Hypervel\Contracts\Support\Arrayable; -use Hypervel\Contracts\Support\Renderable; -use Hypervel\Contracts\View\Engine; +use Hypervel\Support\Contracts\Arrayable; +use Hypervel\Support\Contracts\Renderable; +use Hypervel\View\Contracts\Engine; use Hypervel\Support\MessageBag; use Hypervel\Support\ViewErrorBag; use Hypervel\View\Factory; From 333f59ff91331b3ef60477cebab9c289bc1c8fc1 Mon Sep 17 00:00:00 2001 From: bluehaha Date: Wed, 5 Nov 2025 14:59:57 +0800 Subject: [PATCH 12/21] refactor: add type hint --- src/view/src/Compilers/BladeCompiler.php | 335 ++++-------------- src/view/src/Compilers/Compiler.php | 80 +---- src/view/src/Compilers/CompilerInterface.php | 6 +- .../src/Compilers/ComponentTagCompiler.php | 179 +++------- .../Compilers/Concerns/CompilesComments.php | 3 - .../Concerns/CompilesConditionals.php | 99 +----- .../Compilers/Concerns/CompilesLayouts.php | 26 -- src/view/src/Component.php | 99 +----- src/view/src/Contracts/Factory.php | 44 +-- src/view/src/Contracts/View.php | 8 - src/view/src/Engines/CompilerEngine.php | 25 +- src/view/src/Engines/Engine.php | 8 +- src/view/src/Engines/EngineResolver.php | 19 +- 13 files changed, 154 insertions(+), 777 deletions(-) diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php index 6c0b8ffc1..4d9067d93 100644 --- a/src/view/src/Compilers/BladeCompiler.php +++ b/src/view/src/Compilers/BladeCompiler.php @@ -43,52 +43,38 @@ class BladeCompiler extends Compiler implements CompilerInterface /** * All of the registered extensions. - * - * @var array */ - protected $extensions = []; + protected array $extensions = []; /** * All custom "directive" handlers. - * - * @var array */ - protected $customDirectives = []; + protected array $customDirectives = []; /** * All custom "condition" handlers. - * - * @var array */ - protected $conditions = []; + protected array $conditions = []; /** * The registered string preparation callbacks. - * - * @var array */ - protected $prepareStringsForCompilationUsing = []; + protected array $prepareStringsForCompilationUsing = []; /** * All of the registered precompilers. - * - * @var array */ - protected $precompilers = []; + protected array $precompilers = []; /** * The file currently being compiled. - * - * @var string */ - protected $path; + protected string $path; /** * All of the available compiler functions. - * - * @var string[] */ - protected $compilers = [ + protected array $compilers = [ // 'Comments', 'Extensions', 'Statements', @@ -97,88 +83,58 @@ class BladeCompiler extends Compiler implements CompilerInterface /** * Array of opening and closing tags for raw echos. - * - * @var string[] */ - protected $rawTags = ['{!!', '!!}']; + protected array $rawTags = ['{!!', '!!}']; /** * Array of opening and closing tags for regular echos. - * - * @var string[] */ - protected $contentTags = ['{{', '}}']; + protected array $contentTags = ['{{', '}}']; /** * Array of opening and closing tags for escaped echos. - * - * @var string[] */ - protected $escapedTags = ['{{{', '}}}']; + protected array $escapedTags = ['{{{', '}}}']; /** * The "regular" / legacy echo string format. - * - * @var string */ - protected $echoFormat = 'e(%s)'; + protected string $echoFormat = 'e(%s)'; /** * Array of footer lines to be added to the template. - * - * @var array */ - protected $footer = []; - - /** - * Array to temporarily store the raw blocks found in the template. - * - * @var array - */ - protected $rawBlocks = []; + protected array $footer = []; /** * The array of anonymous component paths to search for components in. - * - * @var array */ - protected $anonymousComponentPaths = []; + protected array $anonymousComponentPaths = []; /** * The array of anonymous component namespaces to autoload from. - * - * @var array */ - protected $anonymousComponentNamespaces = []; + protected array $anonymousComponentNamespaces = []; /** * The array of class component aliases and their class names. - * - * @var array */ - protected $classComponentAliases = []; + protected array $classComponentAliases = []; /** * The array of class component namespaces to autoload from. - * - * @var array */ - protected $classComponentNamespaces = []; + protected array $classComponentNamespaces = []; /** * Indicates if component tags should be compiled. - * - * @var bool */ - protected $compilesComponentTags = true; + protected bool $compilesComponentTags = true; /** * Compile the view at the given path. - * - * @param string|null $path - * @return void */ - public function compile($path = null) + public function compile(string $path): void { if ($path) { $this->setPath($path); @@ -201,11 +157,8 @@ public function compile($path = null) /** * Append the file path to the compiled string. - * - * @param string $contents - * @return string */ - protected function appendFilePath($contents) + protected function appendFilePath(string $contents, string $path): string { $tokens = $this->getOpenAndClosingPhpTokens($contents); @@ -218,11 +171,8 @@ protected function appendFilePath($contents) /** * Get the open and closing PHP tag tokens from the given string. - * - * @param string $contents - * @return \Hypervel\Support\Collection */ - protected function getOpenAndClosingPhpTokens($contents) + protected function getOpenAndClosingPhpTokens(string $contents): Collection { return (new Collection(token_get_all($contents))) ->pluck(0) @@ -254,11 +204,8 @@ public function setPath($path) /** * Compile the given Blade template contents. - * - * @param string $value - * @return string */ - public function compileString($value) + public function compileString(string $value): string { [$this->footer, $result] = [[], '']; @@ -309,13 +256,8 @@ public function compileString($value) /** * Evaluate and render a Blade string to HTML. - * - * @param string $string - * @param array $data - * @param bool $deleteCachedView - * @return string */ - public static function render($string, $data = [], $deleteCachedView = false) + public static function render(string $string, array $data = [], bool $deleteCachedView = false): string { $component = new class($string) extends Component { @@ -345,11 +287,8 @@ public function render() /** * Render a component instance to HTML. - * - * @param \Hypervel\View\Component $component - * @return string */ - public static function renderComponent(Component $component) + public static function renderComponent(Component $component): string { $data = $component->data(); @@ -369,11 +308,8 @@ public static function renderComponent(Component $component) /** * Store the blocks that do not receive compilation. - * - * @param string $value - * @return string */ - protected function storeUncompiledBlocks($value) + protected function storeUncompiledBlocks(string $value): string { if (str_contains($value, '@verbatim')) { $value = $this->storeVerbatimBlocks($value); @@ -388,11 +324,8 @@ protected function storeUncompiledBlocks($value) /** * Store the verbatim blocks and replace them with a temporary placeholder. - * - * @param string $value - * @return string */ - protected function storeVerbatimBlocks($value) + protected function storeVerbatimBlocks(string $value): string { return preg_replace_callback('/(?storeRawBlock($matches[2]); @@ -401,11 +334,8 @@ protected function storeVerbatimBlocks($value) /** * Store the PHP blocks and replace them with a temporary placeholder. - * - * @param string $value - * @return string */ - protected function storePhpBlocks($value) + protected function storePhpBlocks(string $value): string { return preg_replace_callback('/(?storeRawBlock(""); @@ -414,11 +344,8 @@ protected function storePhpBlocks($value) /** * Store a raw block and return a unique raw placeholder. - * - * @param string $value - * @return string */ - protected function storeRawBlock($value) + protected function storeRawBlock(string $value): string { return $this->getRawPlaceholder( array_push($this->rawBlocks, $value) - 1 @@ -426,12 +353,15 @@ protected function storeRawBlock($value) } /** - * Compile the component tags. + * Temporarily store the raw block found in the template. * - * @param string $value - * @return string + * @return int The number of raw blocks in the stack after pushing the new one. */ protected function compileComponentTags($value) + /** + * Compile the component tags. + */ + protected function compileComponentTags(string $value): string { if (! $this->compilesComponentTags) { return $value; @@ -444,11 +374,8 @@ protected function compileComponentTags($value) /** * Replace the raw placeholders with the original code stored in the raw blocks. - * - * @param string $result - * @return string */ - protected function restoreRawContent($result) + protected function restoreRawContent(string $result): string { $result = preg_replace_callback('/'.$this->getRawPlaceholder('(\d+)').'/', function ($matches) { return $this->rawBlocks[$matches[1]]; @@ -461,22 +388,16 @@ protected function restoreRawContent($result) /** * Get a placeholder to temporarily mark the position of raw blocks. - * - * @param int|string $replace - * @return string */ - protected function getRawPlaceholder($replace) + protected function getRawPlaceholder(int|string $replace): string { return str_replace('#', $replace, '@__raw_block_#__@'); } /** * Add the stored footers onto the given content. - * - * @param string $result - * @return string */ - protected function addFooters($result) + protected function addFooters(string $result): string { return ltrim($result, "\n") ."\n".implode("\n", array_reverse($this->footer)); @@ -484,11 +405,8 @@ protected function addFooters($result) /** * Parse the tokens from the template. - * - * @param array $token - * @return string */ - protected function parseToken($token) + protected function parseToken(array $token): string { [$id, $content] = $token; @@ -503,11 +421,8 @@ protected function parseToken($token) /** * Execute the user defined extensions. - * - * @param string $value - * @return string */ - protected function compileExtensions($value) + protected function compileExtensions(string $value): string { foreach ($this->extensions as $compiler) { $value = $compiler($value, $this); @@ -518,11 +433,8 @@ protected function compileExtensions($value) /** * Compile Blade statements that start with "@". - * - * @param string $template - * @return string */ - protected function compileStatements($template) + protected function compileStatements(string $template): string { preg_match_all('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( [\S\s]*? ) \))?/x', $template, $matches); @@ -572,14 +484,8 @@ protected function compileStatements($template) /** * Replace the first match for a statement compilation operation. - * - * @param string $search - * @param string $replace - * @param string $subject - * @param int $offset - * @return array */ - protected function replaceFirstStatement($search, $replace, $subject, $offset) + protected function replaceFirstStatement(string $search, string $replace, string $subject, int $offset): array { $search = (string) $search; @@ -601,11 +507,8 @@ protected function replaceFirstStatement($search, $replace, $subject, $offset) /** * Determine if the given expression has the same number of opening and closing parentheses. - * - * @param string $expression - * @return bool */ - protected function hasEvenNumberOfParentheses(string $expression) + protected function hasEvenNumberOfParentheses(string $expression): bool { $tokens = token_get_all('extensions[] = $compiler; } /** * Get the extensions used by the compiler. - * - * @return array */ - public function getExtensions() + public function getExtensions(): array { return $this->extensions; } /** * Register an "if" statement directive. - * - * @param string $name - * @param callable $callback - * @return void */ - public function if($name, callable $callback) + public function if(string $name, callable $callback): void { $this->conditions[$name] = $callback; @@ -738,25 +622,16 @@ public function if($name, callable $callback) /** * Check the result of a condition. - * - * @param string $name - * @param mixed ...$parameters - * @return bool */ - public function check($name, ...$parameters) + public function check(string $name, mixed ...$parameters): bool { return call_user_func($this->conditions[$name], ...$parameters); } /** * Register a class-based component alias directive. - * - * @param string $class - * @param string|null $alias - * @param string $prefix - * @return void */ - public function component($class, $alias = null, $prefix = '') + public function component(string $class, ?string $alias = null, string $prefix = ''): void { if (! is_null($alias) && str_contains($alias, '\\')) { [$class, $alias] = [$alias, $class]; @@ -779,12 +654,8 @@ public function component($class, $alias = null, $prefix = '') /** * Register an array of class-based components. - * - * @param array $components - * @param string $prefix - * @return void */ - public function components(array $components, $prefix = '') + public function components(array $components, string $prefix = ''): void { foreach ($components as $key => $value) { if (is_numeric($key)) { @@ -797,22 +668,16 @@ public function components(array $components, $prefix = '') /** * Get the registered class component aliases. - * - * @return array */ - public function getClassComponentAliases() + public function getClassComponentAliases(): array { return $this->classComponentAliases; } /** * Register a new anonymous component path. - * - * @param string $path - * @param string|null $prefix - * @return void */ - public function anonymousComponentPath(string $path, ?string $prefix = null) + public function anonymousComponentPath(string $path, ?string $prefix = null): void { $prefixHash = md5($prefix ?: $path); @@ -829,12 +694,8 @@ public function anonymousComponentPath(string $path, ?string $prefix = null) /** * Register an anonymous component namespace. - * - * @param string $directory - * @param string|null $prefix - * @return void */ - public function anonymousComponentNamespace(string $directory, ?string $prefix = null) + public function anonymousComponentNamespace(string $directory, ?string $prefix = null): void { $prefix ??= $directory; @@ -846,54 +707,40 @@ public function anonymousComponentNamespace(string $directory, ?string $prefix = /** * Register a class-based component namespace. - * - * @param string $namespace - * @param string $prefix - * @return void */ - public function componentNamespace($namespace, $prefix) + public function componentNamespace(string $namespace, string $prefix): void { $this->classComponentNamespaces[$prefix] = $namespace; } /** * Get the registered anonymous component paths. - * - * @return array */ - public function getAnonymousComponentPaths() + public function getAnonymousComponentPaths(): array { return $this->anonymousComponentPaths; } /** * Get the registered anonymous component namespaces. - * - * @return array */ - public function getAnonymousComponentNamespaces() + public function getAnonymousComponentNamespaces(): array { return $this->anonymousComponentNamespaces; } /** * Get the registered class component namespaces. - * - * @return array */ - public function getClassComponentNamespaces() + public function getClassComponentNamespaces(): array { return $this->classComponentNamespaces; } /** * Register a component alias directive. - * - * @param string $path - * @param string|null $alias - * @return void */ - public function aliasComponent($path, $alias = null) + public function aliasComponent(string $path, ?string $alias = null): void { $alias = $alias ?: Arr::last(explode('.', $path)); @@ -910,24 +757,16 @@ public function aliasComponent($path, $alias = null) /** * Register an include alias directive. - * - * @param string $path - * @param string|null $alias - * @return void */ - public function include($path, $alias = null) + public function include(string $path, ?string $alias = null): void { $this->aliasInclude($path, $alias); } /** * Register an include alias directive. - * - * @param string $path - * @param string|null $alias - * @return void */ - public function aliasInclude($path, $alias = null) + public function aliasInclude(string $path, ?string $alias = null): void { $alias = $alias ?: Arr::last(explode('.', $path)); @@ -941,13 +780,9 @@ public function aliasInclude($path, $alias = null) /** * Register a handler for custom directives, binding the handler to the compiler. * - * @param string $name - * @param callable $handler - * @return void - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - public function bindDirective($name, callable $handler) + public function bindDirective(string $name, callable $handler): void { $this->directive($name, $handler, bind: true); } @@ -955,14 +790,9 @@ public function bindDirective($name, callable $handler) /** * Register a handler for custom directives. * - * @param string $name - * @param callable $handler - * @param bool $bind - * @return void - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - public function directive($name, callable $handler, bool $bind = false) + public function directive(string $name, callable $handler, bool $bind = false): void { if (! preg_match('/^\w+(?:::\w+)?$/x', $name)) { throw new InvalidArgumentException("The directive name [{$name}] is not valid. Directive names must only contain alphanumeric characters and underscores."); @@ -973,21 +803,16 @@ public function directive($name, callable $handler, bool $bind = false) /** * Get the list of custom directives. - * - * @return array */ - public function getCustomDirectives() + public function getCustomDirectives(): array { return $this->customDirectives; } /** * Indicate that the following callable should be used to prepare strings for compilation. - * - * @param callable $callback - * @return $this */ - public function prepareStringsForCompilationUsing(callable $callback) + public function prepareStringsForCompilationUsing(callable $callback): static { $this->prepareStringsForCompilationUsing[] = $callback; @@ -996,23 +821,16 @@ public function prepareStringsForCompilationUsing(callable $callback) /** * Register a new precompiler. - * - * @param callable $precompiler - * @return void */ - public function precompiler(callable $precompiler) + public function precompiler(callable $precompiler): void { $this->precompilers[] = $precompiler; } /** * Execute the given callback using a custom echo format. - * - * @param string $format - * @param callable $callback - * @return string */ - public function usingEchoFormat($format, callable $callback) + public function usingEchoFormat(string $format, callable $callback): string { $originalEchoFormat = $this->echoFormat; @@ -1029,41 +847,32 @@ public function usingEchoFormat($format, callable $callback) /** * Set the echo format to be used by the compiler. - * - * @param string $format - * @return void */ - public function setEchoFormat($format) + public function setEchoFormat(string $format): void { $this->echoFormat = $format; } /** * Set the "echo" format to double encode entities. - * - * @return void */ - public function withDoubleEncoding() + public function withDoubleEncoding(): void { $this->setEchoFormat('e(%s, true)'); } /** * Set the "echo" format to not double encode entities. - * - * @return void */ - public function withoutDoubleEncoding() + public function withoutDoubleEncoding(): void { $this->setEchoFormat('e(%s, false)'); } /** * Indicate that component tags should not be compiled. - * - * @return void */ - public function withoutComponentTags() + public function withoutComponentTags(): void { $this->compilesComponentTags = false; } diff --git a/src/view/src/Compilers/Compiler.php b/src/view/src/Compilers/Compiler.php index 053085e03..00d9b1fe9 100755 --- a/src/view/src/Compilers/Compiler.php +++ b/src/view/src/Compilers/Compiler.php @@ -7,80 +7,23 @@ use ErrorException; use Hypervel\Filesystem\Filesystem; use Hypervel\Support\Str; -use InvalidArgumentException; abstract class Compiler { - /** - * The filesystem instance. - * - * @var \Hypervel\Filesystem\Filesystem - */ - protected Filesystem $files; - - /** - * The cache path for the compiled views. - * - * @var string - */ - protected string $cachePath; - - /** - * The base path that should be removed from paths before hashing. - * - * @var string - */ - protected string $basePath; - - /** - * Determines if compiled views should be cached. - * - * @var bool - */ - protected bool $shouldCache; - - /** - * The compiled view file extension. - * - * @var string - */ - protected string $compiledExtension = 'php'; - /** * Create a new compiler instance. - * - * @param \Hypervel\Filesystem\Filesystem $files - * @param string $cachePath - * @param string $basePath - * @param bool $shouldCache - * @param string $compiledExtension - * @return void - * - * @throws \InvalidArgumentException */ public function __construct( - Filesystem $files, - string $cachePath, - string $basePath = '', - bool $shouldCache = true, - string $compiledExtension = 'php', - ): void { - 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; + protected Filesystem $files, + protected string $cachePath, + protected string $basePath = '', + protected bool $shouldCache = true, + protected string $compiledExtension = 'php', + ) { } /** * Get the path to the compiled version of a view. - * - * @param string $path - * @return string */ public function getCompiledPath(string $path): string { @@ -90,10 +33,7 @@ public function getCompiledPath(string $path): string /** * Determine if the view at the given path is expired. * - * @param string $path - * @return bool - * - * @throws \ErrorException + * @throws ErrorException */ public function isExpired(string $path): bool { @@ -111,8 +51,7 @@ public function isExpired(string $path): bool } try { - return $this->files->lastModified($path) >= - $this->files->lastModified($compiled); + return $this->files->lastModified($path) >= $this->files->lastModified($compiled); } catch (ErrorException $exception) { if (! $this->files->exists($compiled)) { return true; @@ -124,9 +63,6 @@ public function isExpired(string $path): bool /** * Create the compiled file directory if necessary. - * - * @param string $path - * @return void */ protected function ensureCompiledDirectoryExists(string $path): void { diff --git a/src/view/src/Compilers/CompilerInterface.php b/src/view/src/Compilers/CompilerInterface.php index 1dde84357..3e9fc9b30 100755 --- a/src/view/src/Compilers/CompilerInterface.php +++ b/src/view/src/Compilers/CompilerInterface.php @@ -9,15 +9,15 @@ interface CompilerInterface /** * Get the path to the compiled version of a view. */ - public function getCompiledPath($path); + public function getCompiledPath(string $path): string; /** * Determine if the given view is expired. */ - public function isExpired($path); + public function isExpired(string $path): bool; /** * Compile the view at the given path. */ - public function compile($path); + public function compile(string $path): void; } diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php index 28955ecc1..f85105c4a 100644 --- a/src/view/src/Compilers/ComponentTagCompiler.php +++ b/src/view/src/Compilers/ComponentTagCompiler.php @@ -16,10 +16,6 @@ use InvalidArgumentException; use ReflectionClass; -/** - * @author Spatie bvba - * @author Taylor Otwell - */ class ComponentTagCompiler { /** @@ -30,33 +26,22 @@ class ComponentTagCompiler protected $blade; /** - * The component class aliases. - * - * @var array + * The Blade compiler instance. */ - protected $aliases = []; + protected BladeCompiler $blade; /** - * The component class namespaces. - * - * @var array + * The component class aliases. */ - protected $namespaces = []; + protected array $aliases = []; /** - * The "bind:" attributes that have been compiled for the current component. - * - * @var array + * The component class namespaces. */ - protected $boundAttributes = []; + protected array $namespaces = []; /** * Create a new component tag compiler. - * - * @param array $aliases - * @param array $namespaces - * @param \Hypervel\View\Compilers\BladeCompiler|null $blade - * @return void */ public function __construct(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null) { @@ -68,11 +53,8 @@ public function __construct(array $aliases = [], array $namespaces = [], ?BladeC /** * Compile the component and slot tags within the given string. - * - * @param string $value - * @return string */ - public function compile(string $value) + public function compile(string $value): string { $value = $this->compileSlots($value); @@ -82,12 +64,9 @@ public function compile(string $value) /** * Compile the tags within the given string. * - * @param string $value - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - public function compileTags(string $value) + public function compileTags(string $value): string { $value = $this->compileSelfClosingTags($value); $value = $this->compileOpeningTags($value); @@ -99,12 +78,9 @@ public function compileTags(string $value) /** * Compile the opening tags within the given string. * - * @param string $value - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - protected function compileOpeningTags(string $value) + protected function compileOpeningTags(string $value): string { $pattern = "/ < @@ -163,12 +139,9 @@ protected function compileOpeningTags(string $value) /** * Compile the self-closing tags within the given string. * - * @param string $value - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - protected function compileSelfClosingTags(string $value) + protected function compileSelfClosingTags(string $value): string { $pattern = "/ < @@ -216,7 +189,7 @@ protected function compileSelfClosingTags(string $value) /x"; return preg_replace_callback($pattern, function (array $matches) { - $this->boundAttributes = []; + $this->clearBoundAttributes(); $attributes = $this->getAttributesFromAttributeString($matches['attributes']); @@ -227,13 +200,9 @@ protected function compileSelfClosingTags(string $value) /** * Compile the Blade component string for the given component and attributes. * - * @param string $component - * @param array $attributes - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - protected function componentString(string $component, array $attributes) + protected function componentString(string $component, array $attributes): string { $class = $this->componentClass($component); @@ -271,14 +240,11 @@ protected function componentString(string $component, array $attributes) /** * Get the component class for a given component alias. * - * @param string $component - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - public function componentClass(string $component) + public function componentClass(string $component): string { - $viewFactory = Container::getInstance()->make(Factory::class); + $viewFactory = Container::getInstance()->get(Factory::class); if (isset($this->aliases[$component])) { if (class_exists($alias = $this->aliases[$component])) { @@ -322,12 +288,8 @@ public function componentClass(string $component) /** * Attempt to find an anonymous component using the registered anonymous component paths. - * - * @param \Hypervel\Contracts\View\Factory $viewFactory - * @param string $component - * @return string|null */ - protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, string $component) + protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, string $component): ?string { $delimiter = ViewFinderInterface::HINT_PATH_DELIMITER; @@ -354,16 +316,14 @@ protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, strin // } } + + return null; } /** * Attempt to find an anonymous component using the registered anonymous component namespaces. - * - * @param \Hypervel\Contracts\View\Factory $viewFactory - * @param string $component - * @return string|null */ - protected function guessAnonymousComponentUsingNamespaces(Factory $viewFactory, string $component) + protected function guessAnonymousComponentUsingNamespaces(Factory $viewFactory, string $component): ?string { return (new Collection($this->blade->getAnonymousComponentNamespaces())) ->filter(function ($directory, $prefix) use ($component) { @@ -395,32 +355,28 @@ protected function guessAnonymousComponentUsingNamespaces(Factory $viewFactory, /** * Find the class for the given component using the registered namespaces. - * - * @param string $component - * @return string|null */ - public function findClassByComponent(string $component) + public function findClassByComponent(string $component): ?string { $segments = explode('::', $component); $prefix = $segments[0]; if (! isset($this->namespaces[$prefix], $segments[1])) { - return; + return null; } if (class_exists($class = $this->namespaces[$prefix].'\\'.$this->formatClassName($segments[1]))) { return $class; } + + return null; } /** * Guess the class name for the given component. - * - * @param string $component - * @return string */ - public function guessClassName(string $component) + public function guessClassName(string $component): string { $namespace = Container::getInstance() ->make(Application::class) @@ -433,11 +389,8 @@ public function guessClassName(string $component) /** * Format the class name for the given component. - * - * @param string $component - * @return string */ - public function formatClassName(string $component) + public function formatClassName(string $component): string { $componentPieces = array_map(function ($componentPiece) { return ucfirst(Str::camel($componentPiece)); @@ -448,12 +401,8 @@ public function formatClassName(string $component) /** * Guess the view name for the given component. - * - * @param string $name - * @param string $prefix - * @return string */ - public function guessViewName($name, $prefix = 'components.') + public function guessViewName(string $name, string $prefix = 'components.'): string { if (! Str::endsWith($prefix, '.')) { $prefix .= '.'; @@ -470,12 +419,8 @@ public function guessViewName($name, $prefix = 'components.') /** * 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) + public function partitionDataAndAttributes(string $class, array $attributes): array { // 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 @@ -497,22 +442,16 @@ public function partitionDataAndAttributes($class, array $attributes) /** * Compile the closing tags within the given string. - * - * @param string $value - * @return string */ - protected function compileClosingTags(string $value) + protected function compileClosingTags(string $value): string { 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) + public function compileSlots(string $value): string { $pattern = "/ < @@ -570,7 +509,6 @@ public function compileSlots(string $value) $name = "'{$name}'"; } - $this->boundAttributes = []; $attributes = $this->getAttributesFromAttributeString($matches['attributes']); @@ -589,11 +527,8 @@ public function compileSlots(string $value) /** * Get an array of attributes from the given attribute string. - * - * @param string $attributeString - * @return array */ - protected function getAttributesFromAttributeString(string $attributeString) + protected function getAttributesFromAttributeString(string $attributeString): array { $attributeString = $this->parseShortAttributeSyntax($attributeString); $attributeString = $this->parseAttributeBag($attributeString); @@ -651,11 +586,8 @@ protected function getAttributesFromAttributeString(string $attributeString) /** * 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) + protected function parseShortAttributeSyntax(string $value): string { $pattern = "/\s\:\\\$(\w+)/x"; @@ -666,11 +598,8 @@ protected function parseShortAttributeSyntax(string $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) + protected function parseAttributeBag(string $attributeString): string { $pattern = "/ (?:^|\s+) # start of the string or whitespace between attributes @@ -682,11 +611,8 @@ protected function parseAttributeBag(string $attributeString) /** * Parse @class statements in a given attribute string into their fully-qualified syntax. - * - * @param string $attributeString - * @return string */ - protected function parseComponentTagClassStatements(string $attributeString) + protected function parseComponentTagClassStatements(string $attributeString): string { return preg_replace_callback( '/@(class)(\( ( (?>[^()]+) | (?2) )* \))/x', function ($match) { @@ -703,11 +629,8 @@ protected function parseComponentTagClassStatements(string $attributeString) /** * Parse @style statements in a given attribute string into their fully-qualified syntax. - * - * @param string $attributeString - * @return string */ - protected function parseComponentTagStyleStatements(string $attributeString) + protected function parseComponentTagStyleStatements(string $attributeString): string { return preg_replace_callback( '/@(style)(\( ( (?>[^()]+) | (?2) )* \))/x', function ($match) { @@ -724,11 +647,8 @@ protected function parseComponentTagStyleStatements(string $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) + protected function parseBindAttributes(string $attributeString): string { $pattern = "/ (?:^|\s+) # start of the string or whitespace between attributes @@ -744,11 +664,8 @@ protected function parseBindAttributes(string $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) + protected function compileAttributeEchos(string $attributeString): string { $value = $this->blade->compileEchos($attributeString); @@ -762,11 +679,8 @@ protected function compileAttributeEchos(string $attributeString) /** * Escape the single quotes in the given string that are outside of PHP blocks. - * - * @param string $value - * @return string */ - protected function escapeSingleQuotesOutsideOfPhpBlocks(string $value) + protected function escapeSingleQuotesOutsideOfPhpBlocks(string $value): string { return (new Collection(token_get_all($value)))->map(function ($token) { if (! is_array($token)) { @@ -781,12 +695,8 @@ protected function escapeSingleQuotesOutsideOfPhpBlocks(string $value) /** * Convert an array of attributes to a string. - * - * @param array $attributes - * @param bool $escapeBound - * @return string */ - protected function attributesToString(array $attributes, $escapeBound = true) + protected function attributesToString(array $attributes, bool $escapeBound = true): string { return (new Collection($attributes)) ->map(function (string $value, string $attribute) use ($escapeBound) { @@ -799,11 +709,8 @@ protected function attributesToString(array $attributes, $escapeBound = true) /** * Strip any quotes from the given string. - * - * @param string $value - * @return string */ - public function stripQuotes(string $value) + public function stripQuotes(string $value): string { return Str::startsWith($value, ['"', '\'']) ? substr($value, 1, -1) diff --git a/src/view/src/Compilers/Concerns/CompilesComments.php b/src/view/src/Compilers/Concerns/CompilesComments.php index 30c59473c..6a0bb140d 100644 --- a/src/view/src/Compilers/Concerns/CompilesComments.php +++ b/src/view/src/Compilers/Concerns/CompilesComments.php @@ -8,9 +8,6 @@ trait CompilesComments { /** * Compile Blade comments into an empty string. - * - * @param string $value - * @return string */ protected function compileComments(string $value): string { diff --git a/src/view/src/Compilers/Concerns/CompilesConditionals.php b/src/view/src/Compilers/Concerns/CompilesConditionals.php index d2d73b0a0..476525588 100644 --- a/src/view/src/Compilers/Concerns/CompilesConditionals.php +++ b/src/view/src/Compilers/Concerns/CompilesConditionals.php @@ -10,16 +10,11 @@ trait CompilesConditionals { /** * Identifier for the first case in the switch statement. - * - * @var bool */ protected bool $firstCaseInSwitch = true; /** * Compile the if-auth statements into valid PHP. - * - * @param string|null $guard - * @return string */ protected function compileAuth(?string $guard = null): string { @@ -30,9 +25,6 @@ protected function compileAuth(?string $guard = null): string /** * Compile the else-auth statements into valid PHP. - * - * @param string|null $guard - * @return string */ protected function compileElseAuth(?string $guard = null): string { @@ -43,8 +35,6 @@ protected function compileElseAuth(?string $guard = null): string /** * Compile the end-auth statements into valid PHP. - * - * @return string */ protected function compileEndAuth(): string { @@ -53,9 +43,6 @@ protected function compileEndAuth(): string /** * Compile the env statements into valid PHP. - * - * @param string $environments - * @return string */ protected function compileEnv(string $environments): string { @@ -64,8 +51,6 @@ protected function compileEnv(string $environments): string /** * Compile the end-env statements into valid PHP. - * - * @return string */ protected function compileEndEnv(): string { @@ -74,8 +59,6 @@ protected function compileEndEnv(): string /** * Compile the production statements into valid PHP. - * - * @return string */ protected function compileProduction(): string { @@ -84,8 +67,6 @@ protected function compileProduction(): string /** * Compile the end-production statements into valid PHP. - * - * @return string */ protected function compileEndProduction(): string { @@ -94,9 +75,6 @@ protected function compileEndProduction(): string /** * Compile the if-guest statements into valid PHP. - * - * @param string|null $guard - * @return string */ protected function compileGuest(?string $guard = null): string { @@ -107,9 +85,6 @@ protected function compileGuest(?string $guard = null): string /** * Compile the else-guest statements into valid PHP. - * - * @param string|null $guard - * @return string */ protected function compileElseGuest(?string $guard = null): string { @@ -120,8 +95,6 @@ protected function compileElseGuest(?string $guard = null): string /** * Compile the end-guest statements into valid PHP. - * - * @return string */ protected function compileEndGuest(): string { @@ -130,9 +103,6 @@ protected function compileEndGuest(): string /** * Compile the has-section statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileHasSection(string $expression): string { @@ -141,9 +111,6 @@ protected function compileHasSection(string $expression): string /** * Compile the section-missing statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileSectionMissing(string $expression): string { @@ -152,9 +119,6 @@ protected function compileSectionMissing(string $expression): string /** * Compile the if statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileIf(string $expression): string { @@ -163,9 +127,6 @@ protected function compileIf(string $expression): string /** * Compile the unless statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileUnless(string $expression): string { @@ -174,9 +135,6 @@ protected function compileUnless(string $expression): string /** * Compile the else-if statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileElseif(string $expression): string { @@ -185,8 +143,6 @@ protected function compileElseif(string $expression): string /** * Compile the else statements into valid PHP. - * - * @return string */ protected function compileElse(): string { @@ -195,8 +151,6 @@ protected function compileElse(): string /** * Compile the end-if statements into valid PHP. - * - * @return string */ protected function compileEndif(): string { @@ -205,8 +159,6 @@ protected function compileEndif(): string /** * Compile the end-unless statements into valid PHP. - * - * @return string */ protected function compileEndunless(): string { @@ -215,9 +167,6 @@ protected function compileEndunless(): string /** * Compile the if-isset statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileIsset(string $expression): string { @@ -226,8 +175,6 @@ protected function compileIsset(string $expression): string /** * Compile the end-isset statements into valid PHP. - * - * @return string */ protected function compileEndIsset(): string { @@ -236,9 +183,6 @@ protected function compileEndIsset(): string /** * Compile the switch statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileSwitch(string $expression): string { @@ -249,9 +193,6 @@ protected function compileSwitch(string $expression): string /** * Compile the case statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileCase(string $expression): string { @@ -266,8 +207,6 @@ protected function compileCase(string $expression): string /** * Compile the default statements in switch case into valid PHP. - * - * @return string */ protected function compileDefault(): string { @@ -276,8 +215,6 @@ protected function compileDefault(): string /** * Compile the end switch statements into valid PHP. - * - * @return string */ protected function compileEndSwitch(): string { @@ -286,9 +223,6 @@ protected function compileEndSwitch(): string /** * Compile a once block into valid PHP. - * - * @param string|null $id - * @return string */ protected function compileOnce(?string $id = null): string { @@ -299,8 +233,6 @@ protected function compileOnce(?string $id = null): string /** * Compile an end-once block into valid PHP. - * - * @return string */ public function compileEndOnce(): string { @@ -309,20 +241,14 @@ public function compileEndOnce(): string /** * Compile a boolean value into a raw true / false value for embedding into HTML attributes or JavaScript. - * - * @param bool $condition - * @return string */ - protected function compileBool(bool $condition): string + protected function compileBool(string $condition): string { return ""; } /** * Compile a checked block into valid PHP. - * - * @param string $condition - * @return string */ protected function compileChecked(string $condition): string { @@ -331,9 +257,6 @@ protected function compileChecked(string $condition): string /** * Compile a disabled block into valid PHP. - * - * @param string $condition - * @return string */ protected function compileDisabled(string $condition): string { @@ -342,9 +265,6 @@ protected function compileDisabled(string $condition): string /** * Compile a required block into valid PHP. - * - * @param string $condition - * @return string */ protected function compileRequired(string $condition): string { @@ -353,9 +273,6 @@ protected function compileRequired(string $condition): string /** * Compile a readonly block into valid PHP. - * - * @param string $condition - * @return string */ protected function compileReadonly(string $condition): string { @@ -364,9 +281,6 @@ protected function compileReadonly(string $condition): string /** * Compile a selected block into valid PHP. - * - * @param string $condition - * @return string */ protected function compileSelected(string $condition): string { @@ -375,9 +289,6 @@ protected function compileSelected(string $condition): string /** * Compile the push statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compilePushIf(string $expression): string { @@ -388,9 +299,6 @@ protected function compilePushIf(string $expression): string /** * Compile the else-if push statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileElsePushIf(string $expression): string { @@ -401,9 +309,6 @@ protected function compileElsePushIf(string $expression): string /** * Compile the else push statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileElsePush(string $expression): string { @@ -412,8 +317,6 @@ protected function compileElsePush(string $expression): string /** * Compile the end-push statements into valid PHP. - * - * @return string */ protected function compileEndPushIf(): string { diff --git a/src/view/src/Compilers/Concerns/CompilesLayouts.php b/src/view/src/Compilers/Concerns/CompilesLayouts.php index 6306159d7..08c4a24cb 100644 --- a/src/view/src/Compilers/Concerns/CompilesLayouts.php +++ b/src/view/src/Compilers/Concerns/CompilesLayouts.php @@ -8,16 +8,11 @@ trait CompilesLayouts { /** * The name of the last section that was started. - * - * @var string|null */ protected ?string $lastSection = null; /** * Compile the extends statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileExtends(string $expression): string { @@ -32,9 +27,6 @@ protected function compileExtends(string $expression): string /** * Compile the extends-first statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileExtendsFirst(string $expression): string { @@ -49,9 +41,6 @@ protected function compileExtendsFirst(string $expression): string /** * Compile the section statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileSection(string $expression): string { @@ -62,8 +51,6 @@ protected function compileSection(string $expression): string /** * Replace the @parent directive to a placeholder. - * - * @return string */ protected function compileParent(): string { @@ -74,9 +61,6 @@ protected function compileParent(): string /** * Compile the yield statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileYield(string $expression): string { @@ -85,8 +69,6 @@ protected function compileYield(string $expression): string /** * Compile the show statements into valid PHP. - * - * @return string */ protected function compileShow(): string { @@ -95,8 +77,6 @@ protected function compileShow(): string /** * Compile the append statements into valid PHP. - * - * @return string */ protected function compileAppend(): string { @@ -105,8 +85,6 @@ protected function compileAppend(): string /** * Compile the overwrite statements into valid PHP. - * - * @return string */ protected function compileOverwrite(): string { @@ -115,8 +93,6 @@ protected function compileOverwrite(): string /** * Compile the stop statements into valid PHP. - * - * @return string */ protected function compileStop(): string { @@ -125,8 +101,6 @@ protected function compileStop(): string /** * Compile the end-section statements into valid PHP. - * - * @return string */ protected function compileEndsection(): string { diff --git a/src/view/src/Component.php b/src/view/src/Component.php index 4ee9a3879..33434f3df 100644 --- a/src/view/src/Component.php +++ b/src/view/src/Component.php @@ -6,9 +6,11 @@ use Closure; use Hypervel\Container\Container; -use Hypervel\Contracts\Support\Htmlable; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Collection; +use Hypervel\Support\Contracts\Arrayable; +use Hypervel\View\Contracts\Factory; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; @@ -17,36 +19,28 @@ abstract class Component { /** * The properties / methods that should not be exposed to the component. - * - * @var array */ protected array $except = []; /** * The component alias name. - * - * @var string */ public ?string $componentName = null; /** * The component attributes. - * - * @var \Hypervel\View\ComponentAttributeBag */ public ?ComponentAttributeBag $attributes = null; /** * The view factory instance, if any. - * - * @var \Hypervel\Contracts\View\Factory|null */ - protected static $factory = null; + protected static ?Factory $factory = null; /** * The component resolver callback. * - * @var (\Closure(string, array): Component)|null + * @var (Closure(string, array): Component)|null */ protected static ?Closure $componentsResolver = null; @@ -55,19 +49,15 @@ abstract class Component * * @var array */ - protected static $bladeViewCache = []; + protected static array $bladeViewCache = []; /** * The cache of public property names, keyed by class. - * - * @var array */ protected static array $propertyCache = []; /** * The cache of public method names, keyed by class. - * - * @var array */ protected static array $methodCache = []; @@ -76,27 +66,20 @@ abstract class Component * * @var array> */ - protected static $constructorParametersCache = []; + protected static array $constructorParametersCache = []; /** * The cache of ignored parameter names. - * - * @var array */ protected static array $ignoredParameterNames = []; /** * Get the view / view contents that represent the component. - * - * @return \Hypervel\Contracts\View\View|\Hypervel\Contracts\Support\Htmlable|\Closure|string */ - abstract public function render(): mixed; + abstract public function render(): ViewContract|Htmlable|Closure|string; /** * Resolve the component instance with the given data. - * - * @param array $data - * @return static */ public static function resolve(array $data): static { @@ -117,8 +100,6 @@ public static function resolve(array $data): static /** * Extract the constructor parameters for the component. - * - * @return array */ protected static function extractConstructorParameters(): array { @@ -137,10 +118,8 @@ protected static function extractConstructorParameters(): array /** * Resolve the Blade view or view file that should be used when rendering the component. - * - * @return \Hypervel\Contracts\View\View|\Hypervel\Contracts\Support\Htmlable|\Closure|string */ - public function resolveView(): mixed + public function resolveView(): ViewContract|Htmlable|Closure|string { $view = $this->render(); @@ -168,9 +147,6 @@ public function resolveView(): mixed /** * Create a Blade view with the raw component string content. - * - * @param string $contents - * @return string */ protected function extractBladeViewFromString(string $contents): string { @@ -189,12 +165,8 @@ protected function extractBladeViewFromString(string $contents): string /** * Create a Blade view with the raw component string content. - * - * @param \Hypervel\Contracts\View\Factory $factory - * @param string $contents - * @return string */ - protected function createBladeViewFromString($factory, string $contents): string + protected function createBladeViewFromString(Factory $factory, string $contents): string { $factory->addNamespace( '__components', @@ -215,9 +187,6 @@ protected function createBladeViewFromString($factory, string $contents): string /** * Get the data that should be supplied to the view. * - * @author Freek Van der Herten - * @author Brent Roose - * * @return array */ public function data(): array @@ -229,8 +198,6 @@ public function data(): array /** * Extract the public properties for the component. - * - * @return array */ protected function extractPublicProperties(): array { @@ -257,8 +224,6 @@ protected function extractPublicProperties(): array /** * Extract the public methods for the component. - * - * @return array */ protected function extractPublicMethods(): array { @@ -283,9 +248,6 @@ protected function extractPublicMethods(): array /** * Create a callable variable from the given method. - * - * @param \ReflectionMethod $method - * @return mixed */ protected function createVariableFromMethod(ReflectionMethod $method): mixed { @@ -296,9 +258,6 @@ protected function createVariableFromMethod(ReflectionMethod $method): mixed /** * Create an invokable, toStringable variable for the given component method. - * - * @param string $method - * @return \Hypervel\View\InvokableComponentVariable */ protected function createInvokableVariable(string $method): InvokableComponentVariable { @@ -309,9 +268,6 @@ protected function createInvokableVariable(string $method): InvokableComponentVa /** * Determine if the given property / method should be ignored. - * - * @param string $name - * @return bool */ protected function shouldIgnore(string $name): bool { @@ -321,8 +277,6 @@ protected function shouldIgnore(string $name): bool /** * Get the methods that should be ignored. - * - * @return array */ protected function ignoredMethods(): array { @@ -344,9 +298,6 @@ protected function ignoredMethods(): array /** * Set the component alias name. - * - * @param string $name - * @return $this */ public function withName(string $name): static { @@ -357,9 +308,6 @@ public function withName(string $name): static /** * Set the extra attributes that the component should make available. - * - * @param array $attributes - * @return $this */ public function withAttributes(array $attributes): static { @@ -372,9 +320,6 @@ public function withAttributes(array $attributes): static /** * Get a new attribute bag instance. - * - * @param array $attributes - * @return \Hypervel\View\ComponentAttributeBag */ protected function newAttributeBag(array $attributes = []): ComponentAttributeBag { @@ -383,8 +328,6 @@ protected function newAttributeBag(array $attributes = []): ComponentAttributeBa /** * Determine if the component should be rendered. - * - * @return bool */ public function shouldRender(): bool { @@ -393,23 +336,16 @@ public function shouldRender(): bool /** * Get the evaluated view contents for the given view. - * - * @param string|null $view - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData - * @return \Hypervel\Contracts\View\View */ - public function view(?string $view, mixed $data = [], array $mergeData = []): ViewContract + public function view(?string $view, Arrayable|array $data = [], array $mergeData = []): ViewContract { return $this->factory()->make($view, $data, $mergeData); } /** * Get the view factory instance. - * - * @return \Hypervel\Contracts\View\Factory */ - protected function factory(): mixed + protected function factory(): Factory { if (is_null(static::$factory)) { static::$factory = Container::getInstance()->make('view'); @@ -420,8 +356,6 @@ protected function factory(): mixed /** * Get the cached set of anonymous component constructor parameter names to exclude. - * - * @return array */ public static function ignoredParameterNames(): array { @@ -445,8 +379,6 @@ public static function ignoredParameterNames(): array /** * Flush the component's cached state. - * - * @return void */ public static function flushCache(): void { @@ -458,8 +390,6 @@ public static function flushCache(): void /** * Forget the component's factory instance. - * - * @return void */ public static function forgetFactory(): void { @@ -469,8 +399,6 @@ public static function forgetFactory(): void /** * Forget the component's resolver callback. * - * @return void - * * @internal */ public static function forgetComponentsResolver(): void @@ -481,8 +409,7 @@ public static function forgetComponentsResolver(): void /** * Set the callback that should be used to resolve components within views. * - * @param \Closure(string $component, array $data): Component $resolver - * @return void + * @param Closure(string $component, array $data): Component $resolver * * @internal */ diff --git a/src/view/src/Contracts/Factory.php b/src/view/src/Contracts/Factory.php index bac6af127..1ca4b8dc6 100644 --- a/src/view/src/Contracts/Factory.php +++ b/src/view/src/Contracts/Factory.php @@ -4,78 +4,48 @@ namespace Hypervel\View\Contracts; +use Closure; +use Hypervel\Support\Contracts\Arrayable; + interface Factory { /** * Determine if a given view exists. - * - * @param string $view - * @return bool */ public function exists(string $view): bool; /** * Get the evaluated view contents for the given path. - * - * @param string $path - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData - * @return \Hypervel\Contracts\View\View */ - public function file(string $path, $data = [], array $mergeData = []): View; + public function file(string $path, Arrayable|array $data = [], array $mergeData = []): View; /** * Get the evaluated view contents for the given view. - * - * @param string $view - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData - * @return \Hypervel\Contracts\View\View */ - public function make(string $view, $data = [], array $mergeData = []): View; + public function make(string $view, Arrayable|array $data = [], array $mergeData = []): View; /** * Add a piece of shared data to the environment. - * - * @param array|string $key - * @param mixed $value - * @return mixed */ public function share(array|string $key, mixed $value = null): mixed; /** * Register a view composer event. - * - * @param array|string $views - * @param \Closure|string $callback - * @return array */ - public function composer(array|string $views, \Closure|string $callback): array; + public function composer(array|string $views, Closure|string $callback): array; /** * Register a view creator event. - * - * @param array|string $views - * @param \Closure|string $callback - * @return array */ - public function creator(array|string $views, \Closure|string $callback): array; + public function creator(array|string $views, Closure|string $callback): array; /** * Add a new namespace to the loader. - * - * @param string $namespace - * @param string|array $hints - * @return $this */ public function addNamespace(string $namespace, string|array $hints): static; /** * Replace the namespace hints for the given namespace. - * - * @param string $namespace - * @param string|array $hints - * @return $this */ public function replaceNamespace(string $namespace, string|array $hints): static; } diff --git a/src/view/src/Contracts/View.php b/src/view/src/Contracts/View.php index 025607184..89df88596 100644 --- a/src/view/src/Contracts/View.php +++ b/src/view/src/Contracts/View.php @@ -10,24 +10,16 @@ interface View extends Renderable { /** * Get the name of the view. - * - * @return string */ public function name(): string; /** * Add a piece of data to the view. - * - * @param string|array $key - * @param mixed $value - * @return $this */ public function with(string|array $key, mixed $value = null): static; /** * Get the array of view data. - * - * @return array */ public function getData(): array; } diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index b05196094..99f86ddfd 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -18,15 +18,11 @@ class CompilerEngine extends PhpEngine { /** * The Blade compiler instance. - * - * @var \Hypervel\View\Compilers\CompilerInterface */ protected CompilerInterface $compiler; /** * A stack of the last compiled templates. - * - * @var array */ protected array $lastCompiled = []; @@ -39,10 +35,6 @@ class CompilerEngine extends PhpEngine /** * Create a new compiler engine instance. - * - * @param \Hypervel\View\Compilers\CompilerInterface $compiler - * @param \Hypervel\Filesystem\Filesystem|null $files - * @return void */ public function __construct(CompilerInterface $compiler, ?Filesystem $files = null) { @@ -54,9 +46,7 @@ public function __construct(CompilerInterface $compiler, ?Filesystem $files = nu /** * Get the evaluated contents of the view. * - * @param string $path - * @param array $data - * @return string + * @throws ViewException */ public function get(string $path, array $data = []): string { @@ -99,11 +89,7 @@ public function get(string $path, array $data = []): string /** * Handle a view exception. * - * @param \Throwable $e - * @param int $obLevel - * @return void - * - * @throws \Throwable + * @throws Throwable */ protected function handleViewException(Throwable $e, int $obLevel): void { @@ -121,9 +107,6 @@ protected function handleViewException(Throwable $e, int $obLevel): void /** * Get the exception message for an exception. - * - * @param \Throwable $e - * @return string */ protected function getMessage(Throwable $e): string { @@ -132,8 +115,6 @@ protected function getMessage(Throwable $e): string /** * Get the compiler implementation. - * - * @return \Hypervel\View\Compilers\CompilerInterface */ public function getCompiler(): CompilerInterface { @@ -142,8 +123,6 @@ public function getCompiler(): CompilerInterface /** * Clear the cache of views that were compiled or not expired. - * - * @return void */ public function forgetCompiledOrNotExpired(): void { diff --git a/src/view/src/Engines/Engine.php b/src/view/src/Engines/Engine.php index 8da08b755..bc44c9bdc 100755 --- a/src/view/src/Engines/Engine.php +++ b/src/view/src/Engines/Engine.php @@ -8,17 +8,13 @@ abstract class Engine { /** * The view that was last to be rendered. - * - * @var string */ - protected ?string $lastRendered = null; + protected string $lastRendered = null; /** * Get the last view that was rendered. - * - * @return string */ - public function getLastRendered(): ?string + public function getLastRendered(): string { return $this->lastRendered; } diff --git a/src/view/src/Engines/EngineResolver.php b/src/view/src/Engines/EngineResolver.php index 0b9c6d09f..07843a3d9 100755 --- a/src/view/src/Engines/EngineResolver.php +++ b/src/view/src/Engines/EngineResolver.php @@ -6,20 +6,17 @@ use Closure; use InvalidArgumentException; +use Hypervel\View\Contracts\Engine; class EngineResolver { /** * The array of engine resolvers. - * - * @var array */ protected array $resolvers = []; /** * The resolved engine instances. - * - * @var array */ protected array $resolved = []; @@ -27,10 +24,6 @@ class EngineResolver * 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(string $engine, Closure $resolver): void { @@ -42,12 +35,9 @@ public function register(string $engine, Closure $resolver): void /** * Resolve an engine instance by name. * - * @param string $engine - * @return \Hypervel\Contracts\View\Engine - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - public function resolve(string $engine): mixed + public function resolve(string $engine): Engine { if (isset($this->resolved[$engine])) { return $this->resolved[$engine]; @@ -62,9 +52,6 @@ public function resolve(string $engine): mixed /** * Remove a resolved engine. - * - * @param string $engine - * @return void */ public function forget(string $engine): void { From 66321b0ce86fdf945386df900a7467e4e5af2f2d Mon Sep 17 00:00:00 2001 From: bluehaha Date: Wed, 5 Nov 2025 15:00:48 +0800 Subject: [PATCH 13/21] feat: migrate compilers and engines --- src/view/src/Compilers/BladeCompiler.php | 76 +++++++++---------- .../src/Compilers/ComponentTagCompiler.php | 46 ++++++++--- .../Concerns/CompilesConditionals.php | 9 ++- .../src/Compilers/Concerns/CompilesEchos.php | 2 +- src/view/src/Engines/CompilerEngine.php | 28 ++++++- tests/View/Blade/AbstractBladeTestCase.php | 2 - .../Blade/BladeComponentTagCompilerTest.php | 48 +++++++----- tests/View/ViewBladeCompilerTest.php | 41 +--------- tests/View/ViewEngineResolverTest.php | 14 +++- 9 files changed, 146 insertions(+), 120 deletions(-) diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php index 4d9067d93..e1b68eeac 100644 --- a/src/view/src/Compilers/BladeCompiler.php +++ b/src/view/src/Compilers/BladeCompiler.php @@ -5,6 +5,7 @@ namespace Hypervel\View\Compilers; use Hypervel\Container\Container; +use Hypervel\Context\Context; use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Contracts\Factory as ViewFactory; use Hypervel\View\Contracts\View; @@ -18,6 +19,8 @@ class BladeCompiler extends Compiler implements CompilerInterface { + protected const RAW_BLOCKS_CONTEXT_KEY = 'hypervel.view.blade_compiler.raw_blocks'; + use Concerns\CompilesAuthorizations, Concerns\CompilesClasses, Concerns\CompilesComments, @@ -136,21 +139,20 @@ class BladeCompiler extends Compiler implements CompilerInterface */ public function compile(string $path): void { - if ($path) { - $this->setPath($path); - } + $contents = $this->compileString($this->files->get($path)); - if (! is_null($this->cachePath)) { - $contents = $this->compileString($this->files->get($this->getPath())); + if (! empty($path)) { + $contents = $this->appendFilePath($contents, $path); + } - if (! empty($this->getPath())) { - $contents = $this->appendFilePath($contents); - } + $this->ensureCompiledDirectoryExists( + $compiledPath = $this->getCompiledPath($path) + ); - $this->ensureCompiledDirectoryExists( - $compiledPath = $this->getCompiledPath($this->getPath()) - ); + $needSaveCompiledFile = ! $this->files->exists($compiledPath) + || $this->files->hash($compiledPath, 'xxh128') !== hash('xxh128', $contents); + if ($needSaveCompiledFile) { $this->files->put($compiledPath, $contents); } } @@ -166,7 +168,7 @@ protected function appendFilePath(string $contents, string $path): string $contents .= ' ?>'; } - return $contents."getPath()} ENDPATH**/ ?>"; + return $contents.""; } /** @@ -175,33 +177,12 @@ protected function appendFilePath(string $contents, string $path): string protected function getOpenAndClosingPhpTokens(string $contents): Collection { return (new Collection(token_get_all($contents))) - ->pluck(0) + ->pluck('0') ->filter(function ($token) { return in_array($token, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG]); }); } - /** - * Get the path currently being compiled. - * - * @return string - */ - public function getPath() - { - return $this->path; - } - - /** - * Set the path currently being compiled. - * - * @param string $path - * @return void - */ - public function setPath($path) - { - $this->path = $path; - } - /** * Compile the given Blade template contents. */ @@ -233,7 +214,7 @@ public function compileString(string $value): string $result .= is_array($token) ? $this->parseToken($token) : $token; } - if (! empty($this->rawBlocks)) { + if (! empty(Context::get(static::RAW_BLOCKS_CONTEXT_KEY))) { $result = $this->restoreRawContent($result); } @@ -251,7 +232,8 @@ public function compileString(string $value): string return str_replace( ['##BEGIN-COMPONENT-CLASS##', '##END-COMPONENT-CLASS##'], '', - $result); + $result + ); } /** @@ -348,7 +330,7 @@ protected function storePhpBlocks(string $value): string protected function storeRawBlock(string $value): string { return $this->getRawPlaceholder( - array_push($this->rawBlocks, $value) - 1 + $this->pushRawBlock($value) - 1 ); } @@ -357,7 +339,15 @@ protected function storeRawBlock(string $value): string * * @return int The number of raw blocks in the stack after pushing the new one. */ - protected function compileComponentTags($value) + protected function pushRawBlock(string $value): int + { + $stack = Context::get(static::RAW_BLOCKS_CONTEXT_KEY, []); + $stack[] = $value; + Context::set(static::RAW_BLOCKS_CONTEXT_KEY, $stack); + + return count($stack); + } + /** * Compile the component tags. */ @@ -377,11 +367,13 @@ protected function compileComponentTags(string $value): string */ protected function restoreRawContent(string $result): string { - $result = preg_replace_callback('/'.$this->getRawPlaceholder('(\d+)').'/', function ($matches) { - return $this->rawBlocks[$matches[1]]; + $rawBlocks = Context::get(static::RAW_BLOCKS_CONTEXT_KEY); + + $result = preg_replace_callback('/'.$this->getRawPlaceholder('(\d+)').'/', function ($matches) use ($rawBlocks) { + return $rawBlocks[$matches[1]]; }, $result); - $this->rawBlocks = []; + $rawBlocks = Context::set(static::RAW_BLOCKS_CONTEXT_KEY, []); return $result; } @@ -490,7 +482,7 @@ protected function replaceFirstStatement(string $search, string $replace, string $search = (string) $search; if ($search === '') { - return $subject; + return [$subject, 0]; } $position = strpos($subject, $search, $offset); diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php index f85105c4a..6e53d0027 100644 --- a/src/view/src/Compilers/ComponentTagCompiler.php +++ b/src/view/src/Compilers/ComponentTagCompiler.php @@ -5,7 +5,8 @@ namespace Hypervel\View\Compilers; use Hypervel\Container\Container; -use Hypervel\Contracts\Foundation\Application; +use Hypervel\Context\Context; +use Hypervel\Foundation\Contracts\Application; use Hypervel\View\Contracts\Factory; use Hypervel\Filesystem\Filesystem; use Hypervel\Support\Collection; @@ -19,11 +20,9 @@ class ComponentTagCompiler { /** - * The Blade compiler instance. - * - * @var \Hypervel\View\Compilers\BladeCompiler + * The "bind:" attributes that have been compiled for the current component. */ - protected $blade; + protected const BOUND_ATTRIBUTES_CONTEXT_KEY = 'hypervel.view.component_tag_compiler.bound_attributes'; /** * The Blade compiler instance. @@ -128,7 +127,7 @@ protected function compileOpeningTags(string $value): string /x"; return preg_replace_callback($pattern, function (array $matches) { - $this->boundAttributes = []; + $this->clearBoundAttributes(); $attributes = $this->getAttributesFromAttributeString($matches['attributes']); @@ -136,6 +135,32 @@ protected function compileOpeningTags(string $value): string }, $value); } + /** + * Clear the bound attributes for the current component. + */ + protected function clearBoundAttributes(): void + { + Context::set(self::BOUND_ATTRIBUTES_CONTEXT_KEY, []); + } + + /** + * Set a bound attribute for the current component. + */ + protected function setBoundAttribute($attribute): void + { + $boundAttributes = Context::get(self::BOUND_ATTRIBUTES_CONTEXT_KEY, []); + $boundAttributes[$attribute] = true; + Context::set(self::BOUND_ATTRIBUTES_CONTEXT_KEY, $boundAttributes); + } + + /** + * Get the bound attributes for the current component. + */ + protected function getBoundAttributes(): array + { + return Context::get(self::BOUND_ATTRIBUTES_CONTEXT_KEY, []); + } + /** * Compile the self-closing tags within the given string. * @@ -509,6 +534,7 @@ public function compileSlots(string $value): string $name = "'{$name}'"; } + $this->clearBoundAttributes(); $attributes = $this->getAttributesFromAttributeString($matches['attributes']); @@ -571,7 +597,7 @@ protected function getAttributesFromAttributeString(string $attributeString): ar if (str_starts_with($attribute, 'bind:')) { $attribute = Str::after($attribute, 'bind:'); - $this->boundAttributes[$attribute] = true; + $this->setBoundAttribute($attribute); } else { $value = "'".$this->compileAttributeEchos($value)."'"; } @@ -698,9 +724,11 @@ protected function escapeSingleQuotesOutsideOfPhpBlocks(string $value): string */ protected function attributesToString(array $attributes, bool $escapeBound = true): string { + $boundAttributes = $this->getBoundAttributes(); + return (new Collection($attributes)) - ->map(function (string $value, string $attribute) use ($escapeBound) { - return $escapeBound && isset($this->boundAttributes[$attribute]) && $value !== 'true' && ! is_numeric($value) + ->map(function (string $value, string $attribute) use ($escapeBound, $boundAttributes) { + return $escapeBound && isset($boundAttributes[$attribute]) && $value !== 'true' && ! is_numeric($value) ? "'{$attribute}' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute({$value})" : "'{$attribute}' => {$value}"; }) diff --git a/src/view/src/Compilers/Concerns/CompilesConditionals.php b/src/view/src/Compilers/Concerns/CompilesConditionals.php index 476525588..e59614c7b 100644 --- a/src/view/src/Compilers/Concerns/CompilesConditionals.php +++ b/src/view/src/Compilers/Concerns/CompilesConditionals.php @@ -4,6 +4,7 @@ namespace Hypervel\View\Compilers\Concerns; +use Hypervel\Context\Context; use Hypervel\Support\Str; trait CompilesConditionals @@ -11,7 +12,7 @@ trait CompilesConditionals /** * Identifier for the first case in the switch statement. */ - protected bool $firstCaseInSwitch = true; + protected const FIRST_CASE_IN_SWITCH_CONTEXT_KEY = 'hypervel.view.compiles_conditionals.first_case_in_switch'; /** * Compile the if-auth statements into valid PHP. @@ -186,7 +187,7 @@ protected function compileEndIsset(): string */ protected function compileSwitch(string $expression): string { - $this->firstCaseInSwitch = true; + Context::set(static::FIRST_CASE_IN_SWITCH_CONTEXT_KEY, true); return "firstCaseInSwitch) { - $this->firstCaseInSwitch = false; + if (Context::get(static::FIRST_CASE_IN_SWITCH_CONTEXT_KEY)) { + Context::set(static::FIRST_CASE_IN_SWITCH_CONTEXT_KEY, false); return "case {$expression}: ?>"; } diff --git a/src/view/src/Compilers/Concerns/CompilesEchos.php b/src/view/src/Compilers/Concerns/CompilesEchos.php index 2d71936ef..07af9eb98 100644 --- a/src/view/src/Compilers/Concerns/CompilesEchos.php +++ b/src/view/src/Compilers/Concerns/CompilesEchos.php @@ -149,7 +149,7 @@ protected function wrapInEchoHandler(string $value): string return $str->beforeLast(';'); }); - return empty($this->echoHandlers) ? $value : '$__bladeCompiler->applyEchoHandler('.$value.')'; + return empty($this->echoHandlers) ? (string) $value : '$__bladeCompiler->applyEchoHandler('.$value.')'; } /** diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index 99f86ddfd..6f96036a5 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -4,6 +4,7 @@ namespace Hypervel\View\Engines; +use Hypervel\Context\Context; use Hypervel\Database\RecordNotFoundException; use Hypervel\Database\RecordsNotFoundException; use Hypervel\Filesystem\Filesystem; @@ -16,6 +17,11 @@ class CompilerEngine extends PhpEngine { + /** + * The context key for the compiled template path. + */ + protected const COMPILED_PATH_CONTEXT_KEY = 'hypervel.view.compiler_engine.compiled_path'; + /** * The Blade compiler instance. */ @@ -50,7 +56,7 @@ public function __construct(CompilerInterface $compiler, ?Filesystem $files = nu */ public function get(string $path, array $data = []): string { - $this->lastCompiled[] = $path; + $this->pushCompiledPath($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 @@ -81,11 +87,25 @@ public function get(string $path, array $data = []): string $this->compiledOrNotExpired[$path] = true; - array_pop($this->lastCompiled); + $this->popCompiledPath(); return $results; } + protected function pushCompiledPath(string $path): void + { + $stack = Context::get(static::COMPILED_PATH_CONTEXT_KEY, []); + $stack[] = $path; + Context::set(static::COMPILED_PATH_CONTEXT_KEY, $stack); + } + + protected function popCompiledPath(): void + { + $stack = Context::get(static::COMPILED_PATH_CONTEXT_KEY, []); + array_pop($stack); + Context::set(static::COMPILED_PATH_CONTEXT_KEY, $stack); + } + /** * Handle a view exception. * @@ -110,7 +130,9 @@ protected function handleViewException(Throwable $e, int $obLevel): void */ protected function getMessage(Throwable $e): string { - return $e->getMessage().' (View: '.realpath(last($this->lastCompiled)).')'; + $stack = Context::get(static::COMPILED_PATH_CONTEXT_KEY); + + return $e->getMessage().' (View: '.realpath(last($stack)).')'; } /** diff --git a/tests/View/Blade/AbstractBladeTestCase.php b/tests/View/Blade/AbstractBladeTestCase.php index aef40b939..becc606f8 100644 --- a/tests/View/Blade/AbstractBladeTestCase.php +++ b/tests/View/Blade/AbstractBladeTestCase.php @@ -2,7 +2,6 @@ namespace Hypervel\Tests\View\Blade; -use Hypervel\Container\Container; use Hypervel\Filesystem\Filesystem; use Hypervel\View\Compilers\BladeCompiler; use Hypervel\View\Component; @@ -25,7 +24,6 @@ protected function setUp(): void protected function tearDown(): void { - Container::setInstance(null); Component::flushCache(); Component::forgetComponentsResolver(); Component::forgetFactory(); diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php index 8ce3955bb..9df06cc73 100644 --- a/tests/View/Blade/BladeComponentTagCompilerTest.php +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -2,14 +2,19 @@ namespace Hypervel\Tests\View\Blade; +use Closure; use Hypervel\Container\Container; -use Hypervel\Contracts\Foundation\Application; -use Hypervel\Contracts\View\Factory; +use Hypervel\Container\DefinitionSource; +use Hypervel\Context\ApplicationContext; +use Hypervel\View\Contracts\Factory; use Hypervel\Database\Eloquent\Model; +use Hypervel\Foundation\Application; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Compilers\BladeCompiler; use Hypervel\View\Compilers\ComponentTagCompiler; use Hypervel\View\Component; use Hypervel\View\ComponentAttributeBag; +use Hypervel\View\Contracts\View; use InvalidArgumentException; use Mockery as m; @@ -90,7 +95,7 @@ public function testInlineSlotsWithAttributesCanBeCompiled() public function testSlotsWithDynamicAttributesCanBeCompiled() { $this->mockViewFactory(); - $result = $this->compiler()->compileSlots(' + $result = $this->compiler()->compileSlots(' '); $this->assertSame( @@ -917,11 +922,17 @@ public function testOriginalAttributesAreRestoredAfterRenderingChildComponentWit protected function mockViewFactory($existsSucceeds = true) { - $container = new Container; - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $container->alias(Factory::class, 'view'); + $factory = m::mock(Factory::class); $factory->shouldReceive('exists')->andReturn($existsSucceeds); - Container::setInstance($container); + $container = new Application( + new DefinitionSource([ + Factory::class => fn () => $factory, + ]), + 'bath_path', + ); + $container->alias(Factory::class, 'view'); + + ApplicationContext::setContainer($container); } protected function compiler(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null) @@ -941,7 +952,7 @@ public function __construct($title = 'foo', $userId = 1) $this->title = $title; } - public function render() + public function render(): View|Htmlable|Closure|string { return 'alert'; } @@ -956,7 +967,7 @@ public function __construct($userId = 'foo') $this->userId = $userId; } - public function render() + public function render(): View|Htmlable|Closure|string { return 'profile'; } @@ -966,14 +977,14 @@ class TestInputComponent extends Component { public $userId; - public function __construct($name, $label, $value) - { - $this->name = $name; - $this->label = $label; - $this->value = $value; + public function __construct( + protected $name, + protected $label, + protected $value, + ) { } - public function render() + public function render(): View|Htmlable|Closure|string { return 'input'; } @@ -981,7 +992,7 @@ public function render() class TestContainerComponent extends Component { - public function render() + public function render(): View|Htmlable|Closure|string { return 'container'; } @@ -989,11 +1000,14 @@ public function render() namespace App\View\Components\Card; +use Closure; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Component; +use Hypervel\View\Contracts\View; class Card extends Component { - public function render() + public function render(): View|Htmlable|Closure|string { return 'card'; } diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php index 4b5a61925..0da3f1271 100644 --- a/tests/View/ViewBladeCompilerTest.php +++ b/tests/View/ViewBladeCompilerTest.php @@ -4,7 +4,6 @@ use Hypervel\Filesystem\Filesystem; use Hypervel\View\Compilers\BladeCompiler; -use InvalidArgumentException; use Mockery as m; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -23,14 +22,6 @@ public function testIsExpiredReturnsTrueIfCompiledFileDoesntExist() $this->assertTrue($compiler->isExpired('foo')); } - public function testCannotConstructWithBadCachePath() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Please provide a valid cache path.'); - - new BladeCompiler($this->getFiles(), null); - } - public function testIsExpiredReturnsTrueWhenModificationTimesWarrant() { $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); @@ -66,6 +57,7 @@ public function testCompileCompilesFileAndReturnsContents() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); $compiler->compile('foo'); } @@ -76,40 +68,11 @@ public function testCompileCompilesFileAndReturnsContentsCreatingDirectory() $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(false); $files->shouldReceive('makeDirectory')->once()->with(__DIR__, 0777, true, true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); $compiler->compile('foo'); } - public function testCompileCompilesAndGetThePath() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); - $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); - $compiler->compile('foo'); - $this->assertSame('foo', $compiler->getPath()); - } - - public function testCompileSetAndGetThePath() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $compiler->setPath('foo'); - $this->assertSame('foo', $compiler->getPath()); - } - - public function testCompileWithPathSetBefore() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); - $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); - // set path before compilation - $compiler->setPath('foo'); - // trigger compilation with $path - $compiler->compile(); - $this->assertSame('foo', $compiler->getPath()); - } - public function testRawTagsCanBeSetToLegacyValues() { $compiler = new BladeCompiler($this->getFiles(), __DIR__); diff --git a/tests/View/ViewEngineResolverTest.php b/tests/View/ViewEngineResolverTest.php index 5c809dbdb..2d66b29aa 100755 --- a/tests/View/ViewEngineResolverTest.php +++ b/tests/View/ViewEngineResolverTest.php @@ -2,10 +2,10 @@ namespace Hypervel\Tests\View; +use Hypervel\View\Contracts\Engine; use Hypervel\View\Engines\EngineResolver; use InvalidArgumentException; use PHPUnit\Framework\TestCase; -use stdClass; class ViewEngineResolverTest extends TestCase { @@ -13,11 +13,11 @@ public function testResolversMayBeResolved() { $resolver = new EngineResolver; $resolver->register('foo', function () { - return new stdClass; + return new FakeEngine(); }); $result = $resolver->resolve('foo'); - $this->assertEquals(spl_object_hash($result), spl_object_hash($resolver->resolve('foo'))); + $this->assertEquals($result, $resolver->resolve('foo')); } public function testResolverThrowsExceptionOnUnknownEngine() @@ -28,3 +28,11 @@ public function testResolverThrowsExceptionOnUnknownEngine() $resolver->resolve('foo'); } } + +class FakeEngine implements Engine +{ + public function get(string $path, array $data = []): string + { + return ''; + } +} From 4d4c6f21e49b6c22cecb9b8084751de1427b7b2d Mon Sep 17 00:00:00 2001 From: bluehaha Date: Wed, 5 Nov 2025 15:15:23 +0800 Subject: [PATCH 14/21] refactor: add type hints --- .../Compilers/Concerns/CompilesComponents.php | 2 +- .../src/Compilers/Concerns/CompilesLoops.php | 37 ++----------- src/view/src/Factory.php | 2 +- src/view/src/View.php | 54 +++++++++---------- src/view/src/ViewServiceProvider.php | 3 +- 5 files changed, 32 insertions(+), 66 deletions(-) diff --git a/src/view/src/Compilers/Concerns/CompilesComponents.php b/src/view/src/Compilers/Concerns/CompilesComponents.php index cabcac9a5..d815b661a 100644 --- a/src/view/src/Compilers/Concerns/CompilesComponents.php +++ b/src/view/src/Compilers/Concerns/CompilesComponents.php @@ -49,7 +49,7 @@ protected function compileComponent(string $expression): string * @param string $component * @return string */ - public static function newComponentHash(string $component) + public static function newComponentHash(string $component): string { static::$componentHashStack[] = $hash = hash('xxh128', $component); diff --git a/src/view/src/Compilers/Concerns/CompilesLoops.php b/src/view/src/Compilers/Concerns/CompilesLoops.php index ba2a19f95..1504926ed 100644 --- a/src/view/src/Compilers/Concerns/CompilesLoops.php +++ b/src/view/src/Compilers/Concerns/CompilesLoops.php @@ -18,10 +18,7 @@ trait CompilesLoops /** * Compile the for-else statements into valid PHP. * - * @param string|null $expression - * @return string - * - * @throws \Hypervel\Contracts\View\ViewCompilationException + * @throws ViewCompilationException */ protected function compileForelse(?string $expression): string { @@ -46,9 +43,6 @@ protected function compileForelse(?string $expression): string /** * Compile the for-else-empty and empty statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileEmpty(?string $expression): string { @@ -63,8 +57,6 @@ protected function compileEmpty(?string $expression): string /** * Compile the end-for-else statements into valid PHP. - * - * @return string */ protected function compileEndforelse(): string { @@ -73,8 +65,6 @@ protected function compileEndforelse(): string /** * Compile the end-empty statements into valid PHP. - * - * @return string */ protected function compileEndEmpty(): string { @@ -83,9 +73,6 @@ protected function compileEndEmpty(): string /** * Compile the for statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileFor(string $expression): string { @@ -95,10 +82,7 @@ protected function compileFor(string $expression): string /** * Compile the for-each statements into valid PHP. * - * @param string|null $expression - * @return string - * - * @throws \Hypervel\Contracts\View\ViewCompilationException + * @throws ViewCompilationException */ protected function compileForeach(?string $expression): string { @@ -121,11 +105,8 @@ protected function compileForeach(?string $expression): string /** * Compile the break statements into valid PHP. - * - * @param string $expression - * @return string */ - protected function compileBreak(?string $expression): string + protected function compileBreak(?string $expression = null): string { if ($expression) { preg_match('/\(\s*(-?\d+)\s*\)$/', $expression, $matches); @@ -138,9 +119,6 @@ protected function compileBreak(?string $expression): string /** * Compile the continue statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileContinue(?string $expression): string { @@ -155,8 +133,6 @@ protected function compileContinue(?string $expression): string /** * Compile the end-for statements into valid PHP. - * - * @return string */ protected function compileEndfor(): string { @@ -165,8 +141,6 @@ protected function compileEndfor(): string /** * Compile the end-for-each statements into valid PHP. - * - * @return string */ protected function compileEndforeach(): string { @@ -175,9 +149,6 @@ protected function compileEndforeach(): string /** * Compile the while statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileWhile(string $expression): string { @@ -186,8 +157,6 @@ protected function compileWhile(string $expression): string /** * Compile the end-while statements into valid PHP. - * - * @return string */ protected function compileEndwhile(): string { diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index e89bb2e8c..34cb3cbd3 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -400,7 +400,7 @@ public function doneRendering(): bool * @param string $id * @return bool */ - public function hasRenderedOnce(string $id) + public function hasRenderedOnce(string $id): bool { return isset($this->renderedOnce[$id]); } diff --git a/src/view/src/View.php b/src/view/src/View.php index e680c0142..b598f9e5e 100755 --- a/src/view/src/View.php +++ b/src/view/src/View.php @@ -35,8 +35,6 @@ class View implements ArrayAccess, Htmlable, Stringable, ViewContract /** * The engine implementation. - * - * @var \Hypervel\Contracts\View\Engine */ protected Engine $engine; @@ -206,7 +204,7 @@ protected function renderContents(): string * * @return string */ - protected function getContents() + protected function getContents(): string { return $this->engine->get($this->path, $this->gatherData()); } @@ -216,7 +214,7 @@ protected function getContents() * * @return array */ - public function gatherData() + public function gatherData(): array { $data = array_merge($this->factory->getShared(), $this->data); @@ -236,7 +234,7 @@ public function gatherData() * * @throws \Throwable */ - public function renderSections() + public function renderSections(): array { return $this->render(function () { return $this->factory->getSections(); @@ -250,7 +248,7 @@ public function renderSections() * @param mixed $value * @return $this */ - public function with($key, $value = null) + public function with(string|array $key, mixed $value = null): static { if (is_array($key)) { $this->data = array_merge($this->data, $key); @@ -269,7 +267,7 @@ public function with($key, $value = null) * @param array $data * @return $this */ - public function nest($key, $view, array $data = []) + public function nest(string $key, string $view, array $data = []): static { return $this->with($key, $this->factory->make($view, $data)); } @@ -281,7 +279,7 @@ public function nest($key, $view, array $data = []) * @param string $bag * @return $this */ - public function withErrors($provider, $bag = 'default') + public function withErrors(MessageProvider|array|string $provider, string $bag = 'default'): static { return $this->with('errors', (new ViewErrorBag)->put( $bag, $this->formatErrors($provider) @@ -294,7 +292,7 @@ public function withErrors($provider, $bag = 'default') * @param \Hypervel\Contracts\Support\MessageProvider|array|string $provider * @return \Hypervel\Support\MessageBag */ - protected function formatErrors($provider) + protected function formatErrors(MessageProvider|array|string $provider): MessageBag { return $provider instanceof MessageProvider ? $provider->getMessageBag() @@ -306,7 +304,7 @@ protected function formatErrors($provider) * * @return string */ - public function name() + public function name(): string { return $this->getName(); } @@ -316,7 +314,7 @@ public function name() * * @return string */ - public function getName() + public function getName(): string { return $this->view; } @@ -326,7 +324,7 @@ public function getName() * * @return array */ - public function getData() + public function getData(): array { return $this->data; } @@ -336,7 +334,7 @@ public function getData() * * @return string */ - public function getPath() + public function getPath(): string { return $this->path; } @@ -347,7 +345,7 @@ public function getPath() * @param string $path * @return void */ - public function setPath($path) + public function setPath(string $path): void { $this->path = $path; } @@ -357,17 +355,15 @@ public function setPath($path) * * @return \Hypervel\View\Factory */ - public function getFactory() + public function getFactory(): Factory { return $this->factory; } /** * Get the view's rendering engine. - * - * @return \Hypervel\Contracts\View\Engine */ - public function getEngine() + public function getEngine(): Engine { return $this->engine; } @@ -378,7 +374,7 @@ public function getEngine() * @param string $key * @return bool */ - public function offsetExists($key): bool + public function offsetExists(mixed $key): bool { return array_key_exists($key, $this->data); } @@ -389,7 +385,7 @@ public function offsetExists($key): bool * @param string $key * @return mixed */ - public function offsetGet($key): mixed + public function offsetGet(mixed $key): mixed { return $this->data[$key]; } @@ -401,7 +397,7 @@ public function offsetGet($key): mixed * @param mixed $value * @return void */ - public function offsetSet($key, $value): void + public function offsetSet(mixed $key, mixed $value): void { $this->with($key, $value); } @@ -412,7 +408,7 @@ public function offsetSet($key, $value): void * @param string $key * @return void */ - public function offsetUnset($key): void + public function offsetUnset(mixed $key): void { unset($this->data[$key]); } @@ -423,7 +419,7 @@ public function offsetUnset($key): void * @param string $key * @return mixed */ - public function &__get($key) + public function &__get(string $key): mixed { return $this->data[$key]; } @@ -435,7 +431,7 @@ public function &__get($key) * @param mixed $value * @return void */ - public function __set($key, $value) + public function __set(string $key, mixed $value): void { $this->with($key, $value); } @@ -446,7 +442,7 @@ public function __set($key, $value) * @param string $key * @return bool */ - public function __isset($key) + public function __isset(string $key): bool { return isset($this->data[$key]); } @@ -457,7 +453,7 @@ public function __isset($key) * @param string $key * @return void */ - public function __unset($key) + public function __unset(string $key): void { unset($this->data[$key]); } @@ -471,7 +467,7 @@ public function __unset($key) * * @throws \BadMethodCallException */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters): mixed { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); @@ -491,7 +487,7 @@ public function __call($method, $parameters) * * @return string */ - public function toHtml() + public function toHtml(): string { return $this->render(); } @@ -503,7 +499,7 @@ public function toHtml() * * @throws \Throwable */ - public function __toString() + public function __toString(): string { return $this->render(); } diff --git a/src/view/src/ViewServiceProvider.php b/src/view/src/ViewServiceProvider.php index 3e6c166d0..8e364370b 100755 --- a/src/view/src/ViewServiceProvider.php +++ b/src/view/src/ViewServiceProvider.php @@ -5,6 +5,7 @@ namespace Hypervel\View; use Hypervel\Container\Container; +use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Support\ServiceProvider; use Hypervel\View\Compilers\BladeCompiler; use Hypervel\View\Engines\CompilerEngine; @@ -71,7 +72,7 @@ public function registerFactory(): void * @param \Hypervel\Contracts\Events\Dispatcher $events * @return \Hypervel\View\Factory */ - protected function createFactory($resolver, $finder, $events): Factory + protected function createFactory(EngineResolver $resolver, ViewFinderInterface $finder, Dispatcher $events): Factory { return new Factory($resolver, $finder, $events); } From 29531a3e3b0ee154b85c3e54e2f8927a24442c04 Mon Sep 17 00:00:00 2001 From: bluehaha Date: Thu, 13 Nov 2025 14:48:18 +0800 Subject: [PATCH 15/21] feat: migrate Compilers --- src/support/src/helpers.php | 4 +- src/view/src/Compilers/BladeCompiler.php | 56 +++-- .../Concerns/CompilesAuthorizations.php | 24 -- .../Compilers/Concerns/CompilesClasses.php | 3 - .../Compilers/Concerns/CompilesComponents.php | 66 ++---- .../src/Compilers/Concerns/CompilesEchos.php | 27 +-- .../src/Compilers/Concerns/CompilesErrors.php | 8 +- .../Compilers/Concerns/CompilesFragments.php | 7 - .../Compilers/Concerns/CompilesHelpers.php | 16 -- .../Compilers/Concerns/CompilesIncludes.php | 18 -- .../Compilers/Concerns/CompilesInjections.php | 3 - .../src/Compilers/Concerns/CompilesJs.php | 3 - .../src/Compilers/Concerns/CompilesJson.php | 5 - .../Compilers/Concerns/CompilesLayouts.php | 4 +- .../src/Compilers/Concerns/CompilesLoops.php | 30 ++- .../src/Compilers/Concerns/CompilesRawPhp.php | 6 - .../Compilers/Concerns/CompilesSessions.php | 8 +- .../src/Compilers/Concerns/CompilesStacks.php | 23 -- .../src/Compilers/Concerns/CompilesStyles.php | 3 - .../Concerns/CompilesTranslations.php | 8 - .../Concerns/CompilesUseStatements.php | 3 - src/view/src/Concerns/ManagesFragments.php | 54 +++-- src/view/src/Engines/CompilerEngine.php | 22 +- .../Blade/BladeComponentTagCompilerTest.php | 205 +++++++----------- tests/View/Blade/BladeComponentsTest.php | 14 +- tests/View/Blade/BladeEchoHandlerTest.php | 32 ++- tests/View/Blade/BladePrependTest.php | 10 +- tests/View/Blade/BladePushTest.php | 10 +- 28 files changed, 258 insertions(+), 414 deletions(-) diff --git a/src/support/src/helpers.php b/src/support/src/helpers.php index 1545b5b3c..9362764f0 100644 --- a/src/support/src/helpers.php +++ b/src/support/src/helpers.php @@ -53,7 +53,7 @@ function environment(mixed ...$environments): bool|Environment /** * Encode HTML special characters in a string. */ - function e(BackedEnum|DeferringDisplayableValue|float|Htmlable|int|string|null $value, bool $doubleEncode = true): string + function e(BackedEnum|DeferringDisplayableValue|Stringable|float|Htmlable|int|string|null $value, bool $doubleEncode = true): string { if ($value instanceof DeferringDisplayableValue) { $value = $value->resolveDisplayableValue(); @@ -67,7 +67,7 @@ function e(BackedEnum|DeferringDisplayableValue|float|Htmlable|int|string|null $ $value = $value->value; } - return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', $doubleEncode); + return htmlspecialchars((string) ($value ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', $doubleEncode); } } diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php index e1b68eeac..0b02360fe 100644 --- a/src/view/src/Compilers/BladeCompiler.php +++ b/src/view/src/Compilers/BladeCompiler.php @@ -4,6 +4,7 @@ namespace Hypervel\View\Compilers; +use Closure; use Hypervel\Container\Container; use Hypervel\Context\Context; use Hypervel\Support\Contracts\Htmlable; @@ -19,8 +20,21 @@ class BladeCompiler extends Compiler implements CompilerInterface { + /* + * Temporarily store the raw blocks found in the template. + */ protected const RAW_BLOCKS_CONTEXT_KEY = 'hypervel.view.blade_compiler.raw_blocks'; + /* + * Footer lines to be added to the template. + */ + protected const FOOTER_CONTEXT_KEY = 'hypervel.view.blade_compiler.footer'; + + /** + * The "regular" / legacy echo string format. + */ + protected const ECHO_FORMAT_CONTEXT_KEY = 'hypervel.view.blade_compiler.echo_format'; + use Concerns\CompilesAuthorizations, Concerns\CompilesClasses, Concerns\CompilesComments, @@ -99,11 +113,6 @@ class BladeCompiler extends Compiler implements CompilerInterface */ protected array $escapedTags = ['{{{', '}}}']; - /** - * The "regular" / legacy echo string format. - */ - protected string $echoFormat = 'e(%s)'; - /** * Array of footer lines to be added to the template. */ @@ -188,7 +197,8 @@ protected function getOpenAndClosingPhpTokens(string $contents): Collection */ public function compileString(string $value): string { - [$this->footer, $result] = [[], '']; + Context::set(static::FOOTER_CONTEXT_KEY, []); + $result = ''; foreach ($this->prepareStringsForCompilationUsing as $callback) { $value = $callback($value); @@ -221,8 +231,9 @@ public function compileString(string $value): string // If there are any footer lines that need to get added to a template we will // add them here at the end of the template. This gets used mainly for the // template inheritance via the extends keyword that should be appended. - if (count($this->footer) > 0) { - $result = $this->addFooters($result); + $footer = Context::get(static::FOOTER_CONTEXT_KEY, []); + if (count($footer) > 0) { + $result = $this->addFooters($result, $footer); } if (! empty($this->echoHandlers)) { @@ -250,7 +261,7 @@ public function __construct($template) $this->template = $template; } - public function render() + public function render(): View|Htmlable|Closure|string { return $this->template; } @@ -383,16 +394,16 @@ protected function restoreRawContent(string $result): string */ protected function getRawPlaceholder(int|string $replace): string { - return str_replace('#', $replace, '@__raw_block_#__@'); + return str_replace('#', (string) $replace, '@__raw_block_#__@'); } /** * Add the stored footers onto the given content. */ - protected function addFooters(string $result): string + protected function addFooters(string $result, array $footer): string { return ltrim($result, "\n") - ."\n".implode("\n", array_reverse($this->footer)); + ."\n".implode("\n", array_reverse($footer)); } /** @@ -824,7 +835,7 @@ public function precompiler(callable $precompiler): void */ public function usingEchoFormat(string $format, callable $callback): string { - $originalEchoFormat = $this->echoFormat; + $originalEchoFormat = $this->getEchoFormat(); $this->setEchoFormat($format); @@ -840,9 +851,17 @@ public function usingEchoFormat(string $format, callable $callback): string /** * Set the echo format to be used by the compiler. */ - public function setEchoFormat(string $format): void + protected function setEchoFormat(string $format): void { - $this->echoFormat = $format; + Context::set(static::ECHO_FORMAT_CONTEXT_KEY, $format); + } + + /** + * Get the echo format to be used by the compiler. + */ + protected function getEchoFormat(): string + { + return Context::get(static::ECHO_FORMAT_CONTEXT_KEY, 'e(%s)'); } /** @@ -868,4 +887,11 @@ public function withoutComponentTags(): void { $this->compilesComponentTags = false; } + + protected function pushFooter($footer) + { + $stack = Context::get(static::FOOTER_CONTEXT_KEY, []); + $stack[] = $footer; + Context::set(static::FOOTER_CONTEXT_KEY, $stack); + } } diff --git a/src/view/src/Compilers/Concerns/CompilesAuthorizations.php b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php index 93f415f76..cf36db2e8 100644 --- a/src/view/src/Compilers/Concerns/CompilesAuthorizations.php +++ b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php @@ -8,9 +8,6 @@ trait CompilesAuthorizations { /** * Compile the can statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileCan(string $expression): string { @@ -19,9 +16,6 @@ protected function compileCan(string $expression): string /** * Compile the cannot statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileCannot(string $expression): string { @@ -30,9 +24,6 @@ protected function compileCannot(string $expression): string /** * Compile the canany statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileCanany(string $expression): string { @@ -41,9 +32,6 @@ protected function compileCanany(string $expression): string /** * Compile the else-can statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileElsecan(string $expression): string { @@ -52,9 +40,6 @@ protected function compileElsecan(string $expression): string /** * Compile the else-cannot statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileElsecannot(string $expression): string { @@ -63,9 +48,6 @@ protected function compileElsecannot(string $expression): string /** * Compile the else-canany statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileElsecanany(string $expression): string { @@ -74,8 +56,6 @@ protected function compileElsecanany(string $expression): string /** * Compile the end-can statements into valid PHP. - * - * @return string */ protected function compileEndcan(): string { @@ -84,8 +64,6 @@ protected function compileEndcan(): string /** * Compile the end-cannot statements into valid PHP. - * - * @return string */ protected function compileEndcannot(): string { @@ -94,8 +72,6 @@ protected function compileEndcannot(): string /** * Compile the end-canany statements into valid PHP. - * - * @return string */ protected function compileEndcanany(): string { diff --git a/src/view/src/Compilers/Concerns/CompilesClasses.php b/src/view/src/Compilers/Concerns/CompilesClasses.php index fa9b681a8..eb9653850 100644 --- a/src/view/src/Compilers/Concerns/CompilesClasses.php +++ b/src/view/src/Compilers/Concerns/CompilesClasses.php @@ -8,9 +8,6 @@ trait CompilesClasses { /** * Compile the conditional class statement into valid PHP. - * - * @param string|null $expression - * @return string */ protected function compileClass(?string $expression): string { diff --git a/src/view/src/Compilers/Concerns/CompilesComponents.php b/src/view/src/Compilers/Concerns/CompilesComponents.php index d815b661a..0d041e4c3 100644 --- a/src/view/src/Compilers/Concerns/CompilesComponents.php +++ b/src/view/src/Compilers/Concerns/CompilesComponents.php @@ -4,7 +4,9 @@ namespace Hypervel\View\Compilers\Concerns; -use Hypervel\Support\Contracts\CanBeEscapedWhenCastToString; +use Hyperf\Contract\CanBeEscapedWhenCastToString; +use Hyperf\DbConnection\Model\Model; +use Hypervel\Context\Context; use Hypervel\Support\Str; use Hypervel\View\AnonymousComponent; use Hypervel\View\ComponentAttributeBag; @@ -13,16 +15,11 @@ trait CompilesComponents { /** * The component name hash stack. - * - * @var array */ - protected static array $componentHashStack = []; + protected const COMPONENT_HASH_STACK_CONTEXT_KEY = 'hypervel.view.compilers_components.component_hash_stack'; /** * Compile the component statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileComponent(string $expression): string { @@ -45,25 +42,22 @@ protected function compileComponent(string $expression): string /** * Get a new component hash for a component name. - * - * @param string $component - * @return string */ public static function newComponentHash(string $component): string { - static::$componentHashStack[] = $hash = hash('xxh128', $component); + $hash = hash('xxh128', $component); + + Context::override(static::COMPONENT_HASH_STACK_CONTEXT_KEY, function ($stack) use ($hash) { + $stack ??= []; + $stack[] = $hash; + return $stack; + }); 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): string { @@ -79,8 +73,6 @@ public static function compileClassComponentOpening(string $component, string $a /** * Compile the end-component statements into valid PHP. - * - * @return string */ protected function compileEndComponent(): string { @@ -89,12 +81,10 @@ protected function compileEndComponent(): string /** * Compile the end-component statements into valid PHP. - * - * @return string */ public function compileEndComponentClass(): string { - $hash = array_pop(static::$componentHashStack); + $hash = $this->popComponentHashStack(); return $this->compileEndComponent()."\n".implode("\n", [ '', @@ -109,11 +99,19 @@ public function compileEndComponentClass(): string ]); } + protected function popComponentHashStack(): string + { + $stack = Context::get(static::COMPONENT_HASH_STACK_CONTEXT_KEY, []); + + $hash = array_pop($stack); + + Context::set(static::COMPONENT_HASH_STACK_CONTEXT_KEY, $stack); + + return $hash; + } + /** * Compile the slot statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileSlot(string $expression): string { @@ -122,8 +120,6 @@ protected function compileSlot(string $expression): string /** * Compile the end-slot statements into valid PHP. - * - * @return string */ protected function compileEndSlot(): string { @@ -132,9 +128,6 @@ protected function compileEndSlot(): string /** * Compile the component-first statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileComponentFirst(string $expression): string { @@ -143,8 +136,6 @@ protected function compileComponentFirst(string $expression): string /** * Compile the end-component-first statements into valid PHP. - * - * @return string */ protected function compileEndComponentFirst(): string { @@ -153,9 +144,6 @@ protected function compileEndComponentFirst(): string /** * Compile the prop statement into valid PHP. - * - * @param string $expression - * @return string */ protected function compileProps(string $expression): string { @@ -192,9 +180,6 @@ protected function compileProps(string $expression): string /** * Compile the aware statement into valid PHP. - * - * @param string $expression - * @return string */ protected function compileAware(string $expression): string { @@ -206,9 +191,6 @@ protected function compileAware(string $expression): string /** * Sanitize the given component attribute value. - * - * @param mixed $value - * @return mixed */ public static function sanitizeComponentAttribute(mixed $value): mixed { @@ -217,7 +199,7 @@ public static function sanitizeComponentAttribute(mixed $value): mixed } return is_string($value) || - (is_object($value) && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString')) + (is_object($value) && ! $value instanceof Model && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString')) ? e($value) : $value; } diff --git a/src/view/src/Compilers/Concerns/CompilesEchos.php b/src/view/src/Compilers/Concerns/CompilesEchos.php index 07af9eb98..a43e062e5 100644 --- a/src/view/src/Compilers/Concerns/CompilesEchos.php +++ b/src/view/src/Compilers/Concerns/CompilesEchos.php @@ -18,10 +18,6 @@ trait CompilesEchos /** * Add a handler to be executed before echoing a given class. - * - * @param string|callable $class - * @param callable|null $handler - * @return void */ public function stringable(string|callable $class, ?callable $handler = null): void { @@ -34,9 +30,6 @@ public function stringable(string|callable $class, ?callable $handler = null): v /** * Compile Blade echos into valid PHP. - * - * @param string $value - * @return string */ public function compileEchos(string $value): string { @@ -63,9 +56,6 @@ protected function getEchoMethods(): array /** * Compile the "raw" echo statements. - * - * @param string $value - * @return string */ protected function compileRawEchos(string $value): string { @@ -84,9 +74,6 @@ protected function compileRawEchos(string $value): string /** * Compile the "regular" echo statements. - * - * @param string $value - * @return string */ protected function compileRegularEchos(string $value): string { @@ -95,7 +82,7 @@ protected function compileRegularEchos(string $value): string $callback = function ($matches) { $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3]; - $wrapped = sprintf($this->echoFormat, $this->wrapInEchoHandler($matches[2])); + $wrapped = sprintf($this->getEchoFormat(), $this->wrapInEchoHandler($matches[2])); return $matches[1] ? substr($matches[0], 1) : "{$whitespace}"; }; @@ -105,9 +92,6 @@ protected function compileRegularEchos(string $value): string /** * Compile the escaped echo statements. - * - * @param string $value - * @return string */ protected function compileEscapedEchos(string $value): string { @@ -126,9 +110,6 @@ protected function compileEscapedEchos(string $value): string /** * Add an instance of the blade echo handler to the start of the compiled string. - * - * @param string $result - * @return string */ protected function addBladeCompilerVariable(string $result): string { @@ -137,9 +118,6 @@ protected function addBladeCompilerVariable(string $result): string /** * Wrap the echoable value in an echo handler if applicable. - * - * @param string $value - * @return string */ protected function wrapInEchoHandler(string $value): string { @@ -154,9 +132,6 @@ protected function wrapInEchoHandler(string $value): string /** * Apply the echo handler for the value if it exists. - * - * @param mixed $value - * @return mixed */ public function applyEchoHandler(mixed $value): mixed { diff --git a/src/view/src/Compilers/Concerns/CompilesErrors.php b/src/view/src/Compilers/Concerns/CompilesErrors.php index 75132d4d0..441c93817 100644 --- a/src/view/src/Compilers/Concerns/CompilesErrors.php +++ b/src/view/src/Compilers/Concerns/CompilesErrors.php @@ -8,9 +8,6 @@ trait CompilesErrors { /** * Compile the error statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileError(string $expression): string { @@ -25,11 +22,8 @@ protected function compileError(string $expression): string /** * Compile the enderror statements into valid PHP. - * - * @param string $expression - * @return string */ - protected function compileEnderror(string $expression): string + protected function compileEnderror(): string { return 'make({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>"; - $this->footer[] = $echo; + $this->pushFooter($echo); return ''; } @@ -34,7 +34,7 @@ protected function compileExtendsFirst(string $expression): string $echo = "first({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>"; - $this->footer[] = $echo; + $this->pushFooter($echo); return ''; } diff --git a/src/view/src/Compilers/Concerns/CompilesLoops.php b/src/view/src/Compilers/Concerns/CompilesLoops.php index 1504926ed..6067d30ee 100644 --- a/src/view/src/Compilers/Concerns/CompilesLoops.php +++ b/src/view/src/Compilers/Concerns/CompilesLoops.php @@ -4,16 +4,34 @@ namespace Hypervel\View\Compilers\Concerns; +use Hypervel\Context\Context; use Hypervel\View\Contracts\ViewCompilationException; trait CompilesLoops { /** * Counter to keep track of nested forelse statements. - * - * @var int */ - protected int $forElseCounter = 0; + protected const FOR_ELSE_COUNTER_CONTEXT_KEY = 'hypervel.view.compiles_loops.for_else_counter'; + + protected function incrementForElseCounter(): int + { + return Context::override(self::FOR_ELSE_COUNTER_CONTEXT_KEY, function ($value) { + return is_null($value) ? 1 : $value + 1; + }); + } + + protected function decrementForElseCounter(): int + { + return Context::override(self::FOR_ELSE_COUNTER_CONTEXT_KEY, function ($value) { + return is_null($value) ? 0 : max(0, $value - 1); + }); + } + + protected function getForElseCounter(): int + { + return Context::get(self::FOR_ELSE_COUNTER_CONTEXT_KEY, 0); + } /** * Compile the for-else statements into valid PHP. @@ -22,7 +40,8 @@ trait CompilesLoops */ protected function compileForelse(?string $expression): string { - $empty = '$__empty_'.++$this->forElseCounter; + $this->incrementForElseCounter(); + $empty = '$__empty_' . $this->getForElseCounter(); preg_match('/\( *(.+) +as +(.+)\)$/is', $expression ?? '', $matches); @@ -50,7 +69,8 @@ protected function compileEmpty(?string $expression): string return ""; } - $empty = '$__empty_'.$this->forElseCounter--; + $empty = '$__empty_' . $this->getForElseCounter(); + $this->decrementForElseCounter(); return "popLoop(); \$loop = \$__env->getLastLoop(); if ({$empty}): ?>"; } diff --git a/src/view/src/Compilers/Concerns/CompilesRawPhp.php b/src/view/src/Compilers/Concerns/CompilesRawPhp.php index 940616c84..f756a8b39 100644 --- a/src/view/src/Compilers/Concerns/CompilesRawPhp.php +++ b/src/view/src/Compilers/Concerns/CompilesRawPhp.php @@ -8,9 +8,6 @@ trait CompilesRawPhp { /** * Compile the raw PHP statements into valid PHP. - * - * @param string|null $expression - * @return string */ protected function compilePhp(?string $expression): string { @@ -23,9 +20,6 @@ protected function compilePhp(?string $expression): string /** * Compile the unset statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileUnset(string $expression): string { diff --git a/src/view/src/Compilers/Concerns/CompilesSessions.php b/src/view/src/Compilers/Concerns/CompilesSessions.php index 66231c945..4c38d175c 100644 --- a/src/view/src/Compilers/Concerns/CompilesSessions.php +++ b/src/view/src/Compilers/Concerns/CompilesSessions.php @@ -8,9 +8,6 @@ trait CompilesSessions { /** * Compile the session statements into valid PHP. - * - * @param string $expression - * @return string */ protected function compileSession(string $expression): string { @@ -24,11 +21,8 @@ protected function compileSession(string $expression): string /** * Compile the endsession statements into valid PHP. - * - * @param string $expression - * @return string */ - protected function compileEndsession(string $expression): string + protected function compileEndsession(): string { return 'fragmentStack[] = $fragment; + $this->pushFragmentStack($fragment); } } + protected function pushFragmentStack(string $fragment): void + { + Context::override(self::FRAGMENT_STACK_CONTEXT_KEY, function (?array $stack) use ($fragment) { + $stack = $stack ?? []; + $stack[] = $fragment; + return $stack; + }); + } + /** * Stop injecting content into a fragment. * - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function stopFragment(): string { - if (empty($this->fragmentStack)) { + $fragmentStack = Context::get(self::FRAGMENT_STACK_CONTEXT_KEY); + + if (empty($fragmentStack)) { throw new InvalidArgumentException('Cannot end a fragment without first starting one.'); } - $last = array_pop($this->fragmentStack); + $last = array_pop($fragmentStack); + Context::set(self::FRAGMENT_STACK_CONTEXT_KEY, $fragmentStack); - $this->fragments[$last] = ob_get_clean(); + $fragments = Context::get(self::FRAGMENTS_CONTEXT_KEY, []); + $fragments[$last] = ob_get_clean(); + Context::set(self::FRAGMENTS_CONTEXT_KEY, $fragments); - return $this->fragments[$last]; + return $fragments[$last]; } /** * Get the contents of a fragment. - * - * @param string $name - * @param string|null $default - * @return mixed */ public function getFragment(string $name, ?string $default = null): mixed { @@ -69,22 +71,18 @@ public function getFragment(string $name, ?string $default = null): mixed /** * Get the entire array of rendered fragments. - * - * @return array */ public function getFragments(): array { - return $this->fragments; + return Context::get(self::FRAGMENTS_CONTEXT_KEY, []); } /** * Flush all of the fragments. - * - * @return void */ public function flushFragments(): void { - $this->fragments = []; - $this->fragmentStack = []; + Context::set(self::FRAGMENTS_CONTEXT_KEY, []); + Context::set(self::FRAGMENT_STACK_CONTEXT_KEY, []); } } diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index 6f96036a5..3dbe309dc 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -4,21 +4,22 @@ namespace Hypervel\View\Engines; +use Hyperf\Database\Exception\MultipleRecordsFoundException; +use Hyperf\Database\Exception\RecordsNotFoundException; +use Hyperf\Database\Model\ModelNotFoundException; +use Hyperf\HttpMessage\Exception\HttpException; use Hypervel\Context\Context; -use Hypervel\Database\RecordNotFoundException; -use Hypervel\Database\RecordsNotFoundException; use Hypervel\Filesystem\Filesystem; -use Hypervel\Http\Exceptions\HttpResponseException; +use Hypervel\HttpMessage\Exceptions\HttpResponseException; use Hypervel\Support\Str; use Hypervel\View\Compilers\CompilerInterface; use Hypervel\View\ViewException; -use Symfony\Component\HttpKernel\Exception\HttpException; use Throwable; class CompilerEngine extends PhpEngine { /** - * The context key for the compiled template path. + * The context key for a stack of the compiled template path. */ protected const COMPILED_PATH_CONTEXT_KEY = 'hypervel.view.compiler_engine.compiled_path'; @@ -27,11 +28,6 @@ class CompilerEngine extends PhpEngine */ protected CompilerInterface $compiler; - /** - * A stack of the last compiled templates. - */ - protected array $lastCompiled = []; - /** * The view paths that were compiled or are not expired, keyed by the path. * @@ -115,8 +111,10 @@ protected function handleViewException(Throwable $e, int $obLevel): void { if ($e instanceof HttpException || $e instanceof HttpResponseException || - $e instanceof RecordNotFoundException || - $e instanceof RecordsNotFoundException) { + $e instanceof MultipleRecordsFoundException || + $e instanceof RecordsNotFoundException || + $e instanceof ModelNotFoundException + ) { parent::handleViewException($e, $obLevel); } diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php index 9df06cc73..f8ab6bdfc 100644 --- a/tests/View/Blade/BladeComponentTagCompilerTest.php +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -3,7 +3,6 @@ namespace Hypervel\Tests\View\Blade; use Closure; -use Hypervel\Container\Container; use Hypervel\Container\DefinitionSource; use Hypervel\Context\ApplicationContext; use Hypervel\View\Contracts\Factory; @@ -17,6 +16,8 @@ use Hypervel\View\Contracts\View; use InvalidArgumentException; use Mockery as m; +use Psr\EventDispatcher\EventDispatcherInterface; +use Stringable; class BladeComponentTagCompilerTest extends AbstractBladeTestCase { @@ -95,7 +96,7 @@ public function testInlineSlotsWithAttributesCanBeCompiled() public function testSlotsWithDynamicAttributesCanBeCompiled() { $this->mockViewFactory(); - $result = $this->compiler()->compileSlots(' + $result = $this->compiler()->compileSlots(' '); $this->assertSame( @@ -149,11 +150,7 @@ public function testBasicComponentParsing() public function testNestedDefaultComponentParsing() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - Container::setInstance($container); + $this->mockViewFactory(); $result = $this->compiler()->compileTags('
'); @@ -381,30 +378,20 @@ public function testSelfClosingComponentsCanBeCompiled() public function testClassNamesCanBeGuessed() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - Container::setInstance($container); + $this->mockViewFactory(); $result = $this->compiler()->guessClassName('alert'); $this->assertSame("App\View\Components\Alert", trim($result)); - - Container::setInstance(null); } public function testClassNamesCanBeGuessedWithNamespaces() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - Container::setInstance($container); + $this->mockViewFactory(); $result = $this->compiler()->guessClassName('base.alert'); $this->assertSame("App\View\Components\Base\Alert", trim($result)); - - Container::setInstance(null); } public function testComponentsCanBeCompiledWithHyphenAttributes() @@ -515,12 +502,7 @@ public function testPairedComponentTags() public function testClasslessComponents() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - $factory->shouldReceive('exists')->once()->andReturn(true); - Container::setInstance($container); + $this->mockViewFactory(); $result = $this->compiler()->compileTags(''); @@ -534,12 +516,7 @@ public function testClasslessComponents() public function testClasslessComponentsWithIndexView() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $app->shouldReceive('getNamespace')->andReturn('App\\'); - $factory->shouldReceive('exists')->andReturn(false, true); - Container::setInstance($container); + $this->mockViewFactory(false, true); $result = $this->compiler()->compileTags(''); @@ -553,12 +530,7 @@ public function testClasslessComponentsWithIndexView() public function testClasslessComponentsWithComponentView() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $app->shouldReceive('getNamespace')->andReturn('App\\'); - $factory->shouldReceive('exists')->andReturn(false, false, true); - Container::setInstance($container); + $this->mockViewFactory(false, false, true); $result = $this->compiler()->compileTags(''); @@ -572,12 +544,7 @@ public function testClasslessComponentsWithComponentView() public function testPackagesClasslessComponents() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $app->shouldReceive('getNamespace')->andReturn('App\\'); - $factory->shouldReceive('exists')->andReturn(true); - Container::setInstance($container); + $this->mockViewFactory(); $result = $this->compiler()->compileTags(''); @@ -591,20 +558,12 @@ public function testPackagesClasslessComponents() public function testClasslessComponentsWithAnonymousComponentNamespace() { - $container = new Container; - - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - $factory->shouldReceive('exists')->times(4)->andReturnUsing(function ($arg) { + $this->mockViewFactory(function ($arg) { // In our test, we'll do as if the 'public.frontend.anonymous-component' // view exists and not the others. return $arg === 'public.frontend.anonymous-component'; }); - Container::setInstance($container); - $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentNamespaces')->once()->andReturn([ @@ -625,20 +584,12 @@ public function testClasslessComponentsWithAnonymousComponentNamespace() public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexView() { - $container = new Container; - - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - $factory->shouldReceive('exists')->times(5)->andReturnUsing(function (string $viewNameBeingCheckedForExistence) { + $this->mockViewFactory(function (string $viewNameBeingCheckedForExistence) { // In our test, we'll do as if the 'public.frontend.anonymous-component' // view exists and not the others. return $viewNameBeingCheckedForExistence === 'admin.auth.components.anonymous-component.index'; }); - Container::setInstance($container); - $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentNamespaces')->once()->andReturn([ @@ -659,20 +610,12 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV public function testClasslessComponentsWithAnonymousComponentNamespaceWithComponentView() { - $container = new Container; - - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - $factory->shouldReceive('exists')->times(6)->andReturnUsing(function (string $viewNameBeingCheckedForExistence) { + $this->mockViewFactory(function (string $viewNameBeingCheckedForExistence) { // In our test, we'll do as if the 'public.frontend.anonymous-component' // view exists and not the others. return $viewNameBeingCheckedForExistence === 'admin.auth.components.anonymous-component.anonymous-component'; }); - Container::setInstance($container); - $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentNamespaces')->once()->andReturn([ @@ -693,19 +636,10 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithCompon public function testClasslessComponentsWithAnonymousComponentPath() { - $container = new Container; - - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - - $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { + $this->mockViewFactory(function ($arg) { return $arg === md5('test-directory').'::panel.index'; }); - Container::setInstance($container); - $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ @@ -726,19 +660,10 @@ public function testClasslessComponentsWithAnonymousComponentPath() public function testClasslessComponentsWithAnonymousComponentPathComponentName() { - $container = new Container; - - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - - $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { + $this->mockViewFactory(function ($arg) { return $arg === md5('test-directory').'::panel.panel'; }); - Container::setInstance($container); - $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ @@ -759,19 +684,10 @@ public function testClasslessComponentsWithAnonymousComponentPathComponentName() public function testClasslessIndexComponentsWithAnonymousComponentPath() { - $container = new Container; - - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - - $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { + $this->mockViewFactory(function ($arg) { return $arg === md5('test-directory').'::panel'; }); - Container::setInstance($container); - $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ @@ -793,7 +709,7 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() public function testAttributeSanitization() { $this->mockViewFactory(); - $class = new class + $class = new class implements Stringable { public function __toString() { @@ -801,7 +717,13 @@ public function __toString() } }; - $model = new class extends Model {}; + $model = new class extends Model + { + public function getEventDispatcher(): ?EventDispatcherInterface + { + return null; + } + }; $this->assertEquals(e(''), BladeCompiler::sanitizeComponentAttribute('')); $this->assertEquals(e('1'), BladeCompiler::sanitizeComponentAttribute('1')); @@ -821,12 +743,7 @@ public function testItThrowsAnExceptionForNonExistingAliases() public function testItThrowsAnExceptionForNonExistingClass() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); - $factory->shouldReceive('exists')->times(3)->andReturn(false); - Container::setInstance($container); + $this->mockViewFactory(false); $this->expectException(InvalidArgumentException::class); @@ -835,18 +752,26 @@ public function testItThrowsAnExceptionForNonExistingClass() public function testAttributesTreatedAsPropsAreRemovedFromFinalAttributes() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $container->alias(Factory::class, 'view'); - $app->shouldReceive('getNamespace')->never()->andReturn('App\\'); + $factory = m::mock(Factory::class); $factory->shouldReceive('exists')->never(); - Container::setInstance($container); + $container = $this->getMockBuilder(Application::class) + ->setConstructorArgs([ + new DefinitionSource([ + Factory::class => fn () => $factory, + ]), + 'bath_path', + ]) + ->onlyMethods(['getNamespace']) + ->getMock(); + $container->method('getNamespace')->willReturn('App\\'); + $container->alias(Factory::class, 'view'); + + ApplicationContext::setContainer($container); $attributes = new ComponentAttributeBag(['userId' => 'bar', 'other' => 'ok']); - $component = m::mock(Component::class); + $component = m::mock(TestProfileComponent::class); $component->shouldReceive('withName')->with('profile')->once(); $component->shouldReceive('shouldRender')->once()->andReturn(true); $component->shouldReceive('resolveView')->once()->andReturn(''); @@ -872,25 +797,33 @@ public function testAttributesTreatedAsPropsAreRemovedFromFinalAttributes() public function testOriginalAttributesAreRestoredAfterRenderingChildComponentWithProps() { - $container = new Container; - $container->instance(Application::class, $app = m::mock(Application::class)); - $container->instance(Factory::class, $factory = m::mock(Factory::class)); - $container->alias(Factory::class, 'view'); - $app->shouldReceive('getNamespace')->never()->andReturn('App\\'); + $factory = m::mock(Factory::class); $factory->shouldReceive('exists')->never(); - Container::setInstance($container); + $container = $this->getMockBuilder(Application::class) + ->setConstructorArgs([ + new DefinitionSource([ + Factory::class => fn () => $factory, + ]), + 'bath_path', + ]) + ->onlyMethods(['getNamespace']) + ->getMock(); + $container->method('getNamespace')->willReturn('App\\'); + $container->alias(Factory::class, 'view'); + + ApplicationContext::setContainer($container); $attributes = new ComponentAttributeBag(['userId' => 'bar', 'other' => 'ok']); - $containerComponent = m::mock(Component::class); + $containerComponent = m::mock(TestContainerComponent::class); $containerComponent->shouldReceive('withName')->with('container')->once(); $containerComponent->shouldReceive('shouldRender')->once()->andReturn(true); $containerComponent->shouldReceive('resolveView')->once()->andReturn(''); $containerComponent->shouldReceive('data')->once()->andReturn([]); $containerComponent->shouldReceive('withAttributes')->once(); - $profileComponent = m::mock(Component::class); + $profileComponent = m::mock(TestProfileComponent::class); $profileComponent->shouldReceive('withName')->with('profile')->once(); $profileComponent->shouldReceive('shouldRender')->once()->andReturn(true); $profileComponent->shouldReceive('resolveView')->once()->andReturn(''); @@ -920,16 +853,26 @@ public function testOriginalAttributesAreRestoredAfterRenderingChildComponentWit $this->assertSame($attributes->get('other'), 'ok'); } - protected function mockViewFactory($existsSucceeds = true) + protected function mockViewFactory(...$exists) { + $exists = $exists ?: [true]; $factory = m::mock(Factory::class); - $factory->shouldReceive('exists')->andReturn($existsSucceeds); - $container = new Application( - new DefinitionSource([ - Factory::class => fn () => $factory, - ]), - 'bath_path', - ); + if ($exists[0] instanceof Closure) { + $factory->shouldReceive('exists')->andReturnUsing($exists[0]); + } else { + $factory->shouldReceive('exists')->andReturn(...$exists); + } + + $container = $this->getMockBuilder(Application::class) + ->setConstructorArgs([ + new DefinitionSource([ + Factory::class => fn () => $factory, + ]), + 'bath_path', + ]) + ->onlyMethods(['getNamespace']) + ->getMock(); + $container->method('getNamespace')->willReturn('App\\'); $container->alias(Factory::class, 'view'); ApplicationContext::setContainer($container); diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php index d7185cce3..87c0d1a54 100644 --- a/tests/View/Blade/BladeComponentsTest.php +++ b/tests/View/Blade/BladeComponentsTest.php @@ -2,8 +2,10 @@ namespace Hypervel\Tests\View\Blade; +use Closure; +use Hypervel\Support\Contracts\Htmlable; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\View\Component; -use Hypervel\View\ComponentAttributeBag; use Mockery as m; class BladeComponentsTest extends AbstractBladeTestCase @@ -16,8 +18,8 @@ public function testComponentsAreCompiled() public function testClassComponentsAreCompiled() { - $this->assertSame(str_replace("\r\n", "\n", ' - + $this->assertSame(str_replace("\r\n", "\n", ' + "bar"] + (isset($attributes) && $attributes instanceof Hypervel\View\ComponentAttributeBag ? $attributes->all() : [])); ?> withName(\'test\'); ?> shouldRender()): ?> @@ -60,9 +62,7 @@ public function testEndSlotsAreCompiled() public function testPropsAreExtractedFromParentAttributesCorrectlyForClassComponents() { - $attributes = new ComponentAttributeBag(['foo' => 'baz', 'other' => 'ok']); - - $component = m::mock(Component::class); + $component = m::mock(ComponentStub::class); $component->shouldReceive('withName', 'test'); $component->shouldReceive('shouldRender')->andReturn(false); @@ -78,7 +78,7 @@ public function testPropsAreExtractedFromParentAttributesCorrectlyForClassCompon class ComponentStub extends Component { - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return ''; } diff --git a/tests/View/Blade/BladeEchoHandlerTest.php b/tests/View/Blade/BladeEchoHandlerTest.php index cfdbafcb2..f5c2249da 100644 --- a/tests/View/Blade/BladeEchoHandlerTest.php +++ b/tests/View/Blade/BladeEchoHandlerTest.php @@ -3,6 +3,9 @@ namespace Hypervel\Tests\View\Blade; use Exception; +use Hypervel\Container\DefinitionSource; +use Hypervel\Context\ApplicationContext; +use Hypervel\Foundation\Application; use Hypervel\Support\Fluent; use Hypervel\Support\Stringable; use PHPUnit\Framework\Attributes\DataProvider; @@ -60,13 +63,26 @@ public function testHandlerLogicWorksCorrectly($blade) throw new Exception('The fluent object has been successfully handled!'); }); - app()->instance('blade.compiler', $this->compiler); + + $app = $this->createApplication(); + $app->instance('blade.compiler', $this->compiler); $exampleObject = new Fluent(); eval((new Stringable($this->compiler->compileString($blade)))->remove([''])); } + protected function createApplication() + { + $container = new Application( + new DefinitionSource([]), + 'bath_path', + ); + ApplicationContext::setContainer($container); + + return $container; + } + public static function handlerLogicDataProvider() { return [ @@ -82,7 +98,8 @@ public function testHandlerWorksWithIterables($blade, $closure, $expectedOutput) { $this->compiler->stringable('iterable', $closure); - app()->instance('blade.compiler', $this->compiler); + $app = $this->createApplication(); + $app->instance('blade.compiler', $this->compiler); ob_start(); eval((new Stringable($this->compiler->compileString($blade)))->remove([''])); @@ -95,16 +112,19 @@ public function testHandlerWorksWithIterables($blade, $closure, $expectedOutput) public static function handlerWorksWithIterableDataProvider() { return [ - ['{{[1,"two",3]}}', function (iterable $arr) { - return implode(', ', $arr); - }, '1, two, 3'], + [ + '{{[1,"two",3]}}', + fn (iterable $arr) => implode(', ', $arr), + '1, two, 3' + ], ]; } #[DataProvider('nonStringableDataProvider')] public function testHandlerWorksWithNonStringables($blade, $expectedOutput) { - app()->instance('blade.compiler', $this->compiler); + $app = $this->createApplication(); + $app->instance('blade.compiler', $this->compiler); ob_start(); eval((new Stringable($this->compiler->compileString($blade)))->remove([''])); diff --git a/tests/View/Blade/BladePrependTest.php b/tests/View/Blade/BladePrependTest.php index 0780ba89d..c48b0af1f 100644 --- a/tests/View/Blade/BladePrependTest.php +++ b/tests/View/Blade/BladePrependTest.php @@ -3,6 +3,10 @@ namespace Hypervel\Tests\View\Blade; use Hypervel\Support\Str; +use Mockery; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidFactoryInterface; +use Ramsey\Uuid\UuidInterface; class BladePrependTest extends AbstractBladeTestCase { @@ -34,7 +38,11 @@ public function testPrependOnceIsCompiled() public function testPrependOnceIsCompiledWhenIdIsMissing() { - Str::createUuidsUsing(fn () => 'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'); + $uuid = Mockery::mock(UuidInterface::class); + $uuid->shouldReceive('__toString')->andReturn('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'); + $factory = Mockery::mock(UuidFactoryInterface::class); + $factory->shouldReceive('uuid4')->andReturn($uuid); + Uuid::setFactory($factory); $string = '@prependOnce(\'foo\') test diff --git a/tests/View/Blade/BladePushTest.php b/tests/View/Blade/BladePushTest.php index aac7c1e31..d13622bce 100644 --- a/tests/View/Blade/BladePushTest.php +++ b/tests/View/Blade/BladePushTest.php @@ -3,6 +3,10 @@ namespace Hypervel\Tests\View\Blade; use Hypervel\Support\Str; +use Mockery; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidFactoryInterface; +use Ramsey\Uuid\UuidInterface; class BladePushTest extends AbstractBladeTestCase { @@ -44,7 +48,11 @@ public function testPushOnceIsCompiled() public function testPushOnceIsCompiledWhenIdIsMissing() { - Str::createUuidsUsing(fn () => 'e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'); + $uuid = Mockery::mock(UuidInterface::class); + $uuid->shouldReceive('__toString')->andReturn('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'); + $factory = Mockery::mock(UuidFactoryInterface::class); + $factory->shouldReceive('uuid4')->andReturn($uuid); + Uuid::setFactory($factory); $string = '@pushOnce(\'foo\') test From 4d9bae707d249f32377c0b8248413b2d8bb32636 Mon Sep 17 00:00:00 2001 From: bluehaha Date: Fri, 28 Nov 2025 17:00:30 +0800 Subject: [PATCH 16/21] feat: migrate Laravel view --- src/filesystem/src/Filesystem.php | 22 ++ src/foundation/src/helpers.php | 8 +- src/support/src/ViewErrorBag.php | 11 + src/view/src/AnonymousComponent.php | 12 - src/view/src/AppendableAttributeValue.php | 7 - src/view/src/Compilers/BladeCompiler.php | 15 +- .../src/Compilers/ComponentTagCompiler.php | 2 +- .../Compilers/Concerns/CompilesComponents.php | 2 +- .../Concerns/CompilesConditionals.php | 2 +- .../src/Compilers/Concerns/CompilesLoops.php | 2 +- src/view/src/ComponentAttributeBag.php | 101 +------ src/view/src/ComponentSlot.php | 22 -- src/view/src/Concerns/ManagesComponents.php | 191 ++++++++------ src/view/src/Concerns/ManagesEvents.php | 42 --- src/view/src/Concerns/ManagesFragments.php | 4 +- src/view/src/Concerns/ManagesLayouts.php | 166 +++++------- src/view/src/Concerns/ManagesLoops.php | 50 ++-- src/view/src/Concerns/ManagesStacks.php | 132 +++++----- src/view/src/Concerns/ManagesTranslations.php | 17 +- src/view/src/DynamicComponent.php | 27 +- src/view/src/Engines/CompilerEngine.php | 2 +- src/view/src/Factory.php | 249 +++++------------- src/view/src/InvokableComponentVariable.php | 25 +- .../src/Middleware/ShareErrorsFromSession.php | 9 - src/view/src/View.php | 135 ++-------- src/view/src/ViewException.php | 11 +- src/view/src/ViewName.php | 3 - tests/View/ComponentTest.php | 57 ++-- tests/View/ViewBladeCompilerTest.php | 15 +- tests/View/ViewCompilerEngineTest.php | 7 +- tests/View/ViewComponentTest.php | 19 +- tests/View/ViewEngineResolverTest.php | 2 +- tests/View/ViewFactoryTest.php | 102 ++++--- tests/View/ViewTest.php | 22 +- tests/View/fixtures/http-exception.php | 2 +- 35 files changed, 527 insertions(+), 968 deletions(-) create mode 100644 src/support/src/ViewErrorBag.php diff --git a/src/filesystem/src/Filesystem.php b/src/filesystem/src/Filesystem.php index f078128ee..68150abd1 100644 --- a/src/filesystem/src/Filesystem.php +++ b/src/filesystem/src/Filesystem.php @@ -5,6 +5,7 @@ namespace Hypervel\Filesystem; use Hyperf\Support\Filesystem\Filesystem as HyperfFilesystem; +use Hypervel\Http\Exceptions\FileNotFoundException; class Filesystem extends HyperfFilesystem { @@ -17,4 +18,25 @@ public function ensureDirectoryExists(string $path, int $mode = 0755, bool $recu $this->makeDirectory($path, $mode, $recursive); } } + + /** + * Get the returned value of a file. + * + * @throws FileNotFoundException + */ + public function getRequire(string $path, array $data = [[]]) + { + if ($this->isFile($path)) { + $__path = $path; + $__data = $data; + + return (static function () use ($__path, $__data) { + extract($__data, EXTR_SKIP); + + return require $__path; + })(); + } + + throw new FileNotFoundException("File does not exist at path {$path}."); + } } diff --git a/src/foundation/src/helpers.php b/src/foundation/src/helpers.php index 0c4d19ad1..0f0f2eea9 100644 --- a/src/foundation/src/helpers.php +++ b/src/foundation/src/helpers.php @@ -7,8 +7,6 @@ use Hyperf\Contract\Arrayable; use Hyperf\HttpMessage\Cookie\Cookie; use Hyperf\Stringable\Stringable; -use Hyperf\ViewEngine\Contract\FactoryInterface; -use Hyperf\ViewEngine\Contract\ViewInterface; use Hypervel\Auth\Contracts\Factory as AuthFactoryContract; use Hypervel\Auth\Contracts\Gate; use Hypervel\Auth\Contracts\Guard; @@ -33,6 +31,8 @@ use Hypervel\Translation\Contracts\Translator as TranslatorContract; use Hypervel\Validation\Contracts\Factory as ValidatorFactoryContract; use Hypervel\Validation\Contracts\Validator as ValidatorContract; +use Hypervel\View\Contracts\Factory as FactoryContract; +use Hypervel\View\Contracts\View as ViewContract; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; @@ -734,9 +734,9 @@ function __(?string $key = null, array $replace = [], ?string $locale = null): a * @param null|string $view * @param array $mergeData */ - function view($view = null, array|Arrayable $data = [], $mergeData = []): FactoryInterface|ViewInterface + function view($view = null, array|Arrayable $data = [], $mergeData = []): ViewContract { - $factory = app(FactoryInterface::class); + $factory = app(FactoryContract::class); if (func_num_args() === 0) { return $factory; diff --git a/src/support/src/ViewErrorBag.php b/src/support/src/ViewErrorBag.php new file mode 100644 index 000000000..557a96c1f --- /dev/null +++ b/src/support/src/ViewErrorBag.php @@ -0,0 +1,11 @@ +getCompiledPath($path) ); - $needSaveCompiledFile = ! $this->files->exists($compiledPath) - || $this->files->hash($compiledPath, 'xxh128') !== hash('xxh128', $contents); - - if ($needSaveCompiledFile) { - $this->files->put($compiledPath, $contents); - } + $this->files->put($compiledPath, $contents); } /** @@ -851,7 +846,7 @@ public function usingEchoFormat(string $format, callable $callback): string /** * Set the echo format to be used by the compiler. */ - protected function setEchoFormat(string $format): void + public function setEchoFormat(string $format): void { Context::set(static::ECHO_FORMAT_CONTEXT_KEY, $format); } diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php index 6e53d0027..6897b2ee1 100644 --- a/src/view/src/Compilers/ComponentTagCompiler.php +++ b/src/view/src/Compilers/ComponentTagCompiler.php @@ -22,7 +22,7 @@ class ComponentTagCompiler /** * The "bind:" attributes that have been compiled for the current component. */ - protected const BOUND_ATTRIBUTES_CONTEXT_KEY = 'hypervel.view.component_tag_compiler.bound_attributes'; + protected const BOUND_ATTRIBUTES_CONTEXT_KEY = 'bound_attributes'; /** * The Blade compiler instance. diff --git a/src/view/src/Compilers/Concerns/CompilesComponents.php b/src/view/src/Compilers/Concerns/CompilesComponents.php index 0d041e4c3..465abfc00 100644 --- a/src/view/src/Compilers/Concerns/CompilesComponents.php +++ b/src/view/src/Compilers/Concerns/CompilesComponents.php @@ -16,7 +16,7 @@ trait CompilesComponents /** * The component name hash stack. */ - protected const COMPONENT_HASH_STACK_CONTEXT_KEY = 'hypervel.view.compilers_components.component_hash_stack'; + protected const COMPONENT_HASH_STACK_CONTEXT_KEY = 'component_hash_stack'; /** * Compile the component statements into valid PHP. diff --git a/src/view/src/Compilers/Concerns/CompilesConditionals.php b/src/view/src/Compilers/Concerns/CompilesConditionals.php index e59614c7b..84ec716a6 100644 --- a/src/view/src/Compilers/Concerns/CompilesConditionals.php +++ b/src/view/src/Compilers/Concerns/CompilesConditionals.php @@ -12,7 +12,7 @@ trait CompilesConditionals /** * Identifier for the first case in the switch statement. */ - protected const FIRST_CASE_IN_SWITCH_CONTEXT_KEY = 'hypervel.view.compiles_conditionals.first_case_in_switch'; + protected const FIRST_CASE_IN_SWITCH_CONTEXT_KEY = 'first_case_in_switch'; /** * Compile the if-auth statements into valid PHP. diff --git a/src/view/src/Compilers/Concerns/CompilesLoops.php b/src/view/src/Compilers/Concerns/CompilesLoops.php index 6067d30ee..432fa5891 100644 --- a/src/view/src/Compilers/Concerns/CompilesLoops.php +++ b/src/view/src/Compilers/Concerns/CompilesLoops.php @@ -12,7 +12,7 @@ trait CompilesLoops /** * Counter to keep track of nested forelse statements. */ - protected const FOR_ELSE_COUNTER_CONTEXT_KEY = 'hypervel.view.compiles_loops.for_else_counter'; + protected const FOR_ELSE_COUNTER_CONTEXT_KEY = 'for_else_counter'; protected function incrementForElseCounter(): int { diff --git a/src/view/src/ComponentAttributeBag.php b/src/view/src/ComponentAttributeBag.php index a2b20090c..c797adce9 100644 --- a/src/view/src/ComponentAttributeBag.php +++ b/src/view/src/ComponentAttributeBag.php @@ -24,16 +24,11 @@ class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSeria /** * The raw array of attributes. - * - * @var array */ protected array $attributes = []; /** * Create a new component attribute bag instance. - * - * @param array $attributes - * @return void */ public function __construct(array $attributes = []) { @@ -42,8 +37,6 @@ public function __construct(array $attributes = []) /** * Get all of the attribute values. - * - * @return array */ public function all(): array { @@ -52,9 +45,6 @@ public function all(): array /** * Get the first attribute's value. - * - * @param mixed $default - * @return mixed */ public function first(mixed $default = null): mixed { @@ -63,10 +53,6 @@ public function first(mixed $default = null): mixed /** * Get a given attribute from the attribute array. - * - * @param string $key - * @param mixed $default - * @return mixed */ public function get(string $key, mixed $default = null): mixed { @@ -75,9 +61,6 @@ public function get(string $key, mixed $default = null): mixed /** * Determine if a given attribute exists in the attribute array. - * - * @param array|string $key - * @return bool */ public function has(array|string $key): bool { @@ -94,9 +77,6 @@ public function has(array|string $key): bool /** * Determine if any of the keys exist in the attribute array. - * - * @param array|string $key - * @return bool */ public function hasAny(array|string $key): bool { @@ -117,9 +97,6 @@ public function hasAny(array|string $key): bool /** * Determine if a given attribute is missing from the attribute array. - * - * @param string $key - * @return bool */ public function missing(string $key): bool { @@ -128,9 +105,6 @@ public function missing(string $key): bool /** * Only include the given attribute from the attribute array. - * - * @param mixed $keys - * @return static */ public function only(mixed $keys): static { @@ -147,9 +121,6 @@ public function only(mixed $keys): static /** * Exclude the given attribute from the attribute array. - * - * @param mixed|array $keys - * @return static */ public function except(mixed $keys): static { @@ -166,9 +137,6 @@ public function except(mixed $keys): static /** * Filter the attributes, returning a bag of attributes that pass the filter. - * - * @param callable $callback - * @return static */ public function filter(callable $callback): static { @@ -179,7 +147,6 @@ public function filter(callable $callback): static * Return a bag of attributes that have keys starting with the given value / pattern. * * @param string|string[] $needles - * @return static */ public function whereStartsWith(string|array $needles): static { @@ -192,7 +159,6 @@ public function whereStartsWith(string|array $needles): static * 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(string|array $needles): static { @@ -205,7 +171,6 @@ public function whereDoesntStartWith(string|array $needles): static * Return a bag of attributes that have keys starting with the given value / pattern. * * @param string|string[] $needles - * @return static */ public function thatStartWith(string|array $needles): static { @@ -214,9 +179,6 @@ public function thatStartWith(string|array $needles): static /** * Only include the given attribute from the attribute array. - * - * @param mixed|array $keys - * @return static */ public function onlyProps(mixed $keys): static { @@ -225,9 +187,6 @@ public function onlyProps(mixed $keys): static /** * Exclude the given attribute from the attribute array. - * - * @param mixed|array $keys - * @return static */ public function exceptProps(mixed $keys): static { @@ -236,9 +195,6 @@ public function exceptProps(mixed $keys): static /** * Conditionally merge classes into the attribute bag. - * - * @param mixed|array $classList - * @return static */ public function class(mixed $classList): static { @@ -249,9 +205,6 @@ public function class(mixed $classList): static /** * Conditionally merge styles into the attribute bag. - * - * @param mixed|array $styleList - * @return static */ public function style(mixed $styleList): static { @@ -262,10 +215,6 @@ public function style(mixed $styleList): static /** * Merge additional attributes / values into the attribute bag. - * - * @param array $attributeDefaults - * @param bool $escape - * @return static */ public function merge(array $attributeDefaults = [], bool $escape = true): static { @@ -300,10 +249,6 @@ public function merge(array $attributeDefaults = [], bool $escape = true): stati /** * Determine if the specific attribute value should be escaped. - * - * @param bool $escape - * @param mixed $value - * @return bool */ protected function shouldEscapeAttributeValue(bool $escape, mixed $value): bool { @@ -318,9 +263,6 @@ protected function shouldEscapeAttributeValue(bool $escape, mixed $value): bool /** * Create a new appendable attribute value. - * - * @param mixed $value - * @return \Hypervel\View\AppendableAttributeValue */ public function prepends(mixed $value): AppendableAttributeValue { @@ -329,11 +271,6 @@ public function prepends(mixed $value): AppendableAttributeValue /** * Resolve an appendable attribute value default value. - * - * @param array $attributeDefaults - * @param string $key - * @param bool $escape - * @return mixed */ protected function resolveAppendableAttributeDefault(array $attributeDefaults, string $key, bool $escape): mixed { @@ -346,8 +283,6 @@ protected function resolveAppendableAttributeDefault(array $attributeDefaults, s /** * Determine if the attribute bag is empty. - * - * @return bool */ public function isEmpty(): bool { @@ -356,8 +291,6 @@ public function isEmpty(): bool /** * Determine if the attribute bag is not empty. - * - * @return bool */ public function isNotEmpty(): bool { @@ -366,8 +299,6 @@ public function isNotEmpty(): bool /** * Get all of the raw attributes. - * - * @return array */ public function getAttributes(): array { @@ -376,9 +307,6 @@ public function getAttributes(): array /** * Set the underlying attributes. - * - * @param array $attributes - * @return void */ public function setAttributes(array $attributes): void { @@ -396,9 +324,6 @@ public function setAttributes(array $attributes): void /** * Extract "prop" names from given keys. - * - * @param array $keys - * @return array */ public static function extractPropNames(array $keys): array { @@ -416,8 +341,6 @@ public static function extractPropNames(array $keys): array /** * Get content as a string of HTML. - * - * @return string */ public function toHtml(): string { @@ -426,9 +349,6 @@ public function toHtml(): string /** * Merge additional attributes / values into the attribute bag. - * - * @param array $attributeDefaults - * @return \Hypervel\Support\HtmlString */ public function __invoke(array $attributeDefaults = []): HtmlString { @@ -437,9 +357,6 @@ public function __invoke(array $attributeDefaults = []): HtmlString /** * Determine if the given offset exists. - * - * @param string $offset - * @return bool */ public function offsetExists(mixed $offset): bool { @@ -448,9 +365,6 @@ public function offsetExists(mixed $offset): bool /** * Get the value at the given offset. - * - * @param string $offset - * @return mixed */ public function offsetGet(mixed $offset): mixed { @@ -459,10 +373,6 @@ public function offsetGet(mixed $offset): mixed /** * Set the value at a given offset. - * - * @param string $offset - * @param mixed $value - * @return void */ public function offsetSet(mixed $offset, mixed $value): void { @@ -471,9 +381,6 @@ public function offsetSet(mixed $offset, mixed $value): void /** * Remove the value at the given offset. - * - * @param string $offset - * @return void */ public function offsetUnset(mixed $offset): void { @@ -482,8 +389,6 @@ public function offsetUnset(mixed $offset): void /** * Get an iterator for the items. - * - * @return \ArrayIterator */ public function getIterator(): Traversable { @@ -492,8 +397,6 @@ public function getIterator(): Traversable /** * Convert the object into a JSON serializable form. - * - * @return mixed */ public function jsonSerialize(): mixed { @@ -502,8 +405,6 @@ public function jsonSerialize(): mixed /** * Implode the attributes into a single HTML ready string. - * - * @return string */ public function __toString(): string { @@ -518,7 +419,7 @@ public function __toString(): string $value = $key === 'x-data' || str_starts_with($key, 'wire:') ? '' : $key; } - $string .= ' '.$key.'="'.str_replace('"', '\\"', trim($value)).'"'; + $string .= ' '.$key.'="'.str_replace('"', '\\"', trim((string) $value)).'"'; } return trim($string); diff --git a/src/view/src/ComponentSlot.php b/src/view/src/ComponentSlot.php index 43a09b7eb..ba75bd405 100644 --- a/src/view/src/ComponentSlot.php +++ b/src/view/src/ComponentSlot.php @@ -12,24 +12,16 @@ class ComponentSlot implements Htmlable, Stringable { /** * The slot attribute bag. - * - * @var \Hypervel\View\ComponentAttributeBag */ public ComponentAttributeBag $attributes; /** * The slot contents. - * - * @var string */ protected string $contents; /** * Create a new slot instance. - * - * @param string $contents - * @param array $attributes - * @return void */ public function __construct(string $contents = '', array $attributes = []) { @@ -40,9 +32,6 @@ public function __construct(string $contents = '', array $attributes = []) /** * Set the extra attributes that the slot should make available. - * - * @param array $attributes - * @return $this */ public function withAttributes(array $attributes): static { @@ -53,8 +42,6 @@ public function withAttributes(array $attributes): static /** * Get the slot's HTML string. - * - * @return string */ public function toHtml(): string { @@ -63,8 +50,6 @@ public function toHtml(): string /** * Determine if the slot is empty. - * - * @return bool */ public function isEmpty(): bool { @@ -73,8 +58,6 @@ public function isEmpty(): bool /** * Determine if the slot is not empty. - * - * @return bool */ public function isNotEmpty(): bool { @@ -83,9 +66,6 @@ public function isNotEmpty(): bool /** * Determine if the slot has non-comment content. - * - * @param callable|string|null $callable - * @return bool */ public function hasActualContent(callable|string|null $callable = null): bool { @@ -102,8 +82,6 @@ public function hasActualContent(callable|string|null $callable = null): bool /** * Get the slot's HTML string. - * - * @return string */ public function __toString(): string { diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php index 2bd4f7079..abb22b930 100644 --- a/src/view/src/Concerns/ManagesComponents.php +++ b/src/view/src/Concerns/ManagesComponents.php @@ -4,6 +4,8 @@ namespace Hypervel\View\Concerns; +use Closure; +use Hypervel\Context\Context; use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Contracts\View; use Hypervel\Support\Arr; @@ -12,64 +14,78 @@ trait ManagesComponents { /** - * The components being rendered. - * - * @var array + * Context key for the components being rendered. */ - protected array $componentStack = []; + protected const COMPONENT_STACK_CONTEXT_KEY = 'component_stack'; /** - * The original data passed to the component. - * - * @var array + * Context key for the original data passed to the component. */ - protected array $componentData = []; + protected const COMPONENT_DATA_CONTEXT_KEY = 'component_data'; /** - * The component data for the component that is currently being rendered. - * - * @var array + * Context key for the component data for the component that is currently being rendered. */ - protected array $currentComponentData = []; + protected const CURRENT_COMPONENT_DATA_CONTEXT_KEY = 'current_component_data'; /** - * The slot contents for the component. - * - * @var array + * Context key for the slot contents for the component. */ - protected array $slots = []; + protected const SLOTS_CONTEXT_KEY = 'slots'; /** - * The names of the slots being rendered. - * - * @var array + * Context key for the names of the slots being rendered. */ - protected array $slotStack = []; + protected const SLOT_STACK_CONTEXT_KEY = 'slot_stack'; /** * Start a component rendering process. - * - * @param \Hypervel\Contracts\View\View|\Hypervel\Contracts\Support\Htmlable|\Closure|string $view - * @param array $data - * @return void */ - public function startComponent(mixed $view, array $data = []): void + public function startComponent(View|Htmlable|Closure|string $view, array $data = []): void { if (ob_start()) { - $this->componentStack[] = $view; + $this->pushComponentStack($view); - $this->componentData[$this->currentComponent()] = $data; + $this->appendComponentData($data); - $this->slots[$this->currentComponent()] = []; + $this->createSlotContext(); } } + protected function pushComponentStack(View|Htmlable|Closure|string $view): int + { + $componentStack = Context::get(static::COMPONENT_STACK_CONTEXT_KEY, []); + $componentStack[] = $view; + Context::set(static::COMPONENT_STACK_CONTEXT_KEY, $componentStack); + + return count($componentStack); + } + + protected function popComponentStack(): View|Htmlable|Closure|string + { + $componentStack = Context::get(static::COMPONENT_STACK_CONTEXT_KEY, []); + $view = array_pop($componentStack); + Context::set(static::COMPONENT_STACK_CONTEXT_KEY, $componentStack); + + return $view; + } + + protected function appendComponentData(array $data): void + { + $componentData = Context::get(static::COMPONENT_DATA_CONTEXT_KEY, []); + $componentData[$this->currentComponent()] = $data; + Context::set(static::COMPONENT_DATA_CONTEXT_KEY, $componentData); + } + + protected function createSlotContext() + { + $slots = Context::get(static::SLOTS_CONTEXT_KEY, []); + $slots[$this->currentComponent()] = []; + Context::set(static::SLOTS_CONTEXT_KEY, $slots); + } + /** * 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 = []): void { @@ -82,17 +98,16 @@ public function startComponentFirst(array $names, array $data = []): void /** * Render the current component. - * - * @return string */ public function renderComponent(): string { - $view = array_pop($this->componentStack); + $view = $this->popComponentStack(); - $this->currentComponentData = array_merge( - $previousComponentData = $this->currentComponentData, - $data = $this->componentData() - ); + $previousComponentData = Context::get(static::CURRENT_COMPONENT_DATA_CONTEXT_KEY, []); + $data = $this->componentData(); + + $currentComponentData = array_merge($previousComponentData, $data); + Context::set(static::CURRENT_COMPONENT_DATA_CONTEXT_KEY, $currentComponentData); try { $view = value($view, $data); @@ -105,52 +120,57 @@ public function renderComponent(): string return $this->make($view, $data)->render(); } } finally { - $this->currentComponentData = $previousComponentData; + Context::set(static::CURRENT_COMPONENT_DATA_CONTEXT_KEY, $previousComponentData); } } /** * Get the data for the given component. - * - * @return array */ protected function componentData(): array { $defaultSlot = new ComponentSlot(trim(ob_get_clean())); + $componentStack = Context::get(static::COMPONENT_STACK_CONTEXT_KEY, []); + $componentData = Context::get(static::COMPONENT_DATA_CONTEXT_KEY, []); + $slotsData = Context::get(static::SLOTS_CONTEXT_KEY, []); + + $stackCount = count($componentStack); + $slots = array_merge([ '__default' => $defaultSlot, - ], $this->slots[count($this->componentStack)]); + ], $slotsData[$stackCount] ?? []); return array_merge( - $this->componentData[count($this->componentStack)], + $componentData[$stackCount] ?? [], ['slot' => $defaultSlot], - $this->slots[count($this->componentStack)], + $slotsData[$stackCount] ?? [], ['__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(string $key, mixed $default = null): mixed { - if (array_key_exists($key, $this->currentComponentData)) { - return $this->currentComponentData[$key]; + $currentComponentData = Context::get(static::CURRENT_COMPONENT_DATA_CONTEXT_KEY, []); + + if (array_key_exists($key, $currentComponentData)) { + return $currentComponentData[$key]; } - $currentComponent = count($this->componentStack); + $componentStack = Context::get(static::COMPONENT_STACK_CONTEXT_KEY, []); + $currentComponent = count($componentStack); if ($currentComponent === 0) { return value($default); } + $componentData = Context::get(static::COMPONENT_DATA_CONTEXT_KEY, []); + for ($i = $currentComponent - 1; $i >= 0; $i--) { - $data = $this->componentData[$i] ?? []; + $data = $componentData[$i] ?? []; if (array_key_exists($key, $data)) { return $data[$key]; @@ -162,62 +182,77 @@ public function getConsumableComponentData(string $key, mixed $default = null): /** * Start the slot rendering process. - * - * @param string $name - * @param string|null $content - * @param array $attributes - * @return void */ public function slot(string $name, ?string $content = null, array $attributes = []): void { if (func_num_args() === 2 || $content !== null) { - $this->slots[$this->currentComponent()][$name] = $content; + $this->setSlotData($name, $content); } elseif (ob_start()) { - $this->slots[$this->currentComponent()][$name] = ''; + $this->setSlotData($name, ''); - $this->slotStack[$this->currentComponent()][] = [$name, $attributes]; + $this->pushSlotStack([$name, $attributes]); } } + protected function setSlotData(string $name, null|string|ComponentSlot $content): void + { + $currentComponent = $this->currentComponent(); + + $slots = Context::get(static::SLOTS_CONTEXT_KEY, []); + $slots[$currentComponent][$name] = $content; + Context::set(static::SLOTS_CONTEXT_KEY, $slots); + } + + protected function pushSlotStack(array $value): void + { + $currentComponent = $this->currentComponent(); + + $slotStack = Context::get(static::SLOT_STACK_CONTEXT_KEY, []); + $slotStack[$currentComponent][] = $value; + Context::set(static::SLOT_STACK_CONTEXT_KEY, $slotStack); + } + + protected function popSlotStack(): array + { + $currentComponent = $this->currentComponent(); + + $slotStack = Context::get(static::SLOT_STACK_CONTEXT_KEY, []); + $value = array_pop($slotStack[$currentComponent]); + Context::set(static::SLOT_STACK_CONTEXT_KEY, $slotStack); + + return $value; + } + /** * Save the slot content for rendering. - * - * @return void */ public function endSlot(): void { - last($this->componentStack); - - $currentSlot = array_pop( - $this->slotStack[$this->currentComponent()] - ); + $currentSlot = $this->popSlotStack(); [$currentName, $currentAttributes] = $currentSlot; - $this->slots[$this->currentComponent()][$currentName] = new ComponentSlot( + $this->setSlotData($currentName, new ComponentSlot( trim(ob_get_clean()), $currentAttributes - ); + )); } /** * Get the index for the current component. - * - * @return int */ protected function currentComponent(): int { - return count($this->componentStack) - 1; + $componentStack = Context::get(static::COMPONENT_STACK_CONTEXT_KEY, []); + return count($componentStack) - 1; } /** * Flush all of the component state. - * - * @return void */ protected function flushComponents(): void { - $this->componentStack = []; - $this->componentData = []; - $this->currentComponentData = []; + Context::set(static::COMPONENT_STACK_CONTEXT_KEY, []); + Context::set(static::COMPONENT_DATA_CONTEXT_KEY, []); + Context::set(static::CURRENT_COMPONENT_DATA_CONTEXT_KEY, []); } } diff --git a/src/view/src/Concerns/ManagesEvents.php b/src/view/src/Concerns/ManagesEvents.php index 8dec6589f..bef364b49 100644 --- a/src/view/src/Concerns/ManagesEvents.php +++ b/src/view/src/Concerns/ManagesEvents.php @@ -12,10 +12,6 @@ trait ManagesEvents { /** * Register a view creator event. - * - * @param array|string $views - * @param \Closure|string $callback - * @return array */ public function creator(array|string $views, Closure|string $callback): array { @@ -30,9 +26,6 @@ public function creator(array|string $views, Closure|string $callback): array /** * Register multiple view composers via an array. - * - * @param array $composers - * @return array */ public function composers(array $composers): array { @@ -47,10 +40,6 @@ public function composers(array $composers): array /** * Register a view composer event. - * - * @param array|string $views - * @param \Closure|string $callback - * @return array */ public function composer(array|string $views, Closure|string $callback): array { @@ -65,11 +54,6 @@ public function composer(array|string $views, Closure|string $callback): array /** * Add an event for a given view. - * - * @param string $view - * @param \Closure|string $callback - * @param string $prefix - * @return \Closure|null */ protected function addViewEvent(string $view, Closure|string $callback, string $prefix = 'composing: '): ?Closure { @@ -86,11 +70,6 @@ protected function addViewEvent(string $view, Closure|string $callback, string $ /** * Register a class based view composer. - * - * @param string $view - * @param string $class - * @param string $prefix - * @return \Closure */ protected function addClassEvent(string $view, string $class, string $prefix): Closure { @@ -110,10 +89,6 @@ protected function addClassEvent(string $view, string $class, string $prefix): C /** * Build a class based container callback Closure. - * - * @param string $class - * @param string $prefix - * @return \Closure */ protected function buildClassEventCallback(string $class, string $prefix): Closure { @@ -129,10 +104,6 @@ protected function buildClassEventCallback(string $class, string $prefix): Closu /** * Parse a class based composer name. - * - * @param string $class - * @param string $prefix - * @return array */ protected function parseClassEvent(string $class, string $prefix): array { @@ -141,9 +112,6 @@ protected function parseClassEvent(string $class, string $prefix): array /** * Determine the class event method based on the given prefix. - * - * @param string $prefix - * @return string */ protected function classEventMethodForPrefix(string $prefix): string { @@ -152,10 +120,6 @@ protected function classEventMethodForPrefix(string $prefix): string /** * Add a listener to the event dispatcher. - * - * @param string $name - * @param \Closure $callback - * @return void */ protected function addEventListener(string $name, Closure $callback): void { @@ -170,9 +134,6 @@ protected function addEventListener(string $name, Closure $callback): void /** * Call the composer for a given view. - * - * @param \Hypervel\Contracts\View\View $view - * @return void */ public function callComposer(ViewContract $view): void { @@ -183,9 +144,6 @@ public function callComposer(ViewContract $view): void /** * Call the creator for a given view. - * - * @param \Hypervel\Contracts\View\View $view - * @return void */ public function callCreator(ViewContract $view): void { diff --git a/src/view/src/Concerns/ManagesFragments.php b/src/view/src/Concerns/ManagesFragments.php index 2b29df2be..8e8f489fd 100644 --- a/src/view/src/Concerns/ManagesFragments.php +++ b/src/view/src/Concerns/ManagesFragments.php @@ -12,12 +12,12 @@ trait ManagesFragments /** * All of the captured, rendered fragments. */ - protected const FRAGMENTS_CONTEXT_KEY = 'hypervel.view.manages_fragments.fragments'; + protected const FRAGMENTS_CONTEXT_KEY = 'fragments'; /** * The stack of in-progress fragment renders. */ - protected const FRAGMENT_STACK_CONTEXT_KEY = 'hypervel.view.manages_fragments.fragment_stack'; + protected const FRAGMENT_STACK_CONTEXT_KEY = 'fragment_stack'; /** * Start injecting content into a fragment. diff --git a/src/view/src/Concerns/ManagesLayouts.php b/src/view/src/Concerns/ManagesLayouts.php index 694789251..98b4f1943 100644 --- a/src/view/src/Concerns/ManagesLayouts.php +++ b/src/view/src/Concerns/ManagesLayouts.php @@ -4,6 +4,7 @@ namespace Hypervel\View\Concerns; +use Hypervel\Context\Context; use Hypervel\View\Contracts\View; use Hypervel\Support\Str; use InvalidArgumentException; @@ -11,57 +12,38 @@ trait ManagesLayouts { /** - * All of the finished, captured sections. - * - * @var array - */ - protected array $sections = []; - - /** - * The stack of in-progress sections. - * - * @var array + * Context key for finished, captured sections. */ - protected array $sectionStack = []; + protected const SECTIONS_CONTEXT_KEY = 'sections'; /** - * The parent placeholder for the request. - * - * @var mixed + * Context key for the stack of in-progress sections. */ - protected static array $parentPlaceholder = []; + protected const SECTION_STACK_CONTEXT_KEY = 'section_stack'; /** - * The parent placeholder salt for the request. - * - * @var string + * Context key for the parent placeholder. */ - protected static ?string $parentPlaceholderSalt = null; + protected const PARENT_PLACEHOLDER_CONTEXT_KEY = 'parent_placeholder'; /** * Start injecting content into a section. - * - * @param string $section - * @param string|null $content - * @return void */ - public function startSection(string $section, mixed $content = null): void + public function startSection(string $section, string|View|null $content = null): void { if ($content === null) { if (ob_start()) { - $this->sectionStack[] = $section; + $sectionStack = Context::get(static::SECTION_STACK_CONTEXT_KEY, []); + $sectionStack[] = $section; + Context::set(static::SECTION_STACK_CONTEXT_KEY, $sectionStack); } } else { - $this->extendSection($section, $content instanceof View ? $content : e($content)); + $this->extendSection($section, $content instanceof View ? (string) $content : e($content)); } } /** * Inject inline content into a section. - * - * @param string $section - * @param string $content - * @return void */ public function inject(string $section, string $content): void { @@ -70,12 +52,12 @@ public function inject(string $section, string $content): void /** * Stop injecting content into a section and return its contents. - * - * @return string */ public function yieldSection(): string { - if (empty($this->sectionStack)) { + $sectionStack = Context::get(static::SECTION_STACK_CONTEXT_KEY, []); + + if (empty($sectionStack)) { return ''; } @@ -85,21 +67,23 @@ public function yieldSection(): string /** * Stop injecting content into a section. * - * @param bool $overwrite - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function stopSection(bool $overwrite = false): string { - if (empty($this->sectionStack)) { + $sectionStack = Context::get(static::SECTION_STACK_CONTEXT_KEY, []); + + if (empty($sectionStack)) { throw new InvalidArgumentException('Cannot end a section without first starting one.'); } - $last = array_pop($this->sectionStack); + $last = array_pop($sectionStack); + Context::set(static::SECTION_STACK_CONTEXT_KEY, $sectionStack); if ($overwrite) { - $this->sections[$last] = ob_get_clean(); + $sections = Context::get(static::SECTIONS_CONTEXT_KEY, []); + $sections[$last] = ob_get_clean(); + Context::set(static::SECTIONS_CONTEXT_KEY, $sections); } else { $this->extendSection($last, ob_get_clean()); } @@ -110,112 +94,91 @@ public function stopSection(bool $overwrite = false): string /** * Stop injecting content into a section and append it. * - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function appendSection(): string { - if (empty($this->sectionStack)) { + $sectionStack = Context::get(static::SECTION_STACK_CONTEXT_KEY, []); + + if (empty($sectionStack)) { throw new InvalidArgumentException('Cannot end a section without first starting one.'); } - $last = array_pop($this->sectionStack); + $last = array_pop($sectionStack); + Context::set(static::SECTION_STACK_CONTEXT_KEY, $sectionStack); - if (isset($this->sections[$last])) { - $this->sections[$last] .= ob_get_clean(); + $sections = Context::get(static::SECTIONS_CONTEXT_KEY, []); + if (isset($sections[$last])) { + $sections[$last] .= ob_get_clean(); } else { - $this->sections[$last] = ob_get_clean(); + $sections[$last] = ob_get_clean(); } + Context::set(static::SECTIONS_CONTEXT_KEY, $sections); return $last; } /** * Append content to a given section. - * - * @param string $section - * @param string $content - * @return void */ protected function extendSection(string $section, string $content): void { - if (isset($this->sections[$section])) { - $content = str_replace(static::parentPlaceholder($section), $content, $this->sections[$section]); + $sections = Context::get(static::SECTIONS_CONTEXT_KEY, []); + + if (isset($sections[$section])) { + $content = str_replace($this->getParentPlaceholder($section), $content, $sections[$section]); } - $this->sections[$section] = $content; + $sections[$section] = $content; + Context::set(static::SECTIONS_CONTEXT_KEY, $sections); } /** * Get the string contents of a section. - * - * @param string $section - * @param string $default - * @return string */ - public function yieldContent(string $section, string $default = ''): string + public function yieldContent(string $section, string|View $default = ''): string { - $sectionContent = $default instanceof View ? $default : e($default); + $sectionContent = $default instanceof View ? (string) $default : e($default); - if (isset($this->sections[$section])) { - $sectionContent = $this->sections[$section]; + $sections = Context::get(static::SECTIONS_CONTEXT_KEY, []); + if (isset($sections[$section])) { + $sectionContent = $sections[$section]; } $sectionContent = str_replace('@@parent', '--parent--holder--', $sectionContent); return str_replace( - '--parent--holder--', '@parent', str_replace(static::parentPlaceholder($section), '', $sectionContent) + '--parent--holder--', '@parent', str_replace($this->getParentPlaceholder($section), '', $sectionContent) ); } /** * Get the parent placeholder for the current request. - * - * @param string $section - * @return string */ - public static function parentPlaceholder(string $section = ''): string + public function getParentPlaceholder(string $section = ''): string { - if (! isset(static::$parentPlaceholder[$section])) { - $salt = static::parentPlaceholderSalt(); - - static::$parentPlaceholder[$section] = '##parent-placeholder-'.hash('xxh128', $salt.$section).'##'; - } - - return static::$parentPlaceholder[$section]; - } + $parentPlaceholder = Context::get(static::PARENT_PLACEHOLDER_CONTEXT_KEY, []); - /** - * Get the parent placeholder salt. - * - * @return string - */ - protected static function parentPlaceholderSalt(): string - { - if (! static::$parentPlaceholderSalt) { - return static::$parentPlaceholderSalt = Str::random(40); + if (! isset($parentPlaceholder[$section])) { + $salt = Str::random(40); + $parentPlaceholder[$section] = '##parent-placeholder-'.hash('xxh128', $salt.$section).'##'; + Context::set(static::PARENT_PLACEHOLDER_CONTEXT_KEY, $parentPlaceholder); } - return static::$parentPlaceholderSalt; + return $parentPlaceholder[$section]; } /** * Check if section exists. - * - * @param string $name - * @return bool */ public function hasSection(string $name): bool { - return array_key_exists($name, $this->sections); + $sections = Context::get(static::SECTIONS_CONTEXT_KEY, []); + return array_key_exists($name, $sections); } /** * Check if section does not exist. - * - * @param string $name - * @return bool */ public function sectionMissing(string $name): bool { @@ -224,34 +187,27 @@ public function sectionMissing(string $name): bool /** * Get the contents of a section. - * - * @param string $name - * @param string|null $default - * @return mixed */ public function getSection(string $name, ?string $default = null): mixed { - return $this->getSections()[$name] ?? $default; + $sections = Context::get(static::SECTIONS_CONTEXT_KEY, []); + return $sections[$name] ?? $default; } /** * Get the entire array of sections. - * - * @return array */ public function getSections(): array { - return $this->sections; + return Context::get(static::SECTIONS_CONTEXT_KEY, []); } /** * Flush all of the sections. - * - * @return void */ public function flushSections(): void { - $this->sections = []; - $this->sectionStack = []; + Context::set(static::SECTIONS_CONTEXT_KEY, []); + Context::set(static::SECTION_STACK_CONTEXT_KEY, []); } } diff --git a/src/view/src/Concerns/ManagesLoops.php b/src/view/src/Concerns/ManagesLoops.php index ee5fd37d5..f0885590d 100644 --- a/src/view/src/Concerns/ManagesLoops.php +++ b/src/view/src/Concerns/ManagesLoops.php @@ -4,33 +4,33 @@ namespace Hypervel\View\Concerns; +use Closure; +use Generator; +use Hypervel\Context\Context; use Hypervel\Support\Arr; use Hypervel\Support\LazyCollection; +use stdClass; trait ManagesLoops { /** - * The stack of in-progress loops. - * - * @var array + * The context key for loops stack. */ - protected array $loopsStack = []; + protected const LOOPS_STACK_CONTEXT_KEY = 'loops_stack'; /** * Add new loop to the stack. - * - * @param \Countable|array $data - * @return void */ - public function addLoop(mixed $data): void + public function addLoop(Closure|array|Generator|LazyCollection $data): void { $length = is_countable($data) && ! $data instanceof LazyCollection ? count($data) : null; - $parent = Arr::last($this->loopsStack); + $loopsStack = Context::get(static::LOOPS_STACK_CONTEXT_KEY, []); + $parent = Arr::last($loopsStack); - $this->loopsStack[] = [ + $loopsStack[] = [ 'iteration' => 0, 'index' => 0, 'remaining' => $length ?? null, @@ -39,21 +39,22 @@ public function addLoop(mixed $data): void 'last' => isset($length) ? $length == 1 : null, 'odd' => false, 'even' => true, - 'depth' => count($this->loopsStack) + 1, + 'depth' => count($loopsStack) + 1, 'parent' => $parent ? (object) $parent : null, ]; + + Context::set(static::LOOPS_STACK_CONTEXT_KEY, $loopsStack); } /** * Increment the top loop's indices. - * - * @return void */ public function incrementLoopIndices(): void { - $loop = $this->loopsStack[$index = count($this->loopsStack) - 1]; + $loopsStack = Context::get(static::LOOPS_STACK_CONTEXT_KEY, []); + $loop = $loopsStack[$index = count($loopsStack) - 1]; - $this->loopsStack[$index] = array_merge($this->loopsStack[$index], [ + $loopsStack[$index] = array_merge($loopsStack[$index], [ 'iteration' => $loop['iteration'] + 1, 'index' => $loop['iteration'], 'first' => $loop['iteration'] == 0, @@ -62,37 +63,36 @@ public function incrementLoopIndices(): void 'remaining' => isset($loop['count']) ? $loop['remaining'] - 1 : null, 'last' => isset($loop['count']) ? $loop['iteration'] == $loop['count'] - 1 : null, ]); + + Context::set(static::LOOPS_STACK_CONTEXT_KEY, $loopsStack); } /** * Pop a loop from the top of the loop stack. - * - * @return void */ public function popLoop(): void { - array_pop($this->loopsStack); + $loopsStack = Context::get(static::LOOPS_STACK_CONTEXT_KEY, []); + array_pop($loopsStack); + Context::set(static::LOOPS_STACK_CONTEXT_KEY, $loopsStack); } /** * Get an instance of the last loop in the stack. - * - * @return \stdClass|null */ - public function getLastLoop(): ?\stdClass + public function getLastLoop(): ?stdClass { - if ($last = Arr::last($this->loopsStack)) { + $loopsStack = Context::get(static::LOOPS_STACK_CONTEXT_KEY, []); + if ($last = Arr::last($loopsStack)) { return (object) $last; } } /** * Get the entire loop stack. - * - * @return array */ public function getLoopStack(): array { - return $this->loopsStack; + return Context::get(static::LOOPS_STACK_CONTEXT_KEY, []); } } diff --git a/src/view/src/Concerns/ManagesStacks.php b/src/view/src/Concerns/ManagesStacks.php index 47770a53c..ab0095037 100644 --- a/src/view/src/Concerns/ManagesStacks.php +++ b/src/view/src/Concerns/ManagesStacks.php @@ -4,99 +4,104 @@ namespace Hypervel\View\Concerns; +use Hypervel\Context\Context; use InvalidArgumentException; trait ManagesStacks { /** - * All of the finished, captured push sections. - * - * @var array + * Context key for finished, captured push sections. */ - protected array $pushes = []; + protected const PUSHES_CONTEXT_KEY = 'pushes'; /** - * All of the finished, captured prepend sections. - * - * @var array + * Context key for finished, captured prepend sections. */ - protected array $prepends = []; + protected const PREPENDS_CONTEXT_KEY = 'prepends'; /** - * The stack of in-progress push sections. - * - * @var array + * Context key for the stack of in-progress push sections. */ - protected array $pushStack = []; + protected const PUSH_STACK_CONTEXT_KEY = 'push_stack'; /** * Start injecting content into a push section. - * - * @param string $section - * @param string $content - * @return void */ public function startPush(string $section, string $content = ''): void { if ($content === '') { if (ob_start()) { - $this->pushStack[] = $section; + $this->pushStack($section); } } else { $this->extendPush($section, $content); } } + protected function pushStack(string $section): void + { + $pushStack = Context::get(static::PUSH_STACK_CONTEXT_KEY, []); + $pushStack[] = $section; + Context::set(static::PUSH_STACK_CONTEXT_KEY, $pushStack); + } + + private function popStack(): string + { + $pushStack = Context::get(static::PUSH_STACK_CONTEXT_KEY, []); + $last = array_pop($pushStack); + Context::set(static::PUSH_STACK_CONTEXT_KEY, $pushStack); + + return $last; + } + /** * Stop injecting content into a push section. * - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function stopPush(): string { - if (empty($this->pushStack)) { + $last = $this->popStack(); + + if (empty($last)) { throw new InvalidArgumentException('Cannot end a push stack without first starting one.'); } - return tap(array_pop($this->pushStack), function ($last) { + return tap($last, 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(string $section, string $content): void { - if (! isset($this->pushes[$section])) { - $this->pushes[$section] = []; + $pushes = Context::get(static::PUSHES_CONTEXT_KEY, []); + + if (! isset($pushes[$section])) { + $pushes[$section] = []; } - if (! isset($this->pushes[$section][$this->renderCount])) { - $this->pushes[$section][$this->renderCount] = $content; + $renderCount = $this->getRenderCount(); + + if (! isset($pushes[$section][$renderCount])) { + $pushes[$section][$renderCount] = $content; } else { - $this->pushes[$section][$this->renderCount] .= $content; + $pushes[$section][$renderCount] .= $content; } + + Context::set(static::PUSHES_CONTEXT_KEY, $pushes); } /** * Start prepending content into a push section. - * - * @param string $section - * @param string $content - * @return void */ public function startPrepend(string $section, string $content = ''): void { if ($content === '') { if (ob_start()) { - $this->pushStack[] = $section; + $this->pushStack($section); } } else { $this->extendPrepend($section, $content); @@ -106,62 +111,63 @@ public function startPrepend(string $section, string $content = ''): void /** * Stop prepending content into a push section. * - * @return string - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function stopPrepend(): string { - if (empty($this->pushStack)) { + $last = $this->popStack(); + + if (empty($last)) { throw new InvalidArgumentException('Cannot end a prepend operation without first starting one.'); } - return tap(array_pop($this->pushStack), function ($last) { + return tap($last, 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(string $section, string $content): void { - if (! isset($this->prepends[$section])) { - $this->prepends[$section] = []; + $prepends = Context::get(static::PREPENDS_CONTEXT_KEY, []); + + if (! isset($prepends[$section])) { + $prepends[$section] = []; } - if (! isset($this->prepends[$section][$this->renderCount])) { - $this->prepends[$section][$this->renderCount] = $content; + $renderCount = $this->getRenderCount(); + + if (! isset($prepends[$section][$renderCount])) { + $prepends[$section][$renderCount] = $content; } else { - $this->prepends[$section][$this->renderCount] = $content.$this->prepends[$section][$this->renderCount]; + $prepends[$section][$renderCount] = $content.$prepends[$section][$renderCount]; } + + Context::set(static::PREPENDS_CONTEXT_KEY, $prepends); } /** * Get the string contents of a push section. - * - * @param string $section - * @param string $default - * @return string */ public function yieldPushContent(string $section, string $default = ''): string { - if (! isset($this->pushes[$section]) && ! isset($this->prepends[$section])) { + $pushes = Context::get(static::PUSHES_CONTEXT_KEY, []); + $prepends = Context::get(static::PREPENDS_CONTEXT_KEY, []); + + if (! isset($pushes[$section]) && ! isset($prepends[$section])) { return $default; } $output = ''; - if (isset($this->prepends[$section])) { - $output .= implode(array_reverse($this->prepends[$section])); + if (isset($prepends[$section])) { + $output .= implode(array_reverse($prepends[$section])); } - if (isset($this->pushes[$section])) { - $output .= implode($this->pushes[$section]); + if (isset($pushes[$section])) { + $output .= implode($pushes[$section]); } return $output; @@ -169,13 +175,11 @@ public function yieldPushContent(string $section, string $default = ''): string /** * Flush all of the stacks. - * - * @return void */ public function flushStacks(): void { - $this->pushes = []; - $this->prepends = []; - $this->pushStack = []; + Context::set(static::PUSHES_CONTEXT_KEY, []); + Context::set(static::PREPENDS_CONTEXT_KEY, []); + Context::set(static::PUSH_STACK_CONTEXT_KEY, []); } } diff --git a/src/view/src/Concerns/ManagesTranslations.php b/src/view/src/Concerns/ManagesTranslations.php index 923a53307..beaa9b49a 100644 --- a/src/view/src/Concerns/ManagesTranslations.php +++ b/src/view/src/Concerns/ManagesTranslations.php @@ -4,37 +4,32 @@ namespace Hypervel\View\Concerns; +use Hypervel\Context\Context; + trait ManagesTranslations { /** - * The translation replacements for the translation being rendered. - * - * @var array + * Context key for translation replacements. */ - protected array $translationReplacements = []; + protected const TRANSLATION_REPLACEMENTS_CONTEXT_KEY = 'translation_replacements'; /** * Start a translation block. - * - * @param array $replacements - * @return void */ public function startTranslation(array $replacements = []): void { ob_start(); - $this->translationReplacements = $replacements; + Context::set(static::TRANSLATION_REPLACEMENTS_CONTEXT_KEY, $replacements); } /** * Render the current translation. - * - * @return string */ public function renderTranslation(): string { return $this->container->make('translator')->get( - trim(ob_get_clean()), $this->translationReplacements + trim(ob_get_clean()), Context::get(static::TRANSLATION_REPLACEMENTS_CONTEXT_KEY, []) ); } } diff --git a/src/view/src/DynamicComponent.php b/src/view/src/DynamicComponent.php index 74260307c..38ea11399 100644 --- a/src/view/src/DynamicComponent.php +++ b/src/view/src/DynamicComponent.php @@ -4,6 +4,7 @@ namespace Hypervel\View; +use Closure; use Hypervel\Container\Container; use Hypervel\Support\Collection; use Hypervel\Support\Str; @@ -13,30 +14,21 @@ class DynamicComponent extends Component { /** * The name of the component. - * - * @var string */ public string $component; /** * The component tag compiler instance. - * - * @var \Hypervel\View\Compilers\ComponentTagCompiler */ protected static ?ComponentTagCompiler $compiler = null; /** * The cached component classes. - * - * @var array */ protected static array $componentClasses = []; /** * Create a new component instance. - * - * @param string $component - * @return void */ public function __construct(string $component) { @@ -45,10 +37,8 @@ public function __construct(string $component) /** * Get the view / contents that represent the component. - * - * @return \Hypervel\Contracts\View\View|string */ - public function render(): \Closure + public function render(): Closure { $template = <<<'EOF' getAttributes()))->mapWithKeys(function ($value, $key) { return [Hypervel\Support\Str::camel(str_replace([':', '.'], ' ', $key)) => $value]; })->all(), EXTR_SKIP); ?> @@ -86,9 +76,6 @@ class_exists($class) ? '{{ $attributes }}' : '', /** * Compile the @props directive for the component. - * - * @param array $bindings - * @return string */ protected function compileProps(array $bindings): string { @@ -116,9 +103,6 @@ protected function compileBindings(array $bindings): string /** * Compile the slots for the component. - * - * @param array $slots - * @return string */ protected function compileSlots(array $slots): string { @@ -130,8 +114,6 @@ protected function compileSlots(array $slots): string /** * Get the class for the current component. - * - * @return string */ protected function classForComponent(): string { @@ -145,9 +127,6 @@ protected function classForComponent(): string /** * Get the names of the variables that should be bound to the component. - * - * @param string $class - * @return array */ protected function bindings(string $class): array { @@ -158,8 +137,6 @@ protected function bindings(string $class): array /** * Get an instance of the Blade tag compiler. - * - * @return \Hypervel\View\Compilers\ComponentTagCompiler */ protected function compiler(): ComponentTagCompiler { diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index 3dbe309dc..210e34c0b 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -21,7 +21,7 @@ class CompilerEngine extends PhpEngine /** * The context key for a stack of the compiled template path. */ - protected const COMPILED_PATH_CONTEXT_KEY = 'hypervel.view.compiler_engine.compiled_path'; + protected const COMPILED_PATH_CONTEXT_KEY = 'compiled_path'; /** * The Blade compiler instance. diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index 34cb3cbd3..1da1b9c19 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -4,11 +4,14 @@ namespace Hypervel\View; -use Hypervel\Contracts\Container\Container; -use Hypervel\Contracts\Events\Dispatcher; +use Closure; +use Hypervel\Container\Contracts\Container; +use Hypervel\Context\Context; +use Hypervel\Event\Contracts\Dispatcher; use Hypervel\Support\Contracts\Arrayable; use Hypervel\View\Contracts\Engine; use Hypervel\View\Contracts\Factory as FactoryContract; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Arr; use Hypervel\Support\Traits\Macroable; use Hypervel\View\Engines\EngineResolver; @@ -16,6 +19,21 @@ class Factory implements FactoryContract { + /** + * Data that should be available to all templates. + */ + protected const SHARE_CONTEXT_KEY = 'share'; + + /** + * The number of active rendering operations. + */ + protected const RENDER_COUNT_CONTEXT_KEY = 'render_count'; + + /** + * The "once" block IDs that have been rendered. + */ + protected const RENDERED_ONCE_CONTEXT_KEY = 'rendered_once'; + use Macroable, Concerns\ManagesComponents, Concerns\ManagesEvents, @@ -27,43 +45,26 @@ class Factory implements FactoryContract /** * The engine implementation. - * - * @var \Hypervel\View\Engines\EngineResolver */ protected EngineResolver $engines; /** * The view finder implementation. - * - * @var \Hypervel\View\ViewFinderInterface */ protected ViewFinderInterface $finder; /** * The event dispatcher instance. - * - * @var \Hypervel\Contracts\Events\Dispatcher */ protected Dispatcher $events; /** * The IoC container instance. - * - * @var \Hypervel\Contracts\Container\Container */ protected Container $container; - /** - * Data that should be available to all templates. - * - * @var array - */ - protected array $shared = []; - /** * The extension to engine bindings. - * - * @var array */ protected array $extensions = [ 'blade.php' => 'blade', @@ -74,46 +75,21 @@ class Factory implements FactoryContract /** * The view composer events. - * - * @var array */ protected array $composers = []; - /** - * The number of active rendering operations. - * - * @var int - */ - protected int $renderCount = 0; - - /** - * The "once" block IDs that have been rendered. - * - * @var array - */ - protected array $renderedOnce = []; - /** * The cached array of engines for paths. - * - * @var array */ protected array $pathEngineCache = []; /** * The cache of normalized names for views. - * - * @var array */ protected array $normalizedNameCache = []; /** * Create a new view factory instance. - * - * @param \Hypervel\View\Engines\EngineResolver $engines - * @param \Hypervel\View\ViewFinderInterface $finder - * @param \Hypervel\Contracts\Events\Dispatcher $events - * @return void */ public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events) { @@ -126,10 +102,6 @@ public function __construct(EngineResolver $engines, ViewFinderInterface $finder /** * Get the evaluated view contents for the given view. - * - * @param string $path - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData * @return \Hypervel\Contracts\View\View */ public function file(string $path, Arrayable|array $data = [], array $mergeData = []): View @@ -143,13 +115,8 @@ public function file(string $path, Arrayable|array $data = [], array $mergeData /** * Get the evaluated view contents for the given view. - * - * @param string $view - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData - * @return \Hypervel\Contracts\View\View */ - public function make(string $view, Arrayable|array $data = [], array $mergeData = []): View + public function make(string $view, Arrayable|array $data = [], array $mergeData = []): ViewContract { $path = $this->finder->find( $view = $this->normalizeName($view) @@ -168,14 +135,9 @@ public function make(string $view, Arrayable|array $data = [], array $mergeData /** * Get the first view that actually exists from the given list. * - * @param array $views - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData - * @return \Hypervel\Contracts\View\View - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - public function first(array $views, Arrayable|array $data = [], array $mergeData = []): View + public function first(array $views, Arrayable|array $data = [], array $mergeData = []): ViewContract { $view = Arr::first($views, function ($view) { return $this->exists($view); @@ -190,12 +152,6 @@ public function first(array $views, Arrayable|array $data = [], array $mergeData /** * Get the rendered content of the view based on a given condition. - * - * @param bool $condition - * @param string $view - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData - * @return string */ public function renderWhen(bool $condition, string $view, Arrayable|array $data = [], array $mergeData = []): string { @@ -208,12 +164,6 @@ public function renderWhen(bool $condition, string $view, Arrayable|array $data /** * Get the rendered content of the view based on the negation of a given condition. - * - * @param bool $condition - * @param string $view - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @param array $mergeData - * @return string */ public function renderUnless(bool $condition, string $view, Arrayable|array $data = [], array $mergeData = []): string { @@ -222,12 +172,6 @@ public function renderUnless(bool $condition, string $view, Arrayable|array $dat /** * 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(string $view, array $data, string $iterator, string $empty = 'raw|'): string { @@ -258,9 +202,6 @@ public function renderEach(string $view, array $data, string $iterator, string $ /** * Normalize a view name. - * - * @param string $name - * @return string */ protected function normalizeName(string $name): string { @@ -269,9 +210,6 @@ protected function normalizeName(string $name): string /** * Parse the given data into a raw array. - * - * @param mixed $data - * @return array */ protected function parseData(mixed $data): array { @@ -280,22 +218,14 @@ protected function parseData(mixed $data): array /** * Create a new view instance from the given arguments. - * - * @param string $view - * @param string $path - * @param \Hypervel\Contracts\Support\Arrayable|array $data - * @return \Hypervel\Contracts\View\View */ - protected function viewInstance(string $view, string $path, Arrayable|array $data): View + protected function viewInstance(string $view, string $path, Arrayable|array $data): ViewContract { return new View($this, $this->getEngineFromPath($path), $view, $path, $data); } /** * Determine if a given view exists. - * - * @param string $view - * @return bool */ public function exists(string $view): bool { @@ -311,10 +241,7 @@ public function exists(string $view): bool /** * Get the appropriate view engine for the given path. * - * @param string $path - * @return \Hypervel\Contracts\View\Engine - * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function getEngineFromPath(string $path): Engine { @@ -333,9 +260,6 @@ public function getEngineFromPath(string $path): Engine /** * Get the extension used by the view file. - * - * @param string $path - * @return string|null */ protected function getExtension(string $path): ?string { @@ -348,79 +272,83 @@ protected function getExtension(string $path): ?string /** * Add a piece of shared data to the environment. - * - * @param array|string $key - * @param mixed|null $value - * @return mixed */ public function share(array|string $key, mixed $value = null): mixed { $keys = is_array($key) ? $key : [$key => $value]; + $shared = $this->getShared(); + foreach ($keys as $key => $value) { - $this->shared[$key] = $value; + $shared[$key] = $value; } + $this->setShared($shared); + return $value; } /** * Increment the rendering counter. - * - * @return void */ public function incrementRender(): void { - $this->renderCount++; + Context::override(self::RENDER_COUNT_CONTEXT_KEY, function ($value) { + return ($value ?? 0) + 1; + }); } /** * Decrement the rendering counter. - * - * @return void */ public function decrementRender(): void { - $this->renderCount--; + Context::override(self::RENDER_COUNT_CONTEXT_KEY, function ($value) { + return ($value ?? 1) - 1; + }); + } + + /** + * Get the rendering counter. + */ + protected function getRenderCount(): int + { + return Context::get(self::RENDER_COUNT_CONTEXT_KEY, 0); } /** * Check if there are no active render operations. - * - * @return bool */ public function doneRendering(): bool { - return $this->renderCount == 0; + return Context::get(self::RENDER_COUNT_CONTEXT_KEY, 0) === 0; } /** * Determine if the given once token has been rendered. - * - * @param string $id - * @return bool */ public function hasRenderedOnce(string $id): bool { - return isset($this->renderedOnce[$id]); + $renderedOnce = Context::get(self::RENDERED_ONCE_CONTEXT_KEY, []); + + return isset($renderedOnce[$id]); } /** * Mark the given once token as having been rendered. - * - * @param string $id - * @return void */ public function markAsRenderedOnce(string $id): void { - $this->renderedOnce[$id] = true; + Context::override(self::RENDERED_ONCE_CONTEXT_KEY, function ($value) use ($id) { + $value ??= []; + $value[$id] = true; + + return $value; + }); } /** * Add a location to the array of view locations. - * - * @param string $location - * @return void */ public function addLocation(string $location): void { @@ -429,9 +357,6 @@ public function addLocation(string $location): void /** * Prepend a location to the array of view locations. - * - * @param string $location - * @return void */ public function prependLocation(string $location): void { @@ -440,10 +365,6 @@ public function prependLocation(string $location): void /** * Add a new namespace to the loader. - * - * @param string $namespace - * @param string|array $hints - * @return $this */ public function addNamespace(string $namespace, string|array $hints): static { @@ -454,10 +375,6 @@ public function addNamespace(string $namespace, string|array $hints): static /** * Prepend a new namespace to the loader. - * - * @param string $namespace - * @param string|array $hints - * @return $this */ public function prependNamespace(string $namespace, string|array $hints): static { @@ -468,10 +385,6 @@ public function prependNamespace(string $namespace, string|array $hints): static /** * Replace the namespace hints for the given namespace. - * - * @param string $namespace - * @param string|array $hints - * @return $this */ public function replaceNamespace(string $namespace, string|array $hints): static { @@ -482,13 +395,8 @@ public function replaceNamespace(string $namespace, string|array $hints): static /** * Register a valid view extension and its engine. - * - * @param string $extension - * @param string $engine - * @param \Closure|null $resolver - * @return void */ - public function addExtension(string $extension, string $engine, \Closure $resolver = null): void + public function addExtension(string $extension, string $engine, ?Closure $resolver = null): void { $this->finder->addExtension($extension); @@ -505,13 +413,11 @@ public function addExtension(string $extension, string $engine, \Closure $resolv /** * Flush all of the factory state like sections and stacks. - * - * @return void */ public function flushState(): void { - $this->renderCount = 0; - $this->renderedOnce = []; + Context::set(self::RENDER_COUNT_CONTEXT_KEY, 0); + Context::set(self::RENDERED_ONCE_CONTEXT_KEY, []); $this->flushSections(); $this->flushStacks(); @@ -521,8 +427,6 @@ public function flushState(): void /** * Flush all of the section contents if done rendering. - * - * @return void */ public function flushStateIfDoneRendering(): void { @@ -533,8 +437,6 @@ public function flushStateIfDoneRendering(): void /** * Get the extension to engine bindings. - * - * @return array */ public function getExtensions(): array { @@ -543,8 +445,6 @@ public function getExtensions(): array /** * Get the engine resolver instance. - * - * @return \Hypervel\View\Engines\EngineResolver */ public function getEngineResolver(): EngineResolver { @@ -553,8 +453,6 @@ public function getEngineResolver(): EngineResolver /** * Get the view finder instance. - * - * @return \Hypervel\View\ViewFinderInterface */ public function getFinder(): ViewFinderInterface { @@ -563,9 +461,6 @@ public function getFinder(): ViewFinderInterface /** * Set the view finder instance. - * - * @param \Hypervel\View\ViewFinderInterface $finder - * @return void */ public function setFinder(ViewFinderInterface $finder): void { @@ -574,8 +469,6 @@ public function setFinder(ViewFinderInterface $finder): void /** * Flush the cache of views located by the finder. - * - * @return void */ public function flushFinderCache(): void { @@ -584,8 +477,6 @@ public function flushFinderCache(): void /** * Get the event dispatcher instance. - * - * @return \Hypervel\Contracts\Events\Dispatcher */ public function getDispatcher(): Dispatcher { @@ -594,9 +485,6 @@ public function getDispatcher(): Dispatcher /** * Set the event dispatcher instance. - * - * @param \Hypervel\Contracts\Events\Dispatcher $events - * @return void */ public function setDispatcher(Dispatcher $events): void { @@ -605,8 +493,6 @@ public function setDispatcher(Dispatcher $events): void /** * Get the IoC container instance. - * - * @return \Hypervel\Contracts\Container\Container */ public function getContainer(): Container { @@ -615,9 +501,6 @@ public function getContainer(): Container /** * Set the IoC container instance. - * - * @param \Hypervel\Contracts\Container\Container $container - * @return void */ public function setContainer(Container $container): void { @@ -626,23 +509,25 @@ public function setContainer(Container $container): void /** * Get an item from the shared data. - * - * @param string $key - * @param mixed $default - * @return mixed */ public function shared(string $key, mixed $default = null): mixed { - return Arr::get($this->shared, $key, $default); + return Arr::get($this->getShared(), $key, $default); } /** * Get all of the shared data for the environment. - * - * @return array */ public function getShared(): array { - return $this->shared; + return Context::get(self::SHARE_CONTEXT_KEY, []); + } + + /** + * Set the shared data for the environment. + */ + public function setShared(array $data): void + { + Context::set(self::SHARE_CONTEXT_KEY, $data); } } diff --git a/src/view/src/InvokableComponentVariable.php b/src/view/src/InvokableComponentVariable.php index a2efd8849..c2fd96702 100644 --- a/src/view/src/InvokableComponentVariable.php +++ b/src/view/src/InvokableComponentVariable.php @@ -6,8 +6,9 @@ use ArrayIterator; use Closure; +use Hyperf\Collection\Enumerable; use Hypervel\Support\Contracts\DeferringDisplayableValue; -use Hypervel\Support\Enumerable; +use Hypervel\Support\Contracts\Htmlable; use IteratorAggregate; use Stringable; use Traversable; @@ -16,16 +17,11 @@ class InvokableComponentVariable implements DeferringDisplayableValue, IteratorA { /** * The callable instance to resolve the variable value. - * - * @var \Closure */ protected Closure $callable; /** * Create a new variable instance. - * - * @param \Closure $callable - * @return void */ public function __construct(Closure $callable) { @@ -34,18 +30,14 @@ public function __construct(Closure $callable) /** * Resolve the displayable value that the class is deferring. - * - * @return \Hypervel\Contracts\Support\Htmlable|string */ - public function resolveDisplayableValue(): mixed + public function resolveDisplayableValue(): Htmlable|string { return $this->__invoke(); } /** * Get an iterator instance for the variable. - * - * @return \ArrayIterator */ public function getIterator(): Traversable { @@ -56,9 +48,6 @@ public function getIterator(): Traversable /** * Dynamically proxy attribute access to the variable. - * - * @param string $key - * @return mixed */ public function __get(string $key): mixed { @@ -67,10 +56,6 @@ public function __get(string $key): mixed /** * Dynamically proxy method access to the variable. - * - * @param string $method - * @param array $parameters - * @return mixed */ public function __call(string $method, array $parameters): mixed { @@ -79,8 +64,6 @@ public function __call(string $method, array $parameters): mixed /** * Resolve the variable. - * - * @return mixed */ public function __invoke(): mixed { @@ -89,8 +72,6 @@ public function __invoke(): mixed /** * Resolve the variable as a string. - * - * @return string */ public function __toString(): string { diff --git a/src/view/src/Middleware/ShareErrorsFromSession.php b/src/view/src/Middleware/ShareErrorsFromSession.php index d0cbefc30..4aa099184 100644 --- a/src/view/src/Middleware/ShareErrorsFromSession.php +++ b/src/view/src/Middleware/ShareErrorsFromSession.php @@ -12,16 +12,11 @@ class ShareErrorsFromSession { /** * The view factory implementation. - * - * @var \Hypervel\Contracts\View\Factory */ protected ViewFactory $view; /** * Create a new error binder instance. - * - * @param \Hypervel\Contracts\View\Factory $view - * @return void */ public function __construct(ViewFactory $view) { @@ -30,10 +25,6 @@ public function __construct(ViewFactory $view) /** * Handle an incoming request. - * - * @param mixed $request - * @param \Closure $next - * @return mixed */ public function handle(mixed $request, Closure $next): mixed { diff --git a/src/view/src/View.php b/src/view/src/View.php index b598f9e5e..9bfc8140f 100755 --- a/src/view/src/View.php +++ b/src/view/src/View.php @@ -6,9 +6,9 @@ use ArrayAccess; use BadMethodCallException; +use Hyperf\Contract\MessageProvider; use Hypervel\Support\Contracts\Arrayable; use Hypervel\Support\Contracts\Htmlable; -use Hypervel\Support\Contracts\MessageProvider; use Hypervel\Support\Contracts\Renderable; use Hypervel\View\Contracts\Engine; use Hypervel\View\Contracts\View as ViewContract; @@ -28,8 +28,6 @@ class View implements ArrayAccess, Htmlable, Stringable, ViewContract /** * The view factory instance. - * - * @var \Hypervel\View\Factory */ protected Factory $factory; @@ -40,34 +38,21 @@ class View implements ArrayAccess, Htmlable, Stringable, ViewContract /** * The name of the view. - * - * @var string */ protected string $view; /** * The array of view data. - * - * @var array */ protected array $data; /** * The path to the view file. - * - * @var string */ protected string $path; /** * Create a new view instance. - * - * @param \Hypervel\View\Factory $factory - * @param \Hypervel\Contracts\View\Engine $engine - * @param string $view - * @param string $path - * @param mixed $data - * @return void */ public function __construct(Factory $factory, Engine $engine, string $view, string $path, mixed $data = []) { @@ -81,9 +66,6 @@ public function __construct(Factory $factory, Engine $engine, string $view, stri /** * Get the evaluated contents of a given fragment. - * - * @param string $fragment - * @return string */ public function fragment(string $fragment): string { @@ -94,9 +76,6 @@ public function fragment(string $fragment): string /** * Get the evaluated contents for a given array of fragments or return all fragments. - * - * @param array|null $fragments - * @return string */ public function fragments(?array $fragments = null): string { @@ -107,10 +86,6 @@ public function fragments(?array $fragments = null): string /** * 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(bool $boolean, string $fragment): string { @@ -123,10 +98,6 @@ public function fragmentIf(bool $boolean, string $fragment): string /** * Get the evaluated contents for a given array of fragments if the given condition is true. - * - * @param bool $boolean - * @param array|null $fragments - * @return string */ public function fragmentsIf(bool $boolean, ?array $fragments = null): string { @@ -139,8 +110,6 @@ public function fragmentsIf(bool $boolean, ?array $fragments = null): string /** * Get all fragments as a single string. - * - * @return string */ protected function allFragments(): string { @@ -150,10 +119,7 @@ protected function allFragments(): string /** * Get the string contents of the view. * - * @param callable|null $callback - * @return string - * - * @throws \Throwable + * @throws Throwable */ public function render(?callable $callback = null): string { @@ -177,8 +143,6 @@ public function render(?callable $callback = null): string /** * Get the contents of the view instance. - * - * @return string */ protected function renderContents(): string { @@ -201,8 +165,6 @@ protected function renderContents(): string /** * Get the evaluated contents of the view. - * - * @return string */ protected function getContents(): string { @@ -211,8 +173,6 @@ protected function getContents(): string /** * Get the data bound to the view instance. - * - * @return array */ public function gatherData(): array { @@ -230,23 +190,33 @@ public function gatherData(): array /** * Get the sections of the rendered view. * - * @return array + * This function is similar to render. We need to call `renderContents` function first. + * Because sections are only populated during the view rendering process. * - * @throws \Throwable + * @throws Throwable */ public function renderSections(): array { - return $this->render(function () { - return $this->factory->getSections(); - }); + try { + $this->renderContents(); + + $response = $this->factory->getSections(); + + // 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 $response; + } catch (Throwable $e) { + $this->factory->flushState(); + + throw $e; + } } /** * Add a piece of data to the view. - * - * @param string|array $key - * @param mixed $value - * @return $this */ public function with(string|array $key, mixed $value = null): static { @@ -261,11 +231,6 @@ public function with(string|array $key, mixed $value = null): static /** * Add a view instance to the view data. - * - * @param string $key - * @param string $view - * @param array $data - * @return $this */ public function nest(string $key, string $view, array $data = []): static { @@ -274,10 +239,6 @@ public function nest(string $key, string $view, array $data = []): static /** * Add validation errors to the view. - * - * @param \Hypervel\Contracts\Support\MessageProvider|array|string $provider - * @param string $bag - * @return $this */ public function withErrors(MessageProvider|array|string $provider, string $bag = 'default'): static { @@ -288,9 +249,6 @@ public function withErrors(MessageProvider|array|string $provider, string $bag = /** * Parse the given errors into an appropriate value. - * - * @param \Hypervel\Contracts\Support\MessageProvider|array|string $provider - * @return \Hypervel\Support\MessageBag */ protected function formatErrors(MessageProvider|array|string $provider): MessageBag { @@ -301,8 +259,6 @@ protected function formatErrors(MessageProvider|array|string $provider): Message /** * Get the name of the view. - * - * @return string */ public function name(): string { @@ -311,8 +267,6 @@ public function name(): string /** * Get the name of the view. - * - * @return string */ public function getName(): string { @@ -321,8 +275,6 @@ public function getName(): string /** * Get the array of view data. - * - * @return array */ public function getData(): array { @@ -331,8 +283,6 @@ public function getData(): array /** * Get the path to the view file. - * - * @return string */ public function getPath(): string { @@ -341,9 +291,6 @@ public function getPath(): string /** * Set the path to the view. - * - * @param string $path - * @return void */ public function setPath(string $path): void { @@ -352,8 +299,6 @@ public function setPath(string $path): void /** * Get the view factory instance. - * - * @return \Hypervel\View\Factory */ public function getFactory(): Factory { @@ -370,9 +315,6 @@ public function getEngine(): Engine /** * Determine if a piece of data is bound. - * - * @param string $key - * @return bool */ public function offsetExists(mixed $key): bool { @@ -381,9 +323,6 @@ public function offsetExists(mixed $key): bool /** * Get a piece of bound data to the view. - * - * @param string $key - * @return mixed */ public function offsetGet(mixed $key): mixed { @@ -392,10 +331,6 @@ public function offsetGet(mixed $key): mixed /** * Set a piece of data on the view. - * - * @param string $key - * @param mixed $value - * @return void */ public function offsetSet(mixed $key, mixed $value): void { @@ -404,9 +339,6 @@ public function offsetSet(mixed $key, mixed $value): void /** * Unset a piece of data from the view. - * - * @param string $key - * @return void */ public function offsetUnset(mixed $key): void { @@ -415,9 +347,6 @@ public function offsetUnset(mixed $key): void /** * Get a piece of data from the view. - * - * @param string $key - * @return mixed */ public function &__get(string $key): mixed { @@ -426,10 +355,6 @@ public function &__get(string $key): mixed /** * Set a piece of data on the view. - * - * @param string $key - * @param mixed $value - * @return void */ public function __set(string $key, mixed $value): void { @@ -438,9 +363,6 @@ public function __set(string $key, mixed $value): void /** * Check if a piece of data is bound to the view. - * - * @param string $key - * @return bool */ public function __isset(string $key): bool { @@ -449,9 +371,6 @@ public function __isset(string $key): bool /** * Remove a piece of bound data from the view. - * - * @param string $key - * @return void */ public function __unset(string $key): void { @@ -461,11 +380,9 @@ public function __unset(string $key): void /** * Dynamically bind parameters to the view. * - * @param string $method - * @param array $parameters - * @return \Hypervel\View\View + * @return static * - * @throws \BadMethodCallException + * @throws BadMethodCallException */ public function __call(string $method, array $parameters): mixed { @@ -485,7 +402,7 @@ public function __call(string $method, array $parameters): mixed /** * Get content as a string of HTML. * - * @return string + * @throws Throwable */ public function toHtml(): string { @@ -495,9 +412,7 @@ public function toHtml(): string /** * Get the string contents of the view. * - * @return string - * - * @throws \Throwable + * @throws Throwable */ public function __toString(): string { diff --git a/src/view/src/ViewException.php b/src/view/src/ViewException.php index dd943415e..42b61080d 100644 --- a/src/view/src/ViewException.php +++ b/src/view/src/ViewException.php @@ -7,15 +7,15 @@ use ErrorException; use Hypervel\Container\Container; use Hypervel\Support\Reflector; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class ViewException extends ErrorException { /** * Report the exception. - * - * @return bool|null */ - public function report(): bool|null + public function report(): ?bool { $exception = $this->getPrevious(); @@ -28,11 +28,8 @@ public function report(): bool|null /** * Render the exception into an HTTP response. - * - * @param \Hypervel\Http\Request $request - * @return \Hypervel\Http\Response|null */ - public function render($request): mixed + public function render(RequestInterface $request): ?ResponseInterface { $exception = $this->getPrevious(); diff --git a/src/view/src/ViewName.php b/src/view/src/ViewName.php index f31cf0b03..658cd2411 100644 --- a/src/view/src/ViewName.php +++ b/src/view/src/ViewName.php @@ -8,9 +8,6 @@ class ViewName { /** * Normalize the given view name. - * - * @param string $name - * @return string */ public static function normalize(string $name): string { diff --git a/tests/View/ComponentTest.php b/tests/View/ComponentTest.php index 0ba7e7ce9..988743f9d 100644 --- a/tests/View/ComponentTest.php +++ b/tests/View/ComponentTest.php @@ -3,15 +3,17 @@ namespace Hypervel\Tests\View; use Closure; +use Hyperf\Context\ApplicationContext; +use Hyperf\Di\Definition\DefinitionSource; +use Hyperf\Di\Exception\InvalidDefinitionException; use Hypervel\Config\Repository as Config; use Hypervel\Container\Container; -use Hypervel\Contracts\Container\BindingResolutionException; use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Contracts\Factory as FactoryContract; -use Hypervel\Support\Facades\Facade; use Hypervel\Support\HtmlString; use Hypervel\View\Component; use Hypervel\View\ComponentSlot; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\View\Factory; use Hypervel\View\View; use Mockery as m; @@ -27,27 +29,24 @@ protected function setUp(): void { parent::setUp(); - $this->config = m::mock(Config::class); - - $container = new Container; - $this->viewFactory = m::mock(Factory::class); + $this->config = m::mock(Config::class); - $container->instance('view', $this->viewFactory); - $container->alias('view', FactoryContract::class); - $container->instance('config', $this->config); + $container = new Container( + new DefinitionSource([ + 'config' => fn () => $this->config, + 'view' => fn () => $this->viewFactory, + FactoryContract::class => fn () => $this->viewFactory, + ]) + ); - Container::setInstance($container); - Facade::setFacadeApplication($container); + ApplicationContext::setContainer($container); } protected function tearDown(): void { m::close(); - Facade::clearResolvedInstances(); - Facade::setFacadeApplication(null); - Container::setInstance(null); Component::flushCache(); Component::forgetFactory(); @@ -89,7 +88,7 @@ public function __construct($title = 'World') $this->title = $title; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return function (array $data) { return "

Hello {$this->title}

"; @@ -142,8 +141,7 @@ public function testHtmlableGetReturned() public function testResolveWithUnresolvableDependency() { - $this->expectException(BindingResolutionException::class); - $this->expectExceptionMessage('Unresolvable dependency resolving'); + $this->expectException(InvalidDefinitionException::class); TestInlineViewComponentWhereRenderDependsOnProps::resolve([]); } @@ -162,14 +160,13 @@ public function __construct($a = null, $b = null) $this->content = $a.$b; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return $this->content; } }; $component = $component::resolve(['a' => 'a', 'b' => 'b']); - $component = $component::resolve(['b' => 'b', 'a' => 'a']); $this->assertSame('ab', $component->render()); } @@ -305,11 +302,7 @@ public function testFactoryGetsSharedBetweenComponents() $this->assertSame($this->viewFactory, $getFactory($regular)); - Container::getInstance()->instance('view', 'foo'); $this->assertSame($this->viewFactory, $getFactory($inline)); - - Component::forgetFactory(); - $this->assertNotSame($this->viewFactory, $getFactory($inline)); } public function testComponentSlotIsEmpty() @@ -379,7 +372,7 @@ public function __construct($title = 'foo') $this->title = $title; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'Hello {{ $title }}'; } @@ -394,7 +387,7 @@ public function __construct(FactoryContract $dependency) $this->dependency = $dependency; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return ''; } @@ -412,7 +405,7 @@ public function __construct(FactoryContract $dependency, $content) $this->dependency = $dependency; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return $this->content; } @@ -420,7 +413,7 @@ public function render() class TestInlineViewComponentWithoutDependencies extends Component { - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'alert'; } @@ -435,7 +428,7 @@ public function __construct($content) $this->content = $content; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return $this->content; } @@ -450,7 +443,7 @@ public function __construct($title = 'foo') $this->title = $title; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return view('alert'); } @@ -465,7 +458,7 @@ public function __construct($title = 'foo') $this->title = $title; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return $this->view('alert'); } @@ -480,7 +473,7 @@ public function __construct($title = 'foo') $this->title = $title; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'alert'; } @@ -495,7 +488,7 @@ public function __construct($title = 'foo') $this->title = $title; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return new HtmlString("

Hello {$this->title}

"); } diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php index 0da3f1271..ff8b1376c 100644 --- a/tests/View/ViewBladeCompilerTest.php +++ b/tests/View/ViewBladeCompilerTest.php @@ -57,7 +57,6 @@ public function testCompileCompilesFileAndReturnsContents() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); - $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); $compiler->compile('foo'); } @@ -68,7 +67,6 @@ public function testCompileCompilesFileAndReturnsContentsCreatingDirectory() $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(false); $files->shouldReceive('makeDirectory')->once()->with(__DIR__, 0777, true, true); - $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); $compiler->compile('foo'); } @@ -151,18 +149,7 @@ public function testDontIncludeEmptyPath() $files->shouldReceive('get')->once()->with('')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World'); - $compiler->setPath(''); - $compiler->compile(); - } - - public function testDontIncludeNullPath() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('get')->once()->with(null)->andReturn('Hello World'); - $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World'); - $compiler->setPath(null); - $compiler->compile(); + $compiler->compile(''); } public function testShouldStartFromStrictTypesDeclaration() diff --git a/tests/View/ViewCompilerEngineTest.php b/tests/View/ViewCompilerEngineTest.php index 6406ee508..dacc8054b 100755 --- a/tests/View/ViewCompilerEngineTest.php +++ b/tests/View/ViewCompilerEngineTest.php @@ -4,14 +4,14 @@ use ErrorException; use Exception; -use Hypervel\Contracts\Filesystem\FileNotFoundException; use Hypervel\Filesystem\Filesystem; +use Hypervel\Http\Exceptions\FileNotFoundException; +use Hypervel\HttpMessage\Exceptions\HttpException; use Hypervel\View\Compilers\CompilerInterface; use Hypervel\View\Engines\CompilerEngine; use Hypervel\View\ViewException; use Mockery as m; use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpKernel\Exception\HttpException; class ViewCompilerEngineTest extends TestCase { @@ -28,7 +28,8 @@ public function testViewsMayBeRecompiledAndRendered() $engine->getCompiler()->shouldReceive('compile')->once()->with(__DIR__.'/fixtures/foo.php'); $results = $engine->get(__DIR__.'/fixtures/foo.php'); - $this->assertSame('Hello World' . PHP_EOL, $results); + $this->assertSame('Hello World +', $results); } public function testViewsAreNotRecompiledIfTheyAreNotExpired() diff --git a/tests/View/ViewComponentTest.php b/tests/View/ViewComponentTest.php index cd3f7d500..b908be5a9 100644 --- a/tests/View/ViewComponentTest.php +++ b/tests/View/ViewComponentTest.php @@ -2,6 +2,9 @@ namespace Hypervel\Tests\View; +use Closure; +use Hypervel\Support\Contracts\Htmlable; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\View\Component; use Hypervel\View\ComponentAttributeBag; use Hypervel\View\ComponentSlot; @@ -25,9 +28,9 @@ public function testIgnoredMethodsAreNotExposedToViewData() { $component = new class extends Component { - protected $except = ['goodbye']; + protected array $except = ['goodbye']; - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'test'; } @@ -149,7 +152,7 @@ class TestViewComponent extends Component { public $votes = 10; - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'test'; } @@ -170,7 +173,7 @@ class TestSampleViewComponent extends Component private $privateCounter = 0; - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'test'; } @@ -202,7 +205,7 @@ private function privateHello() class TestExceptedViewComponent extends Component { - protected $except = ['hello', 'hello2', 'taylor']; + protected array $except = ['hello', 'hello2', 'taylor']; public $taylor = 'Otwell'; @@ -216,7 +219,7 @@ public function hello2() return $this->taylor = ''; } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'test'; } @@ -224,7 +227,7 @@ public function render() class TestHelloPropertyHelloMethodComponent extends Component { - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return 'test'; } @@ -251,7 +254,7 @@ public function __construct() $this->withAttributes(['class' => 'text-red-500']); } - public function render() + public function render(): ViewContract|Htmlable|Closure|string { return $this->attributes->get('id'); } diff --git a/tests/View/ViewEngineResolverTest.php b/tests/View/ViewEngineResolverTest.php index 2d66b29aa..c3856eb04 100755 --- a/tests/View/ViewEngineResolverTest.php +++ b/tests/View/ViewEngineResolverTest.php @@ -17,7 +17,7 @@ public function testResolversMayBeResolved() }); $result = $resolver->resolve('foo'); - $this->assertEquals($result, $resolver->resolve('foo')); + $this->assertTrue($result === $resolver->resolve('foo')); } public function testResolverThrowsExceptionOnUnknownEngine() diff --git a/tests/View/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php index 5b24d132a..4ecf94f12 100755 --- a/tests/View/ViewFactoryTest.php +++ b/tests/View/ViewFactoryTest.php @@ -4,14 +4,16 @@ use Closure; use ErrorException; -use Hypervel\Container\Container; -use Hypervel\Contracts\Events\Dispatcher as DispatcherContract; +use Hypervel\Container\Contracts\Container; +use Hypervel\Event\Contracts\Dispatcher as DispatcherContract; use Hypervel\View\Contracts\Engine; use Hypervel\View\Contracts\View as ViewContract; -use Hypervel\Events\Dispatcher; +use Hypervel\Event\EventDispatcher; +use Hypervel\Event\ListenerProvider; use Hypervel\Filesystem\Filesystem; use Hypervel\Support\HtmlString; use Hypervel\Support\LazyCollection; +use Hypervel\Tests\Foundation\Concerns\HasMockedApplication; use Hypervel\View\Compilers\CompilerInterface; use Hypervel\View\Engines\CompilerEngine; use Hypervel\View\Engines\EngineResolver; @@ -27,6 +29,8 @@ class ViewFactoryTest extends TestCase { + use HasMockedApplication; + protected function tearDown(): void { m::close(); @@ -40,8 +44,8 @@ public function testMakeCreatesNewViewInstanceWithProperPathAndEngine() $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.php'); $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class)); $factory->getFinder()->shouldReceive('addExtension')->once()->with('php'); - $factory->setDispatcher(new Dispatcher); - $factory->creator('view', function ($view) { + $factory->setDispatcher($this->createEventDispatcher()); + $factory->creator('view', function ($eventName, $view) { $_SERVER['__test.view'] = $view; }); $factory->addExtension('php', 'php'); @@ -53,6 +57,15 @@ public function testMakeCreatesNewViewInstanceWithProperPathAndEngine() unset($_SERVER['__test.view']); } + private function createEventDispatcher() + { + return new EventDispatcher( + new ListenerProvider(), + null, + $this->getApplication() + ); + } + public function testExistsPassesAndFailsViews() { $factory = $this->getFactory(); @@ -82,8 +95,8 @@ public function testFirstCreatesNewViewInstanceWithProperPath() $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class); $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class)); $factory->getFinder()->shouldReceive('addExtension')->once()->with('php'); - $factory->setDispatcher(new Dispatcher); - $factory->creator('view', function ($view) { + $factory->setDispatcher($this->createEventDispatcher()); + $factory->creator('view', function ($eventName, $view) { $_SERVER['__test.view'] = $view; }); $factory->addExtension('php', 'php'); @@ -103,17 +116,14 @@ public function testFirstThrowsInvalidArgumentExceptionIfNoneFound() $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->once()->with('view')->andThrow(InvalidArgumentException::class); $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class); - $factory->getEngineResolver()->shouldReceive('resolve')->with('php')->andReturn($engine = m::mock(Engine::class)); - $factory->getFinder()->shouldReceive('addExtension')->with('php'); - $factory->addExtension('php', 'php'); $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']); } public function testRenderEachCreatesViewForEachItemInArray() { $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs()); - $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock(stdClass::class)); - $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock(stdClass::class)); + $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock(ViewContract::class)); + $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock(ViewContract::class)); $mockView1->shouldReceive('render')->once()->andReturn('dayle'); $mockView2->shouldReceive('render')->once()->andReturn('rees'); @@ -125,7 +135,7 @@ public function testRenderEachCreatesViewForEachItemInArray() public function testEmptyViewsCanBeReturnedFromRenderEach() { $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs()); - $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock(stdClass::class)); + $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock(ViewContract::class)); $mockView->shouldReceive('render')->once()->andReturn('empty'); $this->assertSame('empty', $factory->renderEach('view', [], 'iterator', 'foo')); @@ -593,6 +603,7 @@ public function testBasicSectionDefault() $factory = $this->getFactory(); $factory->startSection('foo', 'hi'); $this->assertSame('hi', $factory->yieldContent('foo')); + $factory->flushSections(); } public function testBasicSectionDefaultIsEscaped() @@ -600,6 +611,7 @@ public function testBasicSectionDefaultIsEscaped() $factory = $this->getFactory(); $factory->startSection('foo', '

hi

'); $this->assertSame('<p>hi</p>', $factory->yieldContent('foo')); + $factory->flushSections(); } public function testBasicSectionDefaultViewIsNotEscapedTwice() @@ -609,12 +621,13 @@ public function testBasicSectionDefaultViewIsNotEscapedTwice() $view->shouldReceive('__toString')->once()->andReturn('

hi

<p>already escaped</p>'); $factory->startSection('foo', $view); $this->assertSame('

hi

<p>already escaped</p>', $factory->yieldContent('foo')); + $factory->flushSections(); } public function testSectionExtending() { - $placeholder = Factory::parentPlaceholder('foo'); $factory = $this->getFactory(); + $placeholder = $factory->getParentPlaceholder('foo'); $factory->startSection('foo'); echo 'hi '.$placeholder; $factory->stopSection(); @@ -622,12 +635,13 @@ public function testSectionExtending() echo 'there'; $factory->stopSection(); $this->assertSame('hi there', $factory->yieldContent('foo')); + $factory->flushSections(); } public function testSectionMultipleExtending() { - $placeholder = Factory::parentPlaceholder('foo'); $factory = $this->getFactory(); + $placeholder = $factory->getParentPlaceholder('foo'); $factory->startSection('foo'); echo 'hello '.$placeholder.' nice to see you '.$placeholder; $factory->stopSection(); @@ -638,6 +652,7 @@ public function testSectionMultipleExtending() echo 'friend'; $factory->stopSection(); $this->assertSame('hello my friend nice to see you my friend', $factory->yieldContent('foo')); + $factory->flushSections(); } public function testComponentHandling() @@ -703,9 +718,10 @@ public function testComponentHandlingUsingHtmlable() public function testTranslation() { - $container = new Container; - $container->instance('translator', $translator = m::mock(stdClass::class)); + $translator = m::mock(stdClass::class); $translator->shouldReceive('get')->with('Foo', ['name' => 'taylor'])->andReturn('Bar'); + $container = m::mock(Container::class); + $container->shouldReceive('make')->with('translator')->andReturn($translator); $factory = $this->getFactory(); $factory->setContainer($container); $factory->startTranslation(['name' => 'taylor']); @@ -722,6 +738,7 @@ public function testSingleStackPush() echo 'hi'; $factory->stopPush(); $this->assertSame('hi', $factory->yieldPushContent('foo')); + $factory->flushStacks(); } public function testMultipleStackPush() @@ -734,6 +751,7 @@ public function testMultipleStackPush() echo ', Hello!'; $factory->stopPush(); $this->assertSame('hi, Hello!', $factory->yieldPushContent('foo')); + $factory->flushStacks(); } public function testSingleStackPrepend() @@ -743,6 +761,7 @@ public function testSingleStackPrepend() echo 'hi'; $factory->stopPrepend(); $this->assertSame('hi', $factory->yieldPushContent('foo')); + $factory->flushStacks(); } public function testMultipleStackPrepend() @@ -755,6 +774,7 @@ public function testMultipleStackPrepend() echo 'hi'; $factory->stopPrepend(); $this->assertSame('hi, Hello!', $factory->yieldPushContent('foo')); + $factory->flushStacks(); } public function testSessionAppending() @@ -767,6 +787,7 @@ public function testSessionAppending() echo 'there'; $factory->appendSection(); $this->assertSame('hithere', $factory->yieldContent('foo')); + $factory->flushSections(); } public function testYieldSectionStopsAndYields() @@ -775,6 +796,7 @@ public function testYieldSectionStopsAndYields() $factory->startSection('foo'); echo 'hi'; $this->assertSame('hi', $factory->yieldSection()); + $factory->flushSections(); } public function testInjectStartsSectionWithContent() @@ -782,6 +804,7 @@ public function testInjectStartsSectionWithContent() $factory = $this->getFactory(); $factory->inject('foo', 'hi'); $this->assertSame('hi', $factory->yieldContent('foo')); + $factory->flushSections(); } public function testEmptyStringIsReturnedForNonSections() @@ -950,6 +973,8 @@ public function testAddingLoops() $factory->popLoop(); $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + + $factory->popLoop(); } public function testAddingLoopDoesNotCloseGenerator() @@ -971,28 +996,8 @@ public function generate() foreach ($data as $chunk) { $this->assertEquals(['a', 'b'], $chunk); } - } - - public function testAddingUncountableLoop() - { - $factory = $this->getFactory(); - - $factory->addLoop(''); - - $expectedLoop = [ - 'iteration' => 0, - 'index' => 0, - 'remaining' => null, - 'count' => null, - 'first' => true, - 'last' => null, - 'odd' => false, - 'even' => true, - 'depth' => 1, - 'parent' => null, - ]; - $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + $factory->popLoop(); } public function testAddingLazyCollection() @@ -1017,6 +1022,8 @@ public function testAddingLazyCollection() ]; $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + + $factory->popLoop(); } public function testIncrementingLoopIndices() @@ -1040,6 +1047,8 @@ public function testIncrementingLoopIndices() $this->assertEquals(2, $factory->getLoopStack()[0]['remaining']); $this->assertFalse($factory->getLoopStack()[0]['odd']); $this->assertTrue($factory->getLoopStack()[0]['even']); + + $factory->popLoop(); } public function testReachingEndOfLoop() @@ -1055,23 +1064,6 @@ public function testReachingEndOfLoop() $this->assertTrue($factory->getLoopStack()[0]['last']); } - public function testIncrementingLoopIndicesOfUncountable() - { - $factory = $this->getFactory(); - - $factory->addLoop(''); - - $factory->incrementLoopIndices(); - - $factory->incrementLoopIndices(); - - $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']); - $this->assertEquals(1, $factory->getLoopStack()[0]['index']); - $this->assertFalse($factory->getLoopStack()[0]['first']); - $this->assertNull($factory->getLoopStack()[0]['remaining']); - $this->assertNull($factory->getLoopStack()[0]['last']); - } - public function testMacro() { $factory = $this->getFactory(); diff --git a/tests/View/ViewTest.php b/tests/View/ViewTest.php index e7bd2147b..587eaeee9 100755 --- a/tests/View/ViewTest.php +++ b/tests/View/ViewTest.php @@ -77,15 +77,19 @@ public function testRenderHandlingCallbackReturnValues() public function testRenderSectionsReturnsEnvironmentSections() { - $view = m::mock(View::class.'[render]', [ - m::mock(Factory::class), - m::mock(Engine::class), - 'view', - 'path', - [], - ]); - - $view->shouldReceive('render')->with(m::type(Closure::class))->once()->andReturn($sections = ['foo' => 'bar']); + $view = $this->getMockBuilder(View::class) + ->setConstructorArgs([ + $factory = m::mock(Factory::class), + m::mock(Engine::class), + 'view', + 'path', + [], + ]) + ->onlyMethods(['renderContents']) + ->getMock(); + + $factory->shouldReceive('getSections')->with()->once()->andReturn($sections = ['foo' => 'bar']); + $factory->shouldReceive('flushStateIfDoneRendering')->with()->once(); $this->assertEquals($sections, $view->renderSections()); } diff --git a/tests/View/fixtures/http-exception.php b/tests/View/fixtures/http-exception.php index 4e239b49a..62b231004 100644 --- a/tests/View/fixtures/http-exception.php +++ b/tests/View/fixtures/http-exception.php @@ -1,5 +1,5 @@ Date: Fri, 28 Nov 2025 17:11:09 +0800 Subject: [PATCH 17/21] feat: use constructor property promotion --- src/view/src/AnonymousComponent.php | 18 +++------- src/view/src/AppendableAttributeValue.php | 11 ++---- .../src/Compilers/ComponentTagCompiler.php | 20 +++-------- src/view/src/ComponentSlot.php | 13 +++---- src/view/src/DynamicComponent.php | 11 ++---- src/view/src/Engines/CompilerEngine.php | 13 +++---- src/view/src/Engines/FileEngine.php | 11 ++---- src/view/src/Engines/PhpEngine.php | 11 ++---- src/view/src/Factory.php | 26 +++----------- src/view/src/FileViewFinder.php | 13 +++---- src/view/src/InvokableComponentVariable.php | 11 ++---- .../src/Middleware/ShareErrorsFromSession.php | 11 ++---- src/view/src/View.php | 34 ++++--------------- 13 files changed, 52 insertions(+), 151 deletions(-) diff --git a/src/view/src/AnonymousComponent.php b/src/view/src/AnonymousComponent.php index 5e42e358d..0d06d91f5 100644 --- a/src/view/src/AnonymousComponent.php +++ b/src/view/src/AnonymousComponent.php @@ -6,23 +6,13 @@ class AnonymousComponent extends Component { - /** - * The component view. - */ - protected string $view; - - /** - * The component data. - */ - protected array $data = []; - /** * Create a new anonymous component instance. */ - public function __construct(string $view, array $data) - { - $this->view = $view; - $this->data = $data; + public function __construct( + protected string $view, + protected array $data = [] + ) { } /** diff --git a/src/view/src/AppendableAttributeValue.php b/src/view/src/AppendableAttributeValue.php index 0d25db7ab..ff1c2471b 100644 --- a/src/view/src/AppendableAttributeValue.php +++ b/src/view/src/AppendableAttributeValue.php @@ -8,17 +8,12 @@ class AppendableAttributeValue implements Stringable { - /** - * The attribute value. - */ - public mixed $value; - /** * Create a new appendable attribute value. */ - public function __construct(mixed $value) - { - $this->value = $value; + public function __construct( + public mixed $value + ) { } /** diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php index 6897b2ee1..585f8b479 100644 --- a/src/view/src/Compilers/ComponentTagCompiler.php +++ b/src/view/src/Compilers/ComponentTagCompiler.php @@ -29,24 +29,14 @@ class ComponentTagCompiler */ protected BladeCompiler $blade; - /** - * The component class aliases. - */ - protected array $aliases = []; - - /** - * The component class namespaces. - */ - protected array $namespaces = []; - /** * Create a new component tag compiler. */ - public function __construct(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null) - { - $this->aliases = $aliases; - $this->namespaces = $namespaces; - + public function __construct( + protected array $aliases = [], + protected array $namespaces = [], + ?BladeCompiler $blade = null + ) { $this->blade = $blade ?: new BladeCompiler(new Filesystem, sys_get_temp_dir()); } diff --git a/src/view/src/ComponentSlot.php b/src/view/src/ComponentSlot.php index ba75bd405..1870042e4 100644 --- a/src/view/src/ComponentSlot.php +++ b/src/view/src/ComponentSlot.php @@ -15,18 +15,13 @@ class ComponentSlot implements Htmlable, Stringable */ public ComponentAttributeBag $attributes; - /** - * The slot contents. - */ - protected string $contents; - /** * Create a new slot instance. */ - public function __construct(string $contents = '', array $attributes = []) - { - $this->contents = $contents; - + public function __construct( + protected string $contents = '', + array $attributes = [] + ) { $this->withAttributes($attributes); } diff --git a/src/view/src/DynamicComponent.php b/src/view/src/DynamicComponent.php index 38ea11399..08247f7c9 100644 --- a/src/view/src/DynamicComponent.php +++ b/src/view/src/DynamicComponent.php @@ -12,11 +12,6 @@ class DynamicComponent extends Component { - /** - * The name of the component. - */ - public string $component; - /** * The component tag compiler instance. */ @@ -30,9 +25,9 @@ class DynamicComponent extends Component /** * Create a new component instance. */ - public function __construct(string $component) - { - $this->component = $component; + public function __construct( + public string $component + ) { } /** diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index 210e34c0b..98af1d842 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -23,11 +23,6 @@ class CompilerEngine extends PhpEngine */ protected const COMPILED_PATH_CONTEXT_KEY = 'compiled_path'; - /** - * The Blade compiler instance. - */ - protected CompilerInterface $compiler; - /** * The view paths that were compiled or are not expired, keyed by the path. * @@ -38,11 +33,11 @@ class CompilerEngine extends PhpEngine /** * Create a new compiler engine instance. */ - public function __construct(CompilerInterface $compiler, ?Filesystem $files = null) - { + public function __construct( + protected CompilerInterface $compiler, + ?Filesystem $files = null + ) { parent::__construct($files ?: new Filesystem); - - $this->compiler = $compiler; } /** diff --git a/src/view/src/Engines/FileEngine.php b/src/view/src/Engines/FileEngine.php index e53824739..b3c20133c 100644 --- a/src/view/src/Engines/FileEngine.php +++ b/src/view/src/Engines/FileEngine.php @@ -9,17 +9,12 @@ class FileEngine implements Engine { - /** - * The filesystem instance. - */ - protected Filesystem $files; - /** * Create a new file engine instance. */ - public function __construct(Filesystem $files) - { - $this->files = $files; + public function __construct( + protected Filesystem $files + ) { } /** diff --git a/src/view/src/Engines/PhpEngine.php b/src/view/src/Engines/PhpEngine.php index bd97804f9..01d9b0ad6 100755 --- a/src/view/src/Engines/PhpEngine.php +++ b/src/view/src/Engines/PhpEngine.php @@ -10,17 +10,12 @@ class PhpEngine implements Engine { - /** - * The filesystem instance. - */ - protected Filesystem $files; - /** * Create a new file engine instance. */ - public function __construct(Filesystem $files) - { - $this->files = $files; + public function __construct( + protected Filesystem $files + ) { } /** diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index 1da1b9c19..3148975bc 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -43,21 +43,6 @@ class Factory implements FactoryContract Concerns\ManagesStacks, Concerns\ManagesTranslations; - /** - * The engine implementation. - */ - protected EngineResolver $engines; - - /** - * The view finder implementation. - */ - protected ViewFinderInterface $finder; - - /** - * The event dispatcher instance. - */ - protected Dispatcher $events; - /** * The IoC container instance. */ @@ -91,12 +76,11 @@ class Factory implements FactoryContract /** * Create a new view factory instance. */ - public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events) - { - $this->finder = $finder; - $this->events = $events; - $this->engines = $engines; - + public function __construct( + protected EngineResolver $engines, + protected ViewFinderInterface $finder, + protected Dispatcher $events + ) { $this->share('__env', $this); } diff --git a/src/view/src/FileViewFinder.php b/src/view/src/FileViewFinder.php index 1143e2a1d..6493bc441 100755 --- a/src/view/src/FileViewFinder.php +++ b/src/view/src/FileViewFinder.php @@ -9,11 +9,6 @@ class FileViewFinder implements ViewFinderInterface { - /** - * The filesystem instance. - */ - protected Filesystem $files; - /** * The array of active view paths. */ @@ -39,9 +34,11 @@ class FileViewFinder implements ViewFinderInterface /** * Create a new file view loader instance. */ - public function __construct(Filesystem $files, array $paths, ?array $extensions = null) - { - $this->files = $files; + public function __construct( + protected Filesystem $files, + array $paths, + ?array $extensions = null + ) { $this->paths = array_map([$this, 'resolvePath'], $paths); if (isset($extensions)) { diff --git a/src/view/src/InvokableComponentVariable.php b/src/view/src/InvokableComponentVariable.php index c2fd96702..e1557c451 100644 --- a/src/view/src/InvokableComponentVariable.php +++ b/src/view/src/InvokableComponentVariable.php @@ -15,17 +15,12 @@ class InvokableComponentVariable implements DeferringDisplayableValue, IteratorAggregate, Stringable { - /** - * The callable instance to resolve the variable value. - */ - protected Closure $callable; - /** * Create a new variable instance. */ - public function __construct(Closure $callable) - { - $this->callable = $callable; + public function __construct( + protected Closure $callable + ) { } /** diff --git a/src/view/src/Middleware/ShareErrorsFromSession.php b/src/view/src/Middleware/ShareErrorsFromSession.php index 4aa099184..583f5bec8 100644 --- a/src/view/src/Middleware/ShareErrorsFromSession.php +++ b/src/view/src/Middleware/ShareErrorsFromSession.php @@ -10,17 +10,12 @@ class ShareErrorsFromSession { - /** - * The view factory implementation. - */ - protected ViewFactory $view; - /** * Create a new error binder instance. */ - public function __construct(ViewFactory $view) - { - $this->view = $view; + public function __construct( + protected ViewFactory $view + ) { } /** diff --git a/src/view/src/View.php b/src/view/src/View.php index 9bfc8140f..727790860 100755 --- a/src/view/src/View.php +++ b/src/view/src/View.php @@ -26,41 +26,21 @@ class View implements ArrayAccess, Htmlable, Stringable, ViewContract __call as macroCall; } - /** - * The view factory instance. - */ - protected Factory $factory; - - /** - * The engine implementation. - */ - protected Engine $engine; - - /** - * The name of the view. - */ - protected string $view; - /** * The array of view data. */ protected array $data; - /** - * The path to the view file. - */ - protected string $path; - /** * Create a new view instance. */ - public function __construct(Factory $factory, Engine $engine, string $view, string $path, mixed $data = []) - { - $this->view = $view; - $this->path = $path; - $this->engine = $engine; - $this->factory = $factory; - + public function __construct( + protected Factory $factory, + protected Engine $engine, + protected string $view, + protected string $path, + mixed $data = [] + ) { $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; } From 5a8143337fd29ae7016681ff30138267b85d5f76 Mon Sep 17 00:00:00 2001 From: bluehaha Date: Wed, 17 Dec 2025 17:36:04 +0800 Subject: [PATCH 18/21] feat: migrate Laravel view --- composer.json | 4 +- .../src/Generator/ComponentCommand.php | 2 +- .../src/Generator/stubs/view-component.stub | 6 +- src/foundation/src/helpers.php | 2 +- src/session/src/Store.php | 4 +- src/support/src/Facades/View.php | 68 +++++++----- src/support/src/MessageBag.php | 3 +- src/support/src/ViewErrorBag.php | 102 ++++++++++++++++- src/view/composer.json | 40 +++---- .../Compilers/Concerns/CompilesLayouts.php | 2 +- src/view/src/Concerns/ManagesLoops.php | 7 +- src/view/src/Engines/CompilerEngine.php | 23 +--- src/view/src/Factory.php | 32 ++---- .../src/Middleware/ShareErrorsFromSession.php | 6 +- src/view/src/ViewException.php | 2 + src/view/src/ViewServiceProvider.php | 104 ++++++++++-------- src/view/src/composer.json | 41 ------- tests/View/ViewCompilerEngineTest.php | 4 - tests/View/ViewTest.php | 1 - 19 files changed, 249 insertions(+), 204 deletions(-) delete mode 100644 src/view/src/composer.json diff --git a/composer.json b/composer.json index b2451be40..f6a56df18 100644 --- a/composer.json +++ b/composer.json @@ -205,7 +205,6 @@ "hyperf/devtool": "~3.1.0", "hyperf/redis": "~3.1.0", "hyperf/testing": "~3.1.0", - "hyperf/view-engine": "~3.1.0", "league/flysystem": "^3.0", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-google-cloud-storage": "^3.0", @@ -261,7 +260,8 @@ "providers": [ "Hypervel\\Notifications\\NotificationServiceProvider", "Hypervel\\Telescope\\TelescopeServiceProvider", - "Hypervel\\Sentry\\SentryServiceProvider" + "Hypervel\\Sentry\\SentryServiceProvider", + "Hypervel\\View\\ViewServiceProvider" ] }, "branch-alias": { diff --git a/src/devtool/src/Generator/ComponentCommand.php b/src/devtool/src/Generator/ComponentCommand.php index c92e5fd16..3f41bf286 100644 --- a/src/devtool/src/Generator/ComponentCommand.php +++ b/src/devtool/src/Generator/ComponentCommand.php @@ -27,7 +27,7 @@ protected function getStub(): string protected function getDefaultNamespace(): string { - return $this->getConfig()['namespace'] ?? 'App\View\Component'; + return $this->getConfig()['namespace'] ?? 'App\View\Components'; } protected function buildClass(string $name): string diff --git a/src/devtool/src/Generator/stubs/view-component.stub b/src/devtool/src/Generator/stubs/view-component.stub index 99be65e94..b4eb26d71 100644 --- a/src/devtool/src/Generator/stubs/view-component.stub +++ b/src/devtool/src/Generator/stubs/view-component.stub @@ -5,8 +5,8 @@ declare(strict_types=1); namespace %NAMESPACE%; use Closure; -use Hyperf\ViewEngine\Component\Component; -use Hyperf\ViewEngine\Contract\ViewInterface; +use Hypervel\View\Component; +use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Facades\View; class %CLASS% extends Component @@ -22,7 +22,7 @@ class %CLASS% extends Component /** * Get the view / contents that represent the component. */ - public function render(): ViewInterface|Closure|string + public function render(): ViewContract|Closure|string { return %VIEW%; } diff --git a/src/foundation/src/helpers.php b/src/foundation/src/helpers.php index 0f0f2eea9..698066f4a 100644 --- a/src/foundation/src/helpers.php +++ b/src/foundation/src/helpers.php @@ -734,7 +734,7 @@ function __(?string $key = null, array $replace = [], ?string $locale = null): a * @param null|string $view * @param array $mergeData */ - function view($view = null, array|Arrayable $data = [], $mergeData = []): ViewContract + function view($view = null, array|Arrayable $data = [], $mergeData = []): FactoryContract|ViewContract { $factory = app(FactoryContract::class); diff --git a/src/session/src/Store.php b/src/session/src/Store.php index 10c7107e7..bc77c63b6 100644 --- a/src/session/src/Store.php +++ b/src/session/src/Store.php @@ -10,9 +10,9 @@ use Hyperf\Context\Context; use Hyperf\Macroable\Macroable; use Hyperf\Stringable\Str; -use Hyperf\Support\MessageBag; -use Hyperf\ViewEngine\ViewErrorBag; use Hypervel\Session\Contracts\Session; +use Hypervel\Support\MessageBag; +use Hypervel\Support\ViewErrorBag; use SessionHandlerInterface; use stdClass; diff --git a/src/support/src/Facades/View.php b/src/support/src/Facades/View.php index bbe8ea9df..ee79183f4 100644 --- a/src/support/src/Facades/View.php +++ b/src/support/src/Facades/View.php @@ -4,70 +4,78 @@ namespace Hypervel\Support\Facades; -use Hyperf\ViewEngine\Contract\FactoryInterface; +use Hypervel\View\Contracts\Factory as FactoryContract; /** - * @method static \Hyperf\ViewEngine\Contract\ViewInterface file(string $path, array|\Hyperf\Contract\Arrayable $data = [], array $mergeData = []) - * @method static \Hyperf\ViewEngine\Contract\ViewInterface make(string $view, array|\Hyperf\Contract\Arrayable $data = [], array $mergeData = []) - * @method static \Hyperf\ViewEngine\Contract\ViewInterface first(array $views, \Hyperf\Contract\Arrayable|array $data = [], array $mergeData = []) - * @method static string renderWhen(bool $condition, string $view, \Hyperf\Contract\Arrayable|array $data = [], array $mergeData = []) - * @method static string renderUnless(bool $condition, string $view, \Hyperf\Contract\Arrayable|array $data = [], array $mergeData = []) + * @method static \Hypervel\View\Contracts\View file(string $path, \Hypervel\Support\Contracts\Arrayable|array $data = [], array $mergeData = []) + * @method static \Hypervel\View\Contracts\View make(string $view, \Hypervel\Support\Contracts\Arrayable|array $data = [], array $mergeData = []) + * @method static \Hypervel\View\Contracts\View first(array $views, \Hypervel\Support\Contracts\Arrayable|array $data = [], array $mergeData = []) + * @method static string renderWhen(bool $condition, string $view, \Hypervel\Support\Contracts\Arrayable|array $data = [], array $mergeData = []) + * @method static string renderUnless(bool $condition, string $view, \Hypervel\Support\Contracts\Arrayable|array $data = [], array $mergeData = []) * @method static string renderEach(string $view, array $data, string $iterator, string $empty = 'raw|') * @method static bool exists(string $view) - * @method static \Hyperf\ViewEngine\Contract\EngineInterface getEngineFromPath(string $path) - * @method static mixed share(array|string $key, null|mixed $value = null) + * @method static \Hypervel\View\Contracts\Engine getEngineFromPath(string $path) + * @method static mixed share(array|string $key, mixed|null $value = null) * @method static void incrementRender() * @method static void decrementRender() * @method static bool doneRendering() * @method static bool hasRenderedOnce(string $id) * @method static void markAsRenderedOnce(string $id) * @method static void addLocation(string $location) - * @method static \Hyperf\ViewEngine\Factory addNamespace(string $namespace, array|string $hints) - * @method static \Hyperf\ViewEngine\Factory prependNamespace(string $namespace, array|string $hints) - * @method static \Hyperf\ViewEngine\Factory replaceNamespace(string $namespace, array|string $hints) + * @method static void prependLocation(string $location) + * @method static \Hypervel\View\Factory addNamespace(string $namespace, string|array $hints) + * @method static \Hypervel\View\Factory prependNamespace(string $namespace, string|array $hints) + * @method static \Hypervel\View\Factory replaceNamespace(string $namespace, string|array $hints) * @method static void addExtension(string $extension, string $engine, \Closure|null $resolver = null) * @method static void flushState() * @method static void flushStateIfDoneRendering() * @method static array getExtensions() - * @method static \Hyperf\ViewEngine\Contract\EngineResolverInterface getEngineResolver() - * @method static \Hyperf\ViewEngine\Contract\FinderInterface getFinder() - * @method static void setFinder(\Hyperf\ViewEngine\Contract\FinderInterface $finder) + * @method static \Hypervel\View\Engines\EngineResolver getEngineResolver() + * @method static \Hypervel\View\ViewFinderInterface getFinder() + * @method static void setFinder(\Hypervel\View\ViewFinderInterface $finder) * @method static void flushFinderCache() - * @method static \Psr\EventDispatcher\EventDispatcherInterface getDispatcher() - * @method static void setDispatcher(\Psr\EventDispatcher\EventDispatcherInterface $events) - * @method static \Psr\Container\ContainerInterface getContainer() - * @method static void setContainer(\Psr\Container\ContainerInterface $container) + * @method static \Hypervel\Event\Contracts\Dispatcher getDispatcher() + * @method static void setDispatcher(\Hypervel\Event\Contracts\Dispatcher $events) + * @method static \Hypervel\Container\Contracts\Container getContainer() + * @method static void setContainer(\Hypervel\Container\Contracts\Container $container) * @method static mixed shared(string $key, mixed $default = null) * @method static array getShared() - * @method static void macro(string $name, callable|object $macro) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) - * @method static void startComponent(\Closure|\Hyperf\ViewEngine\Contract\Htmlable|string|\Hyperf\ViewEngine\View $view, array $data = []) + * @method static void flushMacros() + * @method static void startComponent(\Hypervel\View\Contracts\View|\Hypervel\Support\Contracts\Htmlable|\Closure|string $view, array $data = []) * @method static void startComponentFirst(array $names, array $data = []) * @method static string renderComponent() - * @method static void slot(string $name, null|string $content = null) + * @method static mixed|null getConsumableComponentData(string $key, mixed $default = null) + * @method static void slot(string $name, string|null $content = null, array $attributes = []) * @method static void endSlot() * @method static array creator(array|string $views, \Closure|string $callback) * @method static array composers(array $composers) * @method static array composer(array|string $views, \Closure|string $callback) - * @method static void callComposer(\Hyperf\ViewEngine\Contract\ViewInterface $view) - * @method static void callCreator(\Hyperf\ViewEngine\Contract\ViewInterface $view) - * @method static void startSection(string $section, null|string|\Hyperf\ViewEngine\Contract\ViewInterface $content = null) + * @method static void callComposer(\Hypervel\View\Contracts\View $view) + * @method static void callCreator(\Hypervel\View\Contracts\View $view) + * @method static void startFragment(string $fragment) + * @method static string stopFragment() + * @method static mixed getFragment(string $name, string|null $default = null) + * @method static array getFragments() + * @method static void flushFragments() + * @method static void startSection(string $section, string|null $content = null) * @method static void inject(string $section, string $content) * @method static string yieldSection() * @method static string stopSection(bool $overwrite = false) * @method static string appendSection() - * @method static string yieldContent(string $section, \Hyperf\ViewEngine\Contract\ViewInterface|string $default = '') + * @method static string yieldContent(string $section, string $default = '') * @method static string parentPlaceholder(string $section = '') * @method static bool hasSection(string $name) * @method static bool sectionMissing(string $name) - * @method static mixed getSection(string $name, null|string $default = null) + * @method static mixed getSection(string $name, string|null $default = null) * @method static array getSections() * @method static void flushSections() - * @method static void addLoop(null|array|\Countable $data) + * @method static void addLoop(\Countable|array $data) * @method static void incrementLoopIndices() * @method static void popLoop() - * @method static null|\stdClass|void getLastLoop() + * @method static \stdClass|null getLastLoop() * @method static array getLoopStack() * @method static void startPush(string $section, string $content = '') * @method static string stopPush() @@ -78,12 +86,12 @@ * @method static void startTranslation(array $replacements = []) * @method static string renderTranslation() * - * @see \Hyperf\ViewEngine\Factory + * @see \Hypervel\View\Factory */ class View extends Facade { protected static function getFacadeAccessor() { - return FactoryInterface::class; + return FactoryContract::class; } } diff --git a/src/support/src/MessageBag.php b/src/support/src/MessageBag.php index 6654b4ef9..8d9afe8f9 100644 --- a/src/support/src/MessageBag.php +++ b/src/support/src/MessageBag.php @@ -5,7 +5,8 @@ namespace Hypervel\Support; use Hyperf\Support\MessageBag as HyperfMessageBag; +use Hypervel\Support\Contracts\MessageBag as ContractsMessageBag; -class MessageBag extends HyperfMessageBag +class MessageBag extends HyperfMessageBag implements ContractsMessageBag { } diff --git a/src/support/src/ViewErrorBag.php b/src/support/src/ViewErrorBag.php index 557a96c1f..a69e74a94 100644 --- a/src/support/src/ViewErrorBag.php +++ b/src/support/src/ViewErrorBag.php @@ -4,8 +4,106 @@ namespace Hypervel\Support; -use Hyperf\ViewEngine\ViewErrorBag as HyperfViewErrorBag; +use Countable; +use Hypervel\Support\Contracts\MessageBag as MessageBagContract; +use Stringable; -class ViewErrorBag extends HyperfViewErrorBag +/** + * @mixin MessageBagContract + */ +class ViewErrorBag implements Countable, Stringable { + /** + * The array of the view error bags. + */ + protected array $bags = []; + + /** + * Checks if a named MessageBag exists in the bags. + */ + public function hasBag(string $key = 'default'): bool + { + return isset($this->bags[$key]); + } + + /** + * Get a MessageBag instance from the bags. + */ + public function getBag(string $key) + { + return Arr::get($this->bags, $key) ?: new MessageBag; + } + + /** + * Get all the bags. + */ + public function getBags(): array + { + return $this->bags; + } + + /** + * Add a new MessageBag instance to the bags. + */ + public function put(string $key, MessageBagContract $bag): static + { + $this->bags[$key] = $bag; + + return $this; + } + + /** + * Determine if the default message bag has any messages. + */ + public function any(): bool + { + return $this->count() > 0; + } + + /** + * Get the number of messages in the default bag. + */ + public function count(): int + { + return $this->getBag('default')->count(); + } + + /** + * Dynamically call methods on the default bag. + */ + public function __call(string $method, array $parameters): mixed + { + return $this->getBag('default')->$method(...$parameters); + } + + /** + * Dynamically access a view error bag. + * + * @param string $key + * @return MessageBagContract + */ + public function __get($key) + { + return $this->getBag($key); + } + + /** + * Dynamically set a view error bag. + * + * @param string $key + * @param MessageBagContract $value + * @return void + */ + public function __set($key, $value) + { + $this->put($key, $value); + } + + /** + * Convert the default bag to its string representation. + */ + public function __toString(): string + { + return (string) $this->getBag('default'); + } } diff --git a/src/view/composer.json b/src/view/composer.json index 7b0d5c149..8dc0fe24c 100644 --- a/src/view/composer.json +++ b/src/view/composer.json @@ -1,6 +1,5 @@ { "name": "hypervel/view", - "type": "library", "description": "The view package for Hypervel.", "license": "MIT", "keywords": [ @@ -10,41 +9,42 @@ "view", "hypervel" ], + "support": { + "issues": "https://github.com/hypervel/components/issues", + "source": "https://github.com/hypervel/components" + }, "authors": [ { "name": "Albert Chen", "email": "albert@hypervel.org" } ], - "support": { - "issues": "https://github.com/hypervel/components/issues", - "source": "https://github.com/hypervel/components" - }, - "autoload": { - "psr-4": { - "Hypervel\\View\\": "src/" - } - }, "require": { "php": "^8.2", "ext-tokenizer": "*", - "hyperf/contract": "~3.1.0", - "hyperf/http-server": "~3.1.0", - "hyperf/pool": "~3.1.0", - "hypervel/collections": "^0.3", + "hyperf/view": "~3.1.0", "hypervel/container": "^0.3", - "hypervel/contracts": "^0.3", - "hypervel/events": "^0.3", + "hypervel/event": "^0.3", "hypervel/filesystem": "^0.3", - "hypervel/macroable": "^0.3", "hypervel/support": "^0.3" }, - "config": { - "sort-packages": true + "autoload": { + "psr-4": { + "Hypervel\\View\\": "src/" + } }, "extra": { "branch-alias": { "dev-main": "0.3-dev" + }, + "hypervel": { + "providers": [ + "Hypervel\\View\\ViewServiceProvider" + ] } - } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev" } diff --git a/src/view/src/Compilers/Concerns/CompilesLayouts.php b/src/view/src/Compilers/Concerns/CompilesLayouts.php index 5a91c1684..2704d0457 100644 --- a/src/view/src/Compilers/Concerns/CompilesLayouts.php +++ b/src/view/src/Compilers/Concerns/CompilesLayouts.php @@ -56,7 +56,7 @@ protected function compileParent(): string { $escapedLastSection = strtr($this->lastSection, ['\\' => '\\\\', "'" => "\\'"]); - return ""; + return "getParentPlaceholder('{$escapedLastSection}'); ?>"; } /** diff --git a/src/view/src/Concerns/ManagesLoops.php b/src/view/src/Concerns/ManagesLoops.php index f0885590d..0668bef36 100644 --- a/src/view/src/Concerns/ManagesLoops.php +++ b/src/view/src/Concerns/ManagesLoops.php @@ -83,9 +83,10 @@ public function popLoop(): void public function getLastLoop(): ?stdClass { $loopsStack = Context::get(static::LOOPS_STACK_CONTEXT_KEY, []); - if ($last = Arr::last($loopsStack)) { - return (object) $last; - } + + return ! empty($loopsStack) + ? (object) end($loopsStack) + : null; } /** diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index 98af1d842..983883452 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -23,13 +23,6 @@ class CompilerEngine extends PhpEngine */ protected const COMPILED_PATH_CONTEXT_KEY = 'compiled_path'; - /** - * The view paths that were compiled or are not expired, keyed by the path. - * - * @var array - */ - protected $compiledOrNotExpired = []; - /** * Create a new compiler engine instance. */ @@ -52,7 +45,7 @@ public function get(string $path, array $data = []): string // 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)) { + if ($this->compiler->isExpired($path)) { $this->compiler->compile($path); } @@ -67,17 +60,11 @@ public function get(string $path, array $data = []): string 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; - $this->popCompiledPath(); return $results; @@ -135,12 +122,4 @@ public function getCompiler(): CompilerInterface { return $this->compiler; } - - /** - * Clear the cache of views that were compiled or not expired. - */ - public function forgetCompiledOrNotExpired(): void - { - $this->compiledOrNotExpired = []; - } } diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index 3148975bc..10951b950 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -19,11 +19,6 @@ class Factory implements FactoryContract { - /** - * Data that should be available to all templates. - */ - protected const SHARE_CONTEXT_KEY = 'share'; - /** * The number of active rendering operations. */ @@ -48,6 +43,11 @@ class Factory implements FactoryContract */ protected Container $container; + /** + * Data that should be available to all templates. + */ + protected array $shared = []; + /** * The extension to engine bindings. */ @@ -261,14 +261,10 @@ public function share(array|string $key, mixed $value = null): mixed { $keys = is_array($key) ? $key : [$key => $value]; - $shared = $this->getShared(); - foreach ($keys as $key => $value) { - $shared[$key] = $value; + $this->shared[$key] = $value; } - $this->setShared($shared); - return $value; } @@ -496,22 +492,16 @@ public function setContainer(Container $container): void */ public function shared(string $key, mixed $default = null): mixed { - return Arr::get($this->getShared(), $key, $default); + return Arr::get($this->shared, $key, $default); } /** * Get all of the shared data for the environment. + * + * @return array */ - public function getShared(): array - { - return Context::get(self::SHARE_CONTEXT_KEY, []); - } - - /** - * Set the shared data for the environment. - */ - public function setShared(array $data): void + public function getShared() { - Context::set(self::SHARE_CONTEXT_KEY, $data); + return $this->shared; } } diff --git a/src/view/src/Middleware/ShareErrorsFromSession.php b/src/view/src/Middleware/ShareErrorsFromSession.php index 583f5bec8..925763ffd 100644 --- a/src/view/src/Middleware/ShareErrorsFromSession.php +++ b/src/view/src/Middleware/ShareErrorsFromSession.php @@ -5,6 +5,7 @@ namespace Hypervel\View\Middleware; use Closure; +use Hyperf\Contract\SessionInterface; use Hypervel\View\Contracts\Factory as ViewFactory; use Hypervel\Support\ViewErrorBag; @@ -14,7 +15,8 @@ class ShareErrorsFromSession * Create a new error binder instance. */ public function __construct( - protected ViewFactory $view + protected ViewFactory $view, + protected SessionInterface $session, ) { } @@ -27,7 +29,7 @@ public function handle(mixed $request, Closure $next): mixed // 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 + 'errors', $this->session->get('errors') ?: new ViewErrorBag ); // Putting the errors in the view for every view allows the developer to just diff --git a/src/view/src/ViewException.php b/src/view/src/ViewException.php index 42b61080d..52a110517 100644 --- a/src/view/src/ViewException.php +++ b/src/view/src/ViewException.php @@ -36,5 +36,7 @@ public function render(RequestInterface $request): ?ResponseInterface if ($exception && method_exists($exception, 'render')) { return $exception->render($request); } + + return null; } } diff --git a/src/view/src/ViewServiceProvider.php b/src/view/src/ViewServiceProvider.php index 8e364370b..950c5090f 100755 --- a/src/view/src/ViewServiceProvider.php +++ b/src/view/src/ViewServiceProvider.php @@ -4,9 +4,15 @@ namespace Hypervel\View; +use Hyperf\View\Mode; +use Hyperf\ViewEngine\Contract\FactoryInterface as HyperfFactoryInterface; +use Hyperf\ViewEngine\HyperfViewEngine; use Hypervel\Container\Container; -use Hypervel\Contracts\Events\Dispatcher; +use Hypervel\Event\Contracts\Dispatcher; +use Hypervel\Foundation\Application; +use Hypervel\Support\Arr; use Hypervel\Support\ServiceProvider; +use Hypervel\View\Contracts\Factory as FactoryContract; use Hypervel\View\Compilers\BladeCompiler; use Hypervel\View\Engines\CompilerEngine; use Hypervel\View\Engines\EngineResolver; @@ -17,8 +23,6 @@ class ViewServiceProvider extends ServiceProvider { /** * Register the service provider. - * - * @return void */ public function register(): void { @@ -26,20 +30,15 @@ public function register(): void $this->registerViewFinder(); $this->registerBladeCompiler(); $this->registerEngineResolver(); - - $this->app->terminating(static function () { - Component::flushCache(); - }); + $this->compatibleWithHyperfView(); } /** * Register the view environment. - * - * @return void */ - public function registerFactory(): void + protected function registerFactory(): void { - $this->app->singleton('view', function ($app) { + $this->app->bind('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. @@ -56,21 +55,16 @@ public function registerFactory(): void $factory->share('app', $app); - $app->terminating(static function () { - Component::forgetFactory(); - }); - return $factory; }); + + $this->app->bind(FactoryContract::class, function ($app) { + return $app['view']; + }); } /** * Create a new Factory Instance. - * - * @param \Hypervel\View\Engines\EngineResolver $resolver - * @param \Hypervel\View\ViewFinderInterface $finder - * @param \Hypervel\Contracts\Events\Dispatcher $events - * @return \Hypervel\View\Factory */ protected function createFactory(EngineResolver $resolver, ViewFinderInterface $finder, Dispatcher $events): Factory { @@ -79,10 +73,8 @@ protected function createFactory(EngineResolver $resolver, ViewFinderInterface $ /** * Register the view finder implementation. - * - * @return void */ - public function registerViewFinder(): void + protected function registerViewFinder(): void { $this->app->bind('view.finder', function ($app) { return new FileViewFinder($app['files'], $app['config']['view.paths']); @@ -91,12 +83,10 @@ public function registerViewFinder(): void /** * Register the Blade compiler implementation. - * - * @return void */ - public function registerBladeCompiler(): void + protected function registerBladeCompiler(): void { - $this->app->singleton('blade.compiler', function ($app) { + $this->app->bind('blade.compiler', function ($app) { return tap(new BladeCompiler( $app['files'], $app['config']['view.compiled'], @@ -111,12 +101,10 @@ public function registerBladeCompiler(): void /** * Register the engine resolver instance. - * - * @return void */ - public function registerEngineResolver(): void + protected function registerEngineResolver(): void { - $this->app->singleton('view.engine.resolver', function () { + $this->app->bind('view.engine.resolver', function () { $resolver = new EngineResolver; // Next, we will register the various view engines with the resolver so that the @@ -132,11 +120,8 @@ public function registerEngineResolver(): void /** * Register the file engine implementation. - * - * @param \Hypervel\View\Engines\EngineResolver $resolver - * @return void */ - public function registerFileEngine(EngineResolver $resolver): void + protected function registerFileEngine(EngineResolver $resolver): void { $resolver->register('file', function () { return new FileEngine(Container::getInstance()->make('files')); @@ -145,11 +130,8 @@ public function registerFileEngine(EngineResolver $resolver): void /** * Register the PHP engine implementation. - * - * @param \Hypervel\View\Engines\EngineResolver $resolver - * @return void */ - public function registerPhpEngine(EngineResolver $resolver): void + protected function registerPhpEngine(EngineResolver $resolver): void { $resolver->register('php', function () { return new PhpEngine(Container::getInstance()->make('files')); @@ -158,11 +140,8 @@ public function registerPhpEngine(EngineResolver $resolver): void /** * Register the Blade engine implementation. - * - * @param \Hypervel\View\Engines\EngineResolver $resolver - * @return void */ - public function registerBladeEngine(EngineResolver $resolver): void + protected function registerBladeEngine(EngineResolver $resolver): void { $resolver->register('blade', function () { $app = Container::getInstance(); @@ -172,11 +151,42 @@ public function registerBladeEngine(EngineResolver $resolver): void $app->make('files'), ); - $app->terminating(static function () use ($compiler) { - $compiler->forgetCompiledOrNotExpired(); - }); - return $compiler; }); } + + /** + * Make compatible with Hyperf View component. + */ + protected function compatibleWithHyperfView(): void + { + if (! class_exists(HyperfFactoryInterface::class)) { + return; + } + + $this->app->beforeResolving(HyperfFactoryInterface::class, function (string $abstract, array $params, Application $app) { + $config = $app->get('config'); + $viewConfig = $config->get('view', []); + + $customHyperfViewConfig = Arr::only($viewConfig, [ + 'engine', + 'mode', + 'config', + 'event', + 'components', + ]); + $hyperfViewConfig = $customHyperfViewConfig + [ + 'engine' => HyperfViewEngine::class, + 'mode' => Mode::SYNC, + 'config' => [ + 'view_path' => base_path('resources/views'), + 'cache_path' => storage_path('framework/views'), + ], + 'event' => ['enable' => false], + 'components' => [], + ]; + + $config->set('view', array_merge($hyperfViewConfig, $viewConfig)); + }); + } } diff --git a/src/view/src/composer.json b/src/view/src/composer.json deleted file mode 100644 index 41472bc1c..000000000 --- a/src/view/src/composer.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "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.2", - "ext-tokenizer": "*", - "illuminate/collections": "^11.0", - "illuminate/container": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/events": "^11.0", - "illuminate/filesystem": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0" - }, - "autoload": { - "psr-4": { - "Illuminate\\View\\": "" - } - }, - "extra": { - "branch-alias": { - "dev-master": "11.x-dev" - } - }, - "config": { - "sort-packages": true - }, - "minimum-stability": "dev" -} diff --git a/tests/View/ViewCompilerEngineTest.php b/tests/View/ViewCompilerEngineTest.php index dacc8054b..496382590 100755 --- a/tests/View/ViewCompilerEngineTest.php +++ b/tests/View/ViewCompilerEngineTest.php @@ -78,10 +78,6 @@ public function testThatViewsAreNotAskTwiceIfTheyAreExpired() $engine->get(__DIR__.'/fixtures/foo.php'); $engine->get(__DIR__.'/fixtures/foo.php'); $engine->get(__DIR__.'/fixtures/foo.php'); - - $engine->forgetCompiledOrNotExpired(); - - $engine->get(__DIR__.'/fixtures/foo.php'); } public function testViewsAreRecompiledWhenCompiledViewIsMissingViaFileNotFoundException() diff --git a/tests/View/ViewTest.php b/tests/View/ViewTest.php index 587eaeee9..85f0ee457 100755 --- a/tests/View/ViewTest.php +++ b/tests/View/ViewTest.php @@ -4,7 +4,6 @@ use ArrayAccess; use BadMethodCallException; -use Closure; use Hypervel\Support\Contracts\Arrayable; use Hypervel\Support\Contracts\Renderable; use Hypervel\View\Contracts\Engine; From 011c576d0575ab1d26717248a9f95e4c3734a3fd Mon Sep 17 00:00:00 2001 From: bluehaha Date: Mon, 22 Dec 2025 11:54:59 +0800 Subject: [PATCH 19/21] feat: migrate Laravel view --- .../src/Generator/ComponentCommand.php | 8 ++++++- src/support/src/Facades/Blade.php | 21 ++++++++++++------- src/view/src/View.php | 7 +++++-- src/view/src/ViewServiceProvider.php | 8 +++---- tests/View/ViewCompilerEngineTest.php | 19 ++++++++--------- 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/devtool/src/Generator/ComponentCommand.php b/src/devtool/src/Generator/ComponentCommand.php index 3f41bf286..caef32a1f 100644 --- a/src/devtool/src/Generator/ComponentCommand.php +++ b/src/devtool/src/Generator/ComponentCommand.php @@ -5,6 +5,7 @@ namespace Hypervel\Devtool\Generator; use Hyperf\Devtool\Generator\GeneratorCommand; +use Hypervel\Support\Str; class ComponentCommand extends GeneratorCommand { @@ -37,7 +38,12 @@ protected function buildClass(string $name): string protected function replaceView(string $stub, string $name): string { - $view = lcfirst(str_replace($this->getNamespace($name) . '\\', '', $name)); + $view = str_replace($this->getDefaultNamespace($name) . '\\', '', $name); + $view = array_map( + fn ($part) => Str::snake($part), + explode('\\', $view) + ); + $view = implode('.', $view); return str_replace( ['%VIEW%'], diff --git a/src/support/src/Facades/Blade.php b/src/support/src/Facades/Blade.php index 15ee8208a..24e4f32b3 100644 --- a/src/support/src/Facades/Blade.php +++ b/src/support/src/Facades/Blade.php @@ -4,31 +4,36 @@ namespace Hypervel\Support\Facades; -use Hyperf\ViewEngine\Compiler\CompilerInterface; - /** * @method static void compile(string|null $path = null) * @method static string getPath() * @method static void setPath(string $path) * @method static string compileString(string $value) + * @method static string render(string $string, array $data = [], bool $deleteCachedView = false) + * @method static string renderComponent(\Hypervel\View\Component $component) * @method static string stripParentheses(string $expression) * @method static void extend(callable $compiler) * @method static array getExtensions() * @method static void if(string $name, callable $callback) - * @method static bool check(string $name, array ...$parameters) + * @method static bool check(string $name, mixed ...$parameters) * @method static void component(string $class, string|null $alias = null, string $prefix = '') * @method static void components(array $components, string $prefix = '') * @method static array getClassComponentAliases() + * @method static void anonymousComponentPath(string $path, string|null $prefix = null) + * @method static void anonymousComponentNamespace(string $directory, string|null $prefix = null) * @method static void componentNamespace(string $namespace, string $prefix) + * @method static array getAnonymousComponentPaths() + * @method static array getAnonymousComponentNamespaces() * @method static array getClassComponentNamespaces() - * @method static array getComponentAutoload() - * @method static void setComponentAutoload(array $config) * @method static void aliasComponent(string $path, string|null $alias = null) * @method static void include(string $path, string|null $alias = null) * @method static void aliasInclude(string $path, string|null $alias = null) - * @method static void directive(string $name, callable $handler) + * @method static void bindDirective(string $name, callable $handler) + * @method static void directive(string $name, callable $handler, bool $bind = false) * @method static array getCustomDirectives() + * @method static \Hypervel\View\Compilers\BladeCompiler prepareStringsForCompilationUsing(callable $callback) * @method static void precompiler(callable $precompiler) + * @method static string usingEchoFormat(string $format, callable $callback) * @method static void setEchoFormat(string $format) * @method static void withDoubleEncoding() * @method static void withoutDoubleEncoding() @@ -40,7 +45,9 @@ * @method static string compileEndComponentClass() * @method static mixed sanitizeComponentAttribute(mixed $value) * @method static string compileEndOnce() + * @method static void stringable(string|callable $class, callable|null $handler = null) * @method static string compileEchos(string $value) + * @method static string applyEchoHandler(string $value) * * @see \Hypervel\View\Compilers\BladeCompiler */ @@ -48,6 +55,6 @@ class Blade extends Facade { protected static function getFacadeAccessor() { - return CompilerInterface::class; + return 'blade.compiler'; } } diff --git a/src/view/src/View.php b/src/view/src/View.php index 727790860..b76e61057 100755 --- a/src/view/src/View.php +++ b/src/view/src/View.php @@ -13,6 +13,7 @@ use Hypervel\View\Contracts\Engine; use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Collection; +use Hypervel\Support\HtmlString; use Hypervel\Support\MessageBag; use Hypervel\Support\Str; use Hypervel\Support\Traits\Macroable; @@ -47,11 +48,13 @@ public function __construct( /** * Get the evaluated contents of a given fragment. */ - public function fragment(string $fragment): string + public function fragment(string $fragment): HtmlString { - return $this->render(function () use ($fragment) { + $content = $this->render(function () use ($fragment) { return $this->factory->getFragment($fragment); }); + + return new HtmlString($content); } /** diff --git a/src/view/src/ViewServiceProvider.php b/src/view/src/ViewServiceProvider.php index 950c5090f..f6cc12d8f 100755 --- a/src/view/src/ViewServiceProvider.php +++ b/src/view/src/ViewServiceProvider.php @@ -124,7 +124,7 @@ protected function registerEngineResolver(): void protected function registerFileEngine(EngineResolver $resolver): void { $resolver->register('file', function () { - return new FileEngine(Container::getInstance()->make('files')); + return new FileEngine(Container::getInstance()->get('files')); }); } @@ -134,7 +134,7 @@ protected function registerFileEngine(EngineResolver $resolver): void protected function registerPhpEngine(EngineResolver $resolver): void { $resolver->register('php', function () { - return new PhpEngine(Container::getInstance()->make('files')); + return new PhpEngine(Container::getInstance()->get('files')); }); } @@ -147,8 +147,8 @@ protected function registerBladeEngine(EngineResolver $resolver): void $app = Container::getInstance(); $compiler = new CompilerEngine( - $app->make('blade.compiler'), - $app->make('files'), + $app->get('blade.compiler'), + $app->get('files'), ); return $compiler; diff --git a/tests/View/ViewCompilerEngineTest.php b/tests/View/ViewCompilerEngineTest.php index 496382590..3f5be3c2d 100755 --- a/tests/View/ViewCompilerEngineTest.php +++ b/tests/View/ViewCompilerEngineTest.php @@ -77,7 +77,6 @@ public function testThatViewsAreNotAskTwiceIfTheyAreExpired() $engine->get(__DIR__.'/fixtures/foo.php'); $engine->get(__DIR__.'/fixtures/foo.php'); - $engine->get(__DIR__.'/fixtures/foo.php'); } public function testViewsAreRecompiledWhenCompiledViewIsMissingViaFileNotFoundException() @@ -113,12 +112,12 @@ public function testViewsAreRecompiledWhenCompiledViewIsMissingViaFileNotFoundEx $engine->getCompiler() ->shouldReceive('isExpired') - ->once() + ->twice() ->andReturn(true); $engine->getCompiler() ->shouldReceive('compile') - ->twice() + ->times(3) ->with($path); $engine->get($path); @@ -158,12 +157,12 @@ public function testViewsAreRecompiledWhenCompiledViewIsMissingViaRequireExcepti $engine->getCompiler() ->shouldReceive('isExpired') - ->once() + ->twice() ->andReturn(true); $engine->getCompiler() ->shouldReceive('compile') - ->twice() + ->times(3) ->with($path); $engine->get($path); @@ -205,12 +204,12 @@ public function testViewsAreRecompiledJustOnceWhenCompiledViewIsMissing() $engine->getCompiler() ->shouldReceive('isExpired') - ->once() + ->twice() ->andReturn(true); $engine->getCompiler() ->shouldReceive('compile') - ->twice() + ->times(3) ->with($path); $engine->get($path); @@ -264,7 +263,7 @@ public function testViewsAreNotRecompiledIfTheyWereJustCompiled() $engine = $this->getEngine($files); $files->shouldReceive('getRequire') - ->once() + ->twice() ->with($compiled, []) ->andThrow(new FileNotFoundException( "File does not exist at path {$path}." @@ -277,12 +276,12 @@ public function testViewsAreNotRecompiledIfTheyWereJustCompiled() $engine->getCompiler() ->shouldReceive('compile') - ->once() + ->twice() ->with($path); $engine->getCompiler() ->shouldReceive('getCompiledPath') - ->once() + ->twice() ->with($path) ->andReturn($compiled); From 2eaebe38807ae4e16af59b033017271fbafafb23 Mon Sep 17 00:00:00 2001 From: bluehaha Date: Mon, 22 Dec 2025 11:55:19 +0800 Subject: [PATCH 20/21] feat: return Html component --- src/http/src/CoreMiddleware.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/http/src/CoreMiddleware.php b/src/http/src/CoreMiddleware.php index 48269c6bd..f2c572e6c 100644 --- a/src/http/src/CoreMiddleware.php +++ b/src/http/src/CoreMiddleware.php @@ -17,6 +17,7 @@ use Hyperf\HttpServer\Router\DispatcherFactory; use Hyperf\Server\Exception\ServerException; use Hyperf\View\RenderInterface; +use Hyperf\ViewEngine\Contract\Htmlable; use Hyperf\ViewEngine\Contract\Renderable; use Hyperf\ViewEngine\Contract\ViewInterface; use Hypervel\Context\ResponseContext; @@ -65,6 +66,12 @@ protected function transferToResponse($response, ServerRequestInterface $request ->setBody(new SwooleStream($response->render())); } + if ($response instanceof Htmlable) { + return $this->response() + ->addHeader('content-type', 'text/html') + ->setBody(new SwooleStream((string) $response)); + } + if (is_string($response)) { return $this->response()->addHeader('content-type', 'text/plain')->setBody(new SwooleStream($response)); } From df3daa6e09236ccf91c22f1975c7495c8882374e Mon Sep 17 00:00:00 2001 From: bluehaha Date: Mon, 22 Dec 2025 16:46:59 +0800 Subject: [PATCH 21/21] feat: migrate Laravel view --- .../src/Generator/ComponentCommand.php | 6 +- src/support/src/ViewErrorBag.php | 11 +- src/view/src/Compilers/BladeCompiler.php | 139 +++---- src/view/src/Compilers/Compiler.php | 2 +- .../src/Compilers/ComponentTagCompiler.php | 186 ++++----- .../Concerns/CompilesAuthorizations.php | 12 +- .../Compilers/Concerns/CompilesClasses.php | 2 +- .../Compilers/Concerns/CompilesComponents.php | 32 +- .../Concerns/CompilesConditionals.php | 6 +- .../src/Compilers/Concerns/CompilesEchos.php | 12 +- .../src/Compilers/Concerns/CompilesErrors.php | 2 +- .../Compilers/Concerns/CompilesHelpers.php | 4 +- .../Compilers/Concerns/CompilesIncludes.php | 4 +- .../Compilers/Concerns/CompilesInjections.php | 2 +- .../src/Compilers/Concerns/CompilesJs.php | 5 +- .../src/Compilers/Concerns/CompilesJson.php | 2 +- .../src/Compilers/Concerns/CompilesLoops.php | 4 +- .../Compilers/Concerns/CompilesSessions.php | 2 +- .../src/Compilers/Concerns/CompilesStacks.php | 12 +- .../src/Compilers/Concerns/CompilesStyles.php | 2 +- .../Concerns/CompilesTranslations.php | 3 +- .../Concerns/CompilesUseStatements.php | 4 +- src/view/src/Component.php | 18 +- src/view/src/ComponentAttributeBag.php | 27 +- src/view/src/ComponentSlot.php | 2 +- src/view/src/Concerns/ManagesComponents.php | 17 +- src/view/src/Concerns/ManagesEvents.php | 16 +- src/view/src/Concerns/ManagesLayouts.php | 8 +- src/view/src/Concerns/ManagesStacks.php | 2 +- src/view/src/Concerns/ManagesTranslations.php | 3 +- .../Contracts/ViewCompilationException.php | 1 - src/view/src/DynamicComponent.php | 15 +- src/view/src/Engines/CompilerEngine.php | 14 +- src/view/src/Engines/EngineResolver.php | 2 +- src/view/src/Engines/FileEngine.php | 2 +- src/view/src/Engines/PhpEngine.php | 2 +- src/view/src/Factory.php | 27 +- src/view/src/FileViewFinder.php | 4 +- .../src/Middleware/ShareErrorsFromSession.php | 5 +- src/view/src/View.php | 15 +- src/view/src/ViewFinderInterface.php | 2 +- src/view/src/ViewName.php | 2 +- src/view/src/ViewServiceProvider.php | 12 +- tests/View/Blade/AbstractBladeTestCase.php | 2 + tests/View/Blade/BladeAppendTest.php | 6 + tests/View/Blade/BladeBoolTest.php | 6 + tests/View/Blade/BladeBreakStatementsTest.php | 6 + tests/View/Blade/BladeCanStatementsTest.php | 10 +- .../View/Blade/BladeCananyStatementsTest.php | 10 +- .../View/Blade/BladeCannotStatementsTest.php | 10 +- .../View/Blade/BladeCheckedStatementsTest.php | 6 + tests/View/Blade/BladeClassTest.php | 8 +- tests/View/Blade/BladeCommentsTest.php | 6 + tests/View/Blade/BladeComponentFirstTest.php | 6 + .../Blade/BladeComponentTagCompilerTest.php | 386 +++++++++--------- tests/View/Blade/BladeComponentsTest.php | 10 +- .../Blade/BladeContinueStatementsTest.php | 6 + tests/View/Blade/BladeCustomTest.php | 12 +- tests/View/Blade/BladeEchoHandlerTest.php | 29 +- tests/View/Blade/BladeEchoTest.php | 60 ++- .../Blade/BladeElseAuthStatementsTest.php | 6 + .../Blade/BladeElseGuestStatementsTest.php | 6 + .../View/Blade/BladeElseIfStatementsTest.php | 6 + tests/View/Blade/BladeElseStatementsTest.php | 6 + tests/View/Blade/BladeEndSectionsTest.php | 6 + .../Blade/BladeEnvironmentStatementsTest.php | 6 + tests/View/Blade/BladeErrorTest.php | 6 + tests/View/Blade/BladeEscapedTest.php | 6 + tests/View/Blade/BladeExpressionTest.php | 6 + tests/View/Blade/BladeExtendsTest.php | 22 +- tests/View/Blade/BladeForStatementsTest.php | 6 + .../View/Blade/BladeForeachStatementsTest.php | 8 +- .../View/Blade/BladeForelseStatementsTest.php | 8 +- tests/View/Blade/BladeFragmentTest.php | 6 + tests/View/Blade/BladeHasSectionTest.php | 6 + tests/View/Blade/BladeHelpersTest.php | 6 + .../View/Blade/BladeIfAuthStatementsTest.php | 6 + .../View/Blade/BladeIfEmptyStatementsTest.php | 6 + .../View/Blade/BladeIfGuestStatementsTest.php | 6 + .../View/Blade/BladeIfIssetStatementsTest.php | 6 + tests/View/Blade/BladeIfStatementsTest.php | 6 + tests/View/Blade/BladeIncludesTest.php | 6 + tests/View/Blade/BladeInjectTest.php | 16 +- tests/View/Blade/BladeJsTest.php | 6 + tests/View/Blade/BladeJsonTest.php | 6 + tests/View/Blade/BladeLangTest.php | 6 + .../View/Blade/BladeOverwriteSectionTest.php | 6 + tests/View/Blade/BladePhpStatementsTest.php | 24 +- tests/View/Blade/BladePrependTest.php | 7 +- tests/View/Blade/BladePropsTest.php | 8 +- tests/View/Blade/BladePushTest.php | 7 +- tests/View/Blade/BladeSectionMissingTest.php | 6 + tests/View/Blade/BladeSectionTest.php | 6 + tests/View/Blade/BladeSessionTest.php | 6 + tests/View/Blade/BladeShowTest.php | 6 + tests/View/Blade/BladeStackTest.php | 6 + tests/View/Blade/BladeStopSectionTest.php | 6 + tests/View/Blade/BladeStyleTest.php | 8 +- .../View/Blade/BladeUnlessStatementsTest.php | 6 + tests/View/Blade/BladeUnsetStatementsTest.php | 6 + tests/View/Blade/BladeUseTest.php | 22 +- tests/View/Blade/BladeVerbatimTest.php | 6 + tests/View/Blade/BladeWhileStatementsTest.php | 6 + tests/View/Blade/BladeYieldTest.php | 6 + tests/View/ComponentTest.php | 44 +- tests/View/ViewBladeCompilerTest.php | 37 +- tests/View/ViewCompilerEngineTest.php | 54 +-- tests/View/ViewComponentAttributeBagTest.php | 14 +- tests/View/ViewComponentTest.php | 27 +- tests/View/ViewEngineResolverTest.php | 10 +- tests/View/ViewFactoryTest.php | 44 +- tests/View/ViewFileViewFinderTest.php | 80 ++-- tests/View/ViewPhpEngineTest.php | 10 +- tests/View/ViewTest.php | 11 +- tests/View/fixtures/http-exception.php | 2 + tests/View/fixtures/regular-exception.php | 2 + .../fixtures/section-exception-layout.php | 2 + tests/View/fixtures/section-exception.php | 2 +- 118 files changed, 1171 insertions(+), 731 deletions(-) diff --git a/src/devtool/src/Generator/ComponentCommand.php b/src/devtool/src/Generator/ComponentCommand.php index caef32a1f..816cbdd33 100644 --- a/src/devtool/src/Generator/ComponentCommand.php +++ b/src/devtool/src/Generator/ComponentCommand.php @@ -39,10 +39,10 @@ protected function buildClass(string $name): string protected function replaceView(string $stub, string $name): string { $view = str_replace($this->getDefaultNamespace($name) . '\\', '', $name); - $view = array_map( + $view = array_map( fn ($part) => Str::snake($part), - explode('\\', $view) - ); + explode('\\', $view) + ); $view = implode('.', $view); return str_replace( diff --git a/src/support/src/ViewErrorBag.php b/src/support/src/ViewErrorBag.php index a69e74a94..eda81ab74 100644 --- a/src/support/src/ViewErrorBag.php +++ b/src/support/src/ViewErrorBag.php @@ -31,7 +31,7 @@ public function hasBag(string $key = 'default'): bool */ public function getBag(string $key) { - return Arr::get($this->bags, $key) ?: new MessageBag; + return Arr::get($this->bags, $key) ?: new MessageBag(); } /** @@ -73,13 +73,13 @@ public function count(): int */ public function __call(string $method, array $parameters): mixed { - return $this->getBag('default')->$method(...$parameters); + return $this->getBag('default')->{$method}(...$parameters); } /** * Dynamically access a view error bag. * - * @param string $key + * @param string $key * @return MessageBagContract */ public function __get($key) @@ -90,9 +90,8 @@ public function __get($key) /** * Dynamically set a view error bag. * - * @param string $key - * @param MessageBagContract $value - * @return void + * @param string $key + * @param MessageBagContract $value */ public function __set($key, $value) { diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php index 7e6b28cc7..c2f940bc7 100644 --- a/src/view/src/Compilers/BladeCompiler.php +++ b/src/view/src/Compilers/BladeCompiler.php @@ -7,19 +7,42 @@ use Closure; use Hypervel\Container\Container; use Hypervel\Context\Context; -use Hypervel\Support\Contracts\Htmlable; -use Hypervel\View\Contracts\Factory as ViewFactory; -use Hypervel\View\Contracts\View; use Hypervel\Support\Arr; use Hypervel\Support\Collection; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\Support\Str; use Hypervel\Support\Stringable; use Hypervel\Support\Traits\ReflectsClosures; use Hypervel\View\Component; +use Hypervel\View\Contracts\Factory as ViewFactory; +use Hypervel\View\Contracts\View; use InvalidArgumentException; class BladeCompiler extends Compiler implements CompilerInterface { + use Concerns\CompilesAuthorizations; + use Concerns\CompilesClasses; + use Concerns\CompilesComments; + use Concerns\CompilesComponents; + use Concerns\CompilesConditionals; + use Concerns\CompilesEchos; + use Concerns\CompilesErrors; + use Concerns\CompilesFragments; + use Concerns\CompilesHelpers; + use Concerns\CompilesIncludes; + use Concerns\CompilesInjections; + use Concerns\CompilesJson; + use Concerns\CompilesJs; + use Concerns\CompilesLayouts; + use Concerns\CompilesLoops; + use Concerns\CompilesRawPhp; + use Concerns\CompilesSessions; + use Concerns\CompilesStacks; + use Concerns\CompilesStyles; + use Concerns\CompilesTranslations; + use Concerns\CompilesUseStatements; + use ReflectsClosures; + /* * Temporarily store the raw blocks found in the template. */ @@ -35,29 +58,6 @@ class BladeCompiler extends Compiler implements CompilerInterface */ protected const ECHO_FORMAT_CONTEXT_KEY = 'echo_format'; - use Concerns\CompilesAuthorizations, - Concerns\CompilesClasses, - Concerns\CompilesComments, - Concerns\CompilesComponents, - Concerns\CompilesConditionals, - Concerns\CompilesEchos, - Concerns\CompilesErrors, - Concerns\CompilesFragments, - Concerns\CompilesHelpers, - Concerns\CompilesIncludes, - Concerns\CompilesInjections, - Concerns\CompilesJson, - Concerns\CompilesJs, - Concerns\CompilesLayouts, - Concerns\CompilesLoops, - Concerns\CompilesRawPhp, - Concerns\CompilesSessions, - Concerns\CompilesStacks, - Concerns\CompilesStyles, - Concerns\CompilesTranslations, - Concerns\CompilesUseStatements, - ReflectsClosures; - /** * All of the registered extensions. */ @@ -172,7 +172,7 @@ protected function appendFilePath(string $contents, string $path): string $contents .= ' ?>'; } - return $contents.""; + return $contents . ""; } /** @@ -247,8 +247,7 @@ public function compileString(string $value): string */ public static function render(string $string, array $data = [], bool $deleteCachedView = false): string { - $component = new class($string) extends Component - { + $component = new class($string) extends Component { protected $template; public function __construct($template) @@ -284,14 +283,14 @@ public static function renderComponent(Component $component): string if ($view instanceof View) { return $view->with($data)->render(); - } elseif ($view instanceof Htmlable) { + } + if ($view instanceof Htmlable) { return $view->toHtml(); - } else { - return Container::getInstance() - ->make(ViewFactory::class) - ->make($view, $data) - ->render(); } + return Container::getInstance() + ->make(ViewFactory::class) + ->make($view, $data) + ->render(); } /** @@ -316,7 +315,7 @@ protected function storeUncompiledBlocks(string $value): string protected function storeVerbatimBlocks(string $value): string { return preg_replace_callback('/(?storeRawBlock($matches[2]); + return $matches[1] . $this->storeRawBlock($matches[2]); }, $value); } @@ -343,7 +342,7 @@ protected function storeRawBlock(string $value): string /** * Temporarily store the raw block found in the template. * - * @return int The number of raw blocks in the stack after pushing the new one. + * @return int the number of raw blocks in the stack after pushing the new one */ protected function pushRawBlock(string $value): int { @@ -364,7 +363,9 @@ protected function compileComponentTags(string $value): string } return (new ComponentTagCompiler( - $this->classComponentAliases, $this->classComponentNamespaces, $this + $this->classComponentAliases, + $this->classComponentNamespaces, + $this ))->compile($value); } @@ -375,7 +376,7 @@ protected function restoreRawContent(string $result): string { $rawBlocks = Context::get(static::RAW_BLOCKS_CONTEXT_KEY); - $result = preg_replace_callback('/'.$this->getRawPlaceholder('(\d+)').'/', function ($matches) use ($rawBlocks) { + $result = preg_replace_callback('/' . $this->getRawPlaceholder('(\d+)') . '/', function ($matches) use ($rawBlocks) { return $rawBlocks[$matches[1]]; }, $result); @@ -398,7 +399,7 @@ protected function getRawPlaceholder(int|string $replace): string protected function addFooters(string $result, array $footer): string { return ltrim($result, "\n") - ."\n".implode("\n", array_reverse($footer)); + . "\n" . implode("\n", array_reverse($footer)); } /** @@ -438,7 +439,7 @@ protected function compileStatements(string $template): string $offset = 0; - for ($i = 0; isset($matches[0][$i]); $i++) { + for ($i = 0; isset($matches[0][$i]); ++$i) { $match = [ $matches[0][$i], $matches[1][$i], @@ -450,23 +451,23 @@ protected function compileStatements(string $template): string // Here we check to see if we have properly found the closing parenthesis by // regex pattern or not, and will recursively continue on to the next ")" // then check again until the tokenizer confirms we find the right one. - while (isset($match[4]) && - Str::endsWith($match[0], ')') && - ! $this->hasEvenNumberOfParentheses($match[0])) { + while (isset($match[4]) + && Str::endsWith($match[0], ')') + && ! $this->hasEvenNumberOfParentheses($match[0])) { if (($after = Str::after($template, $match[0])) === $template) { break; } $rest = Str::before($after, ')'); - if (isset($matches[0][$i + 1]) && Str::contains($rest.')', $matches[0][$i + 1])) { + if (isset($matches[0][$i + 1]) && Str::contains($rest . ')', $matches[0][$i + 1])) { unset($matches[0][$i + 1]); - $i++; + ++$i; } - $match[0] = $match[0].$rest.')'; - $match[3] = $match[3].$rest.')'; - $match[4] = $match[4].$rest; + $match[0] = $match[0] . $rest . ')'; + $match[3] = $match[3] . $rest . ')'; + $match[4] = $match[4] . $rest; } [$template, $offset] = $this->replaceFirstStatement( @@ -508,7 +509,7 @@ protected function replaceFirstStatement(string $search, string $replace, string */ protected function hasEvenNumberOfParentheses(string $expression): bool { - $tokens = token_get_all('customDirectives[$match[1]])) { $match[0] = $this->callCustomDirective($match[1], Arr::get($match, 3)); - } elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) { - $match[0] = $this->$method(Arr::get($match, 3)); + } elseif (method_exists($this, $method = 'compile' . ucfirst($match[1]))) { + $match[0] = $this->{$method}(Arr::get($match, 3)); } else { return $match[0]; } - return isset($match[3]) ? $match[0] : $match[0].$match[2]; + return isset($match[3]) ? $match[0] : $match[0] . $match[2]; } /** @@ -597,23 +598,23 @@ public function if(string $name, callable $callback): void $this->directive($name, function ($expression) use ($name) { return $expression !== '' - ? "" - : ""; + ? "" + : ""; }); - $this->directive('unless'.$name, function ($expression) use ($name) { + $this->directive('unless' . $name, function ($expression) use ($name) { return $expression !== '' - ? "" - : ""; + ? "" + : ""; }); - $this->directive('else'.$name, function ($expression) use ($name) { + $this->directive('else' . $name, function ($expression) use ($name) { return $expression !== '' - ? "" - : ""; + ? "" + : ""; }); - $this->directive('end'.$name, function () { + $this->directive('end' . $name, function () { return ''; }); } @@ -636,15 +637,15 @@ public function component(string $class, ?string $alias = null, string $prefix = } if (is_null($alias)) { - $alias = str_contains($class, '\\View\\Components\\') - ? (new Collection(explode('\\', Str::after($class, '\\View\\Components\\'))))->map(function ($segment) { + $alias = str_contains($class, '\View\Components\\') + ? (new Collection(explode('\\', Str::after($class, '\View\Components\\'))))->map(function ($segment) { return Str::kebab($segment); })->implode(':') : Str::kebab(class_basename($class)); } if (! empty($prefix)) { - $alias = $prefix.'-'.$alias; + $alias = $prefix . '-' . $alias; } $this->classComponentAliases[$alias] = $class; @@ -748,7 +749,7 @@ public function aliasComponent(string $path, ?string $alias = null): void : "startComponent('{$path}'); ?>"; }); - $this->directive('end'.$alias, function ($expression) { + $this->directive('end' . $alias, function ($expression) { return 'renderComponent(); ?>'; }); } diff --git a/src/view/src/Compilers/Compiler.php b/src/view/src/Compilers/Compiler.php index 00d9b1fe9..cf1b13249 100755 --- a/src/view/src/Compilers/Compiler.php +++ b/src/view/src/Compilers/Compiler.php @@ -27,7 +27,7 @@ public function __construct( */ public function getCompiledPath(string $path): string { - return $this->cachePath.'/'.hash('xxh128', 'v2'.Str::after($path, $this->basePath)).'.'.$this->compiledExtension; + return $this->cachePath . '/' . hash('xxh128', 'v2' . Str::after($path, $this->basePath)) . '.' . $this->compiledExtension; } /** diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php index 585f8b479..341798c13 100644 --- a/src/view/src/Compilers/ComponentTagCompiler.php +++ b/src/view/src/Compilers/ComponentTagCompiler.php @@ -6,12 +6,12 @@ use Hypervel\Container\Container; use Hypervel\Context\Context; -use Hypervel\Foundation\Contracts\Application; -use Hypervel\View\Contracts\Factory; use Hypervel\Filesystem\Filesystem; +use Hypervel\Foundation\Contracts\Application; use Hypervel\Support\Collection; use Hypervel\Support\Str; use Hypervel\View\AnonymousComponent; +use Hypervel\View\Contracts\Factory; use Hypervel\View\DynamicComponent; use Hypervel\View\ViewFinderInterface; use InvalidArgumentException; @@ -37,7 +37,7 @@ public function __construct( protected array $namespaces = [], ?BladeCompiler $blade = null ) { - $this->blade = $blade ?: new BladeCompiler(new Filesystem, sys_get_temp_dir()); + $this->blade = $blade ?: new BladeCompiler(new Filesystem(), sys_get_temp_dir()); } /** @@ -59,9 +59,7 @@ public function compileTags(string $value): string { $value = $this->compileSelfClosingTags($value); $value = $this->compileOpeningTags($value); - $value = $this->compileClosingTags($value); - - return $value; + return $this->compileClosingTags($value); } /** @@ -73,46 +71,46 @@ protected function compileOpeningTags(string $value): string { $pattern = "/ < - \s* - x[-\:]([\w\-\:\.]*) + \\s* + x[-\\:]([\\w\\-\\:\\.]*) (? (?: - \s+ + \\s+ (?: (?: - @(?:class)(\( (?: (?>[^()]+) | (?-1) )* \)) + @(?:class)(\\( (?: (?>[^()]+) | (?-1) )* \\)) ) | (?: - @(?:style)(\( (?: (?>[^()]+) | (?-1) )* \)) + @(?:style)(\\( (?: (?>[^()]+) | (?-1) )* \\)) ) | (?: - \{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\} + \\{\\{\\s*\\\$attributes(?:[^}]+?)?\\s*\\}\\} ) | (?: - (\:\\\$)(\w+) + (\\:\\\$)(\\w+) ) | (?: - [\w\-:.@%]+ + [\\w\\-:.@%]+ ( = (?: \\\"[^\\\"]*\\\" | - \'[^\']*\' + \\'[^\\']*\\' | - [^\'\\\"=<>]+ + [^\\'\\\"=<>]+ ) )? ) ) )* - \s* + \\s* ) - (? /x"; @@ -135,6 +133,7 @@ protected function clearBoundAttributes(): void /** * Set a bound attribute for the current component. + * @param mixed $attribute */ protected function setBoundAttribute($attribute): void { @@ -160,47 +159,47 @@ protected function compileSelfClosingTags(string $value): string { $pattern = "/ < - \s* - x[-\:]([\w\-\:\.]*) - \s* + \\s* + x[-\\:]([\\w\\-\\:\\.]*) + \\s* (? (?: - \s+ + \\s+ (?: (?: - @(?:class)(\( (?: (?>[^()]+) | (?-1) )* \)) + @(?:class)(\\( (?: (?>[^()]+) | (?-1) )* \\)) ) | (?: - @(?:style)(\( (?: (?>[^()]+) | (?-1) )* \)) + @(?:style)(\\( (?: (?>[^()]+) | (?-1) )* \\)) ) | (?: - \{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\} + \\{\\{\\s*\\\$attributes(?:[^}]+?)?\\s*\\}\\} ) | (?: - (\:\\\$)(\w+) + (\\:\\\$)(\\w+) ) | (?: - [\w\-:.@%]+ + [\\w\\-:.@%]+ ( = (?: \\\"[^\\\"]*\\\" | - \'[^\']*\' + \\'[^\\']*\\' | - [^\'\\\"=<>]+ + [^\\'\\\"=<>]+ ) )? ) ) )* - \s* + \\s* ) - \/> + \\/> /x"; return preg_replace_callback($pattern, function (array $matches) { @@ -208,7 +207,7 @@ protected function compileSelfClosingTags(string $value): string $attributes = $this->getAttributesFromAttributeString($matches['attributes']); - return $this->componentString($matches[1], $attributes)."\n@endComponentClass##END-COMPONENT-CLASS##"; + return $this->componentString($matches[1], $attributes) . "\n@endComponentClass##END-COMPONENT-CLASS##"; }, $value); } @@ -233,11 +232,11 @@ protected function componentString(string $component, array $attributes): string if (! class_exists($class)) { $view = Str::startsWith($component, 'mail::') ? "\$__env->getContainer()->make(Hypervel\\View\\Factory::class)->make('{$component}')" - : "'$class'"; + : "'{$class}'"; $parameters = [ 'view' => $view, - 'data' => '['.$this->attributesToString($data->all(), $escapeBound = false).']', + 'data' => '[' . $this->attributesToString($data->all(), $escapeBound = false) . ']', ]; $class = AnonymousComponent::class; @@ -245,11 +244,11 @@ protected function componentString(string $component, array $attributes): string $parameters = $data->all(); } - return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).']) + return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', [" . $this->attributesToString($parameters, $escapeBound = false) . ']) -except(\\'.$class.'::ignoredParameterNames()); ?> +except(\\' . $class . '::ignoredParameterNames()); ?> -withAttributes(['.$this->attributesToString($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>'; +withAttributes([' . $this->attributesToString($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class) . ']); ?>'; } /** @@ -283,12 +282,12 @@ public function componentClass(string $component): string return $class; } - if (class_exists($class = $class.'\\'.Str::afterLast($class, '\\'))) { + if (class_exists($class = $class . '\\' . Str::afterLast($class, '\\'))) { return $class; } - if (! is_null($guess = $this->guessAnonymousComponentUsingNamespaces($viewFactory, $component)) || - ! is_null($guess = $this->guessAnonymousComponentUsingPaths($viewFactory, $component))) { + if (! is_null($guess = $this->guessAnonymousComponentUsingNamespaces($viewFactory, $component)) + || ! is_null($guess = $this->guessAnonymousComponentUsingPaths($viewFactory, $component))) { return $guess; } @@ -310,25 +309,24 @@ protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, strin foreach ($this->blade->getAnonymousComponentPaths() as $path) { try { - if (str_contains($component, $delimiter) && - ! str_starts_with($component, $path['prefix'].$delimiter)) { + if (str_contains($component, $delimiter) + && ! str_starts_with($component, $path['prefix'] . $delimiter)) { continue; } - $formattedComponent = str_starts_with($component, $path['prefix'].$delimiter) + $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, - $viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent.'.'.Str::afterLast($formattedComponent, '.')) => $guess, + $viewFactory->exists($guess = $path['prefixHash'] . $delimiter . $formattedComponent) => $guess, + $viewFactory->exists($guess = $path['prefixHash'] . $delimiter . $formattedComponent . '.index') => $guess, + $viewFactory->exists($guess = $path['prefixHash'] . $delimiter . $formattedComponent . '.' . Str::afterLast($formattedComponent, '.')) => $guess, default => null, })) { return $guess; } } catch (InvalidArgumentException) { - // } } @@ -342,7 +340,7 @@ protected function guessAnonymousComponentUsingNamespaces(Factory $viewFactory, { return (new Collection($this->blade->getAnonymousComponentNamespaces())) ->filter(function ($directory, $prefix) use ($component) { - return Str::startsWith($component, $prefix.'::'); + return Str::startsWith($component, $prefix . '::'); }) ->prepend('components', $component) ->reduce(function ($carry, $directory, $prefix) use ($component, $viewFactory) { @@ -350,19 +348,19 @@ protected function guessAnonymousComponentUsingNamespaces(Factory $viewFactory, return $carry; } - $componentName = Str::after($component, $prefix.'::'); + $componentName = Str::after($component, $prefix . '::'); if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory))) { return $view; } - if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory).'.index')) { + if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory) . '.index')) { return $view; } $lastViewSegment = Str::afterLast(Str::afterLast($componentName, '.'), ':'); - if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory).'.'.$lastViewSegment)) { + if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory) . '.' . $lastViewSegment)) { return $view; } }); @@ -381,7 +379,7 @@ public function findClassByComponent(string $component): ?string return null; } - if (class_exists($class = $this->namespaces[$prefix].'\\'.$this->formatClassName($segments[1]))) { + if (class_exists($class = $this->namespaces[$prefix] . '\\' . $this->formatClassName($segments[1]))) { return $class; } @@ -399,7 +397,7 @@ public function guessClassName(string $component): string $class = $this->formatClassName($component); - return $namespace.'View\\Components\\'.$class; + return $namespace . 'View\Components\\' . $class; } /** @@ -426,10 +424,10 @@ public function guessViewName(string $name, string $prefix = 'components.'): str $delimiter = ViewFinderInterface::HINT_PATH_DELIMITER; if (str_contains($name, $delimiter)) { - return Str::replaceFirst($delimiter, $delimiter.$prefix, $name); + return Str::replaceFirst($delimiter, $delimiter . $prefix, $name); } - return $prefix.$name; + return $prefix . $name; } /** @@ -460,7 +458,7 @@ public function partitionDataAndAttributes(string $class, array $attributes): ar */ protected function compileClosingTags(string $value): string { - return preg_replace("/<\/\s*x[-\:][\w\-\:\.]*\s*>/", ' @endComponentClass##END-COMPONENT-CLASS##', $value); + return preg_replace('/<\\/\\s*x[-\\:][\\w\\-\\:\\.]*\\s*>/', ' @endComponentClass##END-COMPONENT-CLASS##', $value); } /** @@ -470,45 +468,45 @@ public function compileSlots(string $value): string { $pattern = "/ < - \s* - x[\-\:]slot - (?:\:(?\w+(?:-\w+)*))? - (?:\s+name=(?(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))? - (?:\s+\:name=(?(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))? + \\s* + x[\\-\\:]slot + (?:\\:(?\\w+(?:-\\w+)*))? + (?:\\s+name=(?(\"[^\"]+\"|\\\\'[^\\\\']+\\\\'|[^\\s>]+)))? + (?:\\s+\\:name=(?(\"[^\"]+\"|\\\\'[^\\\\']+\\\\'|[^\\s>]+)))? (? (?: - \s+ + \\s+ (?: (?: - @(?:class)(\( (?: (?>[^()]+) | (?-1) )* \)) + @(?:class)(\\( (?: (?>[^()]+) | (?-1) )* \\)) ) | (?: - @(?:style)(\( (?: (?>[^()]+) | (?-1) )* \)) + @(?:style)(\\( (?: (?>[^()]+) | (?-1) )* \\)) ) | (?: - \{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\} + \\{\\{\\s*\\\$attributes(?:[^}]+?)?\\s*\\}\\} ) | (?: - [\w\-:.@]+ + [\\w\\-:.@]+ ( = (?: \\\"[^\\\"]*\\\" | - \'[^\']*\' + \\'[^\\']*\\' | - [^\'\\\"=<>]+ + [^\\'\\\"=<>]+ ) )? ) ) )* - \s* + \\s* ) - (? /x"; @@ -531,11 +529,11 @@ public function compileSlots(string $value): string // If an inline name was provided and a name or bound name was *also* provided, we will assume the name should be an attribute... if (! empty($matches['inlineName']) && (! empty($matches['name']) || ! empty($matches['boundName']))) { $attributes = ! empty($matches['name']) - ? array_merge($attributes, $this->getAttributesFromAttributeString('name='.$matches['name'])) - : array_merge($attributes, $this->getAttributesFromAttributeString(':name='.$matches['boundName'])); + ? array_merge($attributes, $this->getAttributesFromAttributeString('name=' . $matches['name'])) + : array_merge($attributes, $this->getAttributesFromAttributeString(':name=' . $matches['boundName'])); } - return " @slot({$name}, null, [".$this->attributesToString($attributes).']) '; + return " @slot({$name}, null, [" . $this->attributesToString($attributes) . ']) '; }, $value); return preg_replace('/<\/\s*x[\-\:]slot[^>]*>/', ' @endslot', $value); @@ -589,7 +587,7 @@ protected function getAttributesFromAttributeString(string $attributeString): ar $this->setBoundAttribute($attribute); } else { - $value = "'".$this->compileAttributeEchos($value)."'"; + $value = "'" . $this->compileAttributeEchos($value) . "'"; } if (str_starts_with($attribute, '::')) { @@ -605,7 +603,7 @@ protected function getAttributesFromAttributeString(string $attributeString): ar */ protected function parseShortAttributeSyntax(string $value): string { - $pattern = "/\s\:\\\$(\w+)/x"; + $pattern = '/\\s\\:\\$(\\w+)/x'; return preg_replace_callback($pattern, function (array $matches) { return " :{$matches[1]}=\"\${$matches[1]}\""; @@ -617,10 +615,10 @@ protected function parseShortAttributeSyntax(string $value): string */ protected function parseAttributeBag(string $attributeString): string { - $pattern = "/ - (?:^|\s+) # start of the string or whitespace between attributes - \{\{\s*(\\\$attributes(?:[^}]+?(?[^()]+) | (?2) )* \))/x', function ($match) { + '/@(class)(\( ( (?>[^()]+) | (?2) )* \))/x', + function ($match) { if ($match[1] === 'class') { $match[2] = str_replace('"', "'", $match[2]); - return ":class=\"\Hypervel\Support\Arr::toCssClasses{$match[2]}\""; + return ":class=\"\\Hypervel\\Support\\Arr::toCssClasses{$match[2]}\""; } return $match[0]; - }, $attributeString + }, + $attributeString ); } @@ -649,15 +649,17 @@ protected function parseComponentTagClassStatements(string $attributeString): st protected function parseComponentTagStyleStatements(string $attributeString): string { return preg_replace_callback( - '/@(style)(\( ( (?>[^()]+) | (?2) )* \))/x', function ($match) { + '/@(style)(\( ( (?>[^()]+) | (?2) )* \))/x', + function ($match) { if ($match[1] === 'style') { $match[2] = str_replace('"', "'", $match[2]); - return ":style=\"\Hypervel\Support\Arr::toCssStyles{$match[2]}\""; + return ":style=\"\\Hypervel\\Support\\Arr::toCssStyles{$match[2]}\""; } return $match[0]; - }, $attributeString + }, + $attributeString ); } @@ -666,12 +668,12 @@ protected function parseComponentTagStyleStatements(string $attributeString): st */ protected function parseBindAttributes(string $attributeString): string { - $pattern = "/ - (?:^|\s+) # start of the string or whitespace between attributes + $pattern = '/ + (?:^|\\s+) # start of the string or whitespace between attributes :(?!:) # attribute needs to start with a single colon - ([\w\-:.@]+) # match the actual attribute name + ([\\w\\-:.@]+) # match the actual attribute name = # only match attributes that have a value - /xm"; + /xm'; return preg_replace($pattern, ' bind:$1=', $attributeString); } @@ -688,9 +690,7 @@ protected function compileAttributeEchos(string $attributeString): string $value = $this->escapeSingleQuotesOutsideOfPhpBlocks($value); $value = str_replace('', '.\'', $value); - - return $value; + return str_replace('; ?>', '.\'', $value); } /** @@ -719,7 +719,7 @@ protected function attributesToString(array $attributes, bool $escapeBound = tru return (new Collection($attributes)) ->map(function (string $value, string $attribute) use ($escapeBound, $boundAttributes) { return $escapeBound && isset($boundAttributes[$attribute]) && $value !== 'true' && ! is_numeric($value) - ? "'{$attribute}' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute({$value})" + ? "'{$attribute}' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute({$value})" : "'{$attribute}' => {$value}"; }) ->implode(','); diff --git a/src/view/src/Compilers/Concerns/CompilesAuthorizations.php b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php index cf36db2e8..58a8270c1 100644 --- a/src/view/src/Compilers/Concerns/CompilesAuthorizations.php +++ b/src/view/src/Compilers/Concerns/CompilesAuthorizations.php @@ -11,7 +11,7 @@ trait CompilesAuthorizations */ protected function compileCan(string $expression): string { - return "check{$expression}): ?>"; + return "check{$expression}): ?>"; } /** @@ -19,7 +19,7 @@ protected function compileCan(string $expression): string */ protected function compileCannot(string $expression): string { - return "denies{$expression}): ?>"; + return "denies{$expression}): ?>"; } /** @@ -27,7 +27,7 @@ protected function compileCannot(string $expression): string */ protected function compileCanany(string $expression): string { - return "any{$expression}): ?>"; + return "any{$expression}): ?>"; } /** @@ -35,7 +35,7 @@ protected function compileCanany(string $expression): string */ protected function compileElsecan(string $expression): string { - return "check{$expression}): ?>"; + return "check{$expression}): ?>"; } /** @@ -43,7 +43,7 @@ protected function compileElsecan(string $expression): string */ protected function compileElsecannot(string $expression): string { - return "denies{$expression}): ?>"; + return "denies{$expression}): ?>"; } /** @@ -51,7 +51,7 @@ protected function compileElsecannot(string $expression): string */ protected function compileElsecanany(string $expression): string { - return "any{$expression}): ?>"; + return "any{$expression}): ?>"; } /** diff --git a/src/view/src/Compilers/Concerns/CompilesClasses.php b/src/view/src/Compilers/Concerns/CompilesClasses.php index eb9653850..f2b14436c 100644 --- a/src/view/src/Compilers/Concerns/CompilesClasses.php +++ b/src/view/src/Compilers/Concerns/CompilesClasses.php @@ -13,6 +13,6 @@ protected function compileClass(?string $expression): string { $expression = is_null($expression) ? '([])' : $expression; - return "class=\"\""; + return "class=\"\""; } } diff --git a/src/view/src/Compilers/Concerns/CompilesComponents.php b/src/view/src/Compilers/Concerns/CompilesComponents.php index 465abfc00..cea46bc25 100644 --- a/src/view/src/Compilers/Concerns/CompilesComponents.php +++ b/src/view/src/Compilers/Concerns/CompilesComponents.php @@ -30,7 +30,7 @@ protected function compileComponent(string $expression): string $component = trim($component, '\'"'); $hash = static::newComponentHash( - $component === AnonymousComponent::class ? $component.':'.trim($alias, '\'"') : $component + $component === AnonymousComponent::class ? $component . ':' . trim($alias, '\'"') : $component ); if (Str::contains($component, ['::class', '\\'])) { @@ -62,10 +62,10 @@ public static function newComponentHash(string $component): string public static function compileClassComponentOpening(string $component, string $alias, string $data, string $hash): string { return implode("\n", [ - '', - '', - 'all() : [])); ?>', - 'withName('.$alias.'); ?>', + '', + '', + 'all() : [])); ?>', + 'withName(' . $alias . '); ?>', 'shouldRender()): ?>', 'startComponent($component->resolveView(), $component->data()); ?>', ]); @@ -86,15 +86,15 @@ public function compileEndComponentClass(): string { $hash = $this->popComponentHashStack(); - return $this->compileEndComponent()."\n".implode("\n", [ + return $this->compileEndComponent() . "\n" . implode("\n", [ '', - '', - '', - '', + '', + '', + '', '', - '', - '', - '', + '', + '', + '', '', ]); } @@ -150,7 +150,7 @@ protected function compileProps(string $expression): string return "all() as \$__key => \$__value) { if (in_array(\$__key, \$__propNames)) { @@ -160,7 +160,7 @@ protected function compileProps(string $expression): string } } -\$attributes = new \Hypervel\View\ComponentAttributeBag(\$__newAttributes); +\$attributes = new \\Hypervel\\View\\ComponentAttributeBag(\$__newAttributes); unset(\$__propNames); unset(\$__newAttributes); @@ -198,8 +198,8 @@ public static function sanitizeComponentAttribute(mixed $value): mixed return $value->escapeWhenCastingToString(); } - return is_string($value) || - (is_object($value) && ! $value instanceof Model && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString')) + return is_string($value) + || (is_object($value) && ! $value instanceof Model && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString')) ? e($value) : $value; } diff --git a/src/view/src/Compilers/Concerns/CompilesConditionals.php b/src/view/src/Compilers/Concerns/CompilesConditionals.php index 84ec716a6..9a7becb24 100644 --- a/src/view/src/Compilers/Concerns/CompilesConditionals.php +++ b/src/view/src/Compilers/Concerns/CompilesConditionals.php @@ -227,9 +227,9 @@ protected function compileEndSwitch(): string */ protected function compileOnce(?string $id = null): string { - $id = $id ? $this->stripParentheses($id) : "'".(string) Str::uuid()."'"; + $id = $id ? $this->stripParentheses($id) : "'" . (string) Str::uuid() . "'"; - return 'hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); ?>'; + return 'hasRenderedOnce(' . $id . ')): $__env->markAsRenderedOnce(' . $id . '); ?>'; } /** @@ -245,7 +245,7 @@ public function compileEndOnce(): string */ protected function compileBool(string $condition): string { - return ""; + return ""; } /** diff --git a/src/view/src/Compilers/Concerns/CompilesEchos.php b/src/view/src/Compilers/Concerns/CompilesEchos.php index a43e062e5..4dd0d2fea 100644 --- a/src/view/src/Compilers/Concerns/CompilesEchos.php +++ b/src/view/src/Compilers/Concerns/CompilesEchos.php @@ -34,7 +34,7 @@ public function stringable(string|callable $class, ?callable $handler = null): v public function compileEchos(string $value): string { foreach ($this->getEchoMethods() as $method) { - $value = $this->$method($value); + $value = $this->{$method}($value); } return $value; @@ -62,7 +62,7 @@ protected function compileRawEchos(string $value): string $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]; + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; return $matches[1] ? substr($matches[0], 1) @@ -80,7 +80,7 @@ protected function compileRegularEchos(string $value): string $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]; + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; $wrapped = sprintf($this->getEchoFormat(), $this->wrapInEchoHandler($matches[2])); @@ -98,7 +98,7 @@ protected function compileEscapedEchos(string $value): string $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]; + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; return $matches[1] ? $matches[0] @@ -113,7 +113,7 @@ protected function compileEscapedEchos(string $value): string */ protected function addBladeCompilerVariable(string $result): string { - return "".$result; + return "" . $result; } /** @@ -127,7 +127,7 @@ protected function wrapInEchoHandler(string $value): string return $str->beforeLast(';'); }); - return empty($this->echoHandlers) ? (string) $value : '$__bladeCompiler->applyEchoHandler('.$value.')'; + return empty($this->echoHandlers) ? (string) $value : '$__bladeCompiler->applyEchoHandler(' . $value . ')'; } /** diff --git a/src/view/src/Compilers/Concerns/CompilesErrors.php b/src/view/src/Compilers/Concerns/CompilesErrors.php index 441c93817..5e5bd61e1 100644 --- a/src/view/src/Compilers/Concerns/CompilesErrors.php +++ b/src/view/src/Compilers/Concerns/CompilesErrors.php @@ -13,7 +13,7 @@ protected function compileError(string $expression): string { $expression = $this->stripParentheses($expression); - return 'getBag($__errorArgs[1] ?? \'default\'); if ($__bag->has($__errorArgs[0])) : if (isset($message)) { $__messageOriginal = $message; } diff --git a/src/view/src/Compilers/Concerns/CompilesHelpers.php b/src/view/src/Compilers/Concerns/CompilesHelpers.php index 21fd13291..94ca2a5c7 100644 --- a/src/view/src/Compilers/Concerns/CompilesHelpers.php +++ b/src/view/src/Compilers/Concerns/CompilesHelpers.php @@ -49,7 +49,7 @@ protected function compileVite(?string $arguments): string $class = Vite::class; - return ""; + return ""; } /** @@ -59,6 +59,6 @@ protected function compileViteReactRefresh(): string { $class = Vite::class; - return "reactRefresh(); ?>"; + return "reactRefresh(); ?>"; } } diff --git a/src/view/src/Compilers/Concerns/CompilesIncludes.php b/src/view/src/Compilers/Concerns/CompilesIncludes.php index 3d743a001..b94551fdb 100644 --- a/src/view/src/Compilers/Concerns/CompilesIncludes.php +++ b/src/view/src/Compilers/Concerns/CompilesIncludes.php @@ -41,7 +41,7 @@ protected function compileIncludeWhen(string $expression): string { $expression = $this->stripParentheses($expression); - return "renderWhen($expression, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; + return "renderWhen({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; } /** @@ -51,7 +51,7 @@ protected function compileIncludeUnless(string $expression): string { $expression = $this->stripParentheses($expression); - return "renderUnless($expression, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; + return "renderUnless({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; } /** diff --git a/src/view/src/Compilers/Concerns/CompilesInjections.php b/src/view/src/Compilers/Concerns/CompilesInjections.php index 4c011b2cd..b5eb9d074 100644 --- a/src/view/src/Compilers/Concerns/CompilesInjections.php +++ b/src/view/src/Compilers/Concerns/CompilesInjections.php @@ -11,7 +11,7 @@ trait CompilesInjections */ protected function compileInject(string $expression): string { - $segments = explode(',', preg_replace("/[\(\)]/", '', $expression)); + $segments = explode(',', preg_replace('/[\\(\\)]/', '', $expression)); $variable = trim($segments[0], " '\""); diff --git a/src/view/src/Compilers/Concerns/CompilesJs.php b/src/view/src/Compilers/Concerns/CompilesJs.php index 4ebf6497a..fe4179d29 100644 --- a/src/view/src/Compilers/Concerns/CompilesJs.php +++ b/src/view/src/Compilers/Concerns/CompilesJs.php @@ -14,8 +14,9 @@ trait CompilesJs protected function compileJs(string $expression): string { return sprintf( - "toHtml() ?>", - Js::class, $this->stripParentheses($expression) + 'toHtml() ?>', + Js::class, + $this->stripParentheses($expression) ); } } diff --git a/src/view/src/Compilers/Concerns/CompilesJson.php b/src/view/src/Compilers/Concerns/CompilesJson.php index d9042fe66..0df43d4a1 100644 --- a/src/view/src/Compilers/Concerns/CompilesJson.php +++ b/src/view/src/Compilers/Concerns/CompilesJson.php @@ -22,6 +22,6 @@ protected function compileJson(string $expression): string $depth = isset($parts[2]) ? trim($parts[2]) : 512; - return ""; + return ""; } } diff --git a/src/view/src/Compilers/Concerns/CompilesLoops.php b/src/view/src/Compilers/Concerns/CompilesLoops.php index 432fa5891..0ffda4412 100644 --- a/src/view/src/Compilers/Concerns/CompilesLoops.php +++ b/src/view/src/Compilers/Concerns/CompilesLoops.php @@ -131,7 +131,7 @@ protected function compileBreak(?string $expression = null): string if ($expression) { preg_match('/\(\s*(-?\d+)\s*\)$/', $expression, $matches); - return $matches ? '' : ""; + return $matches ? '' : ""; } return ''; @@ -145,7 +145,7 @@ protected function compileContinue(?string $expression): string if ($expression) { preg_match('/\(\s*(-?\d+)\s*\)$/', $expression, $matches); - return $matches ? '' : ""; + return $matches ? '' : ""; } return ''; diff --git a/src/view/src/Compilers/Concerns/CompilesSessions.php b/src/view/src/Compilers/Concerns/CompilesSessions.php index 4c38d175c..7f2327da9 100644 --- a/src/view/src/Compilers/Concerns/CompilesSessions.php +++ b/src/view/src/Compilers/Concerns/CompilesSessions.php @@ -13,7 +13,7 @@ protected function compileSession(string $expression): string { $expression = $this->stripParentheses($expression); - return 'has($__sessionArgs[0])) : if (isset($value)) { $__sessionPrevious[] = $value; } $value = session()->get($__sessionArgs[0]); ?>'; diff --git a/src/view/src/Compilers/Concerns/CompilesStacks.php b/src/view/src/Compilers/Concerns/CompilesStacks.php index d99106c1f..e442f7d5d 100644 --- a/src/view/src/Compilers/Concerns/CompilesStacks.php +++ b/src/view/src/Compilers/Concerns/CompilesStacks.php @@ -33,10 +33,10 @@ protected function compilePushOnce(string $expression): string [$stack, $id] = [$parts[0], $parts[1] ?? '']; - $id = trim($id) ?: "'".(string) Str::uuid()."'"; + $id = trim($id) ?: "'" . (string) Str::uuid() . "'"; - return 'hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); -$__env->startPush('.$stack.'); ?>'; + return 'hasRenderedOnce(' . $id . ')): $__env->markAsRenderedOnce(' . $id . '); +$__env->startPush(' . $stack . '); ?>'; } /** @@ -72,10 +72,10 @@ protected function compilePrependOnce(string $expression): string [$stack, $id] = [$parts[0], $parts[1] ?? '']; - $id = trim($id) ?: "'".(string) Str::uuid()."'"; + $id = trim($id) ?: "'" . (string) Str::uuid() . "'"; - return 'hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); -$__env->startPrepend('.$stack.'); ?>'; + return 'hasRenderedOnce(' . $id . ')): $__env->markAsRenderedOnce(' . $id . '); +$__env->startPrepend(' . $stack . '); ?>'; } /** diff --git a/src/view/src/Compilers/Concerns/CompilesStyles.php b/src/view/src/Compilers/Concerns/CompilesStyles.php index ec5c912dd..a7aaa623c 100644 --- a/src/view/src/Compilers/Concerns/CompilesStyles.php +++ b/src/view/src/Compilers/Concerns/CompilesStyles.php @@ -13,6 +13,6 @@ protected function compileStyle(?string $expression): string { $expression = is_null($expression) ? '([])' : $expression; - return "style=\"\""; + return "style=\"\""; } } diff --git a/src/view/src/Compilers/Concerns/CompilesTranslations.php b/src/view/src/Compilers/Concerns/CompilesTranslations.php index 0d6c27226..e4da946d3 100644 --- a/src/view/src/Compilers/Concerns/CompilesTranslations.php +++ b/src/view/src/Compilers/Concerns/CompilesTranslations.php @@ -13,7 +13,8 @@ protected function compileLang(?string $expression): string { if (is_null($expression)) { return 'startTranslation(); ?>'; - } elseif ($expression[1] === '[') { + } + if ($expression[1] === '[') { return "startTranslation{$expression}; ?>"; } diff --git a/src/view/src/Compilers/Concerns/CompilesUseStatements.php b/src/view/src/Compilers/Concerns/CompilesUseStatements.php index f634f846c..e1d6dbe63 100644 --- a/src/view/src/Compilers/Concerns/CompilesUseStatements.php +++ b/src/view/src/Compilers/Concerns/CompilesUseStatements.php @@ -11,10 +11,10 @@ trait CompilesUseStatements */ protected function compileUse(string $expression): string { - $segments = explode(',', preg_replace("/[\(\)]/", '', $expression)); + $segments = explode(',', preg_replace('/[\\(\\)]/', '', $expression)); $use = ltrim(trim($segments[0], " '\""), '\\'); - $as = isset($segments[1]) ? ' as '.trim($segments[1], " '\"") : ''; + $as = isset($segments[1]) ? ' as ' . trim($segments[1], " '\"") : ''; return ""; } diff --git a/src/view/src/Component.php b/src/view/src/Component.php index 33434f3df..2d6dd79a0 100644 --- a/src/view/src/Component.php +++ b/src/view/src/Component.php @@ -6,11 +6,11 @@ use Closure; use Hypervel\Container\Container; -use Hypervel\Support\Contracts\Htmlable; -use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Collection; use Hypervel\Support\Contracts\Arrayable; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\Contracts\Factory; +use Hypervel\View\Contracts\View as ViewContract; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; @@ -40,7 +40,7 @@ abstract class Component /** * The component resolver callback. * - * @var (Closure(string, array): Component)|null + * @var null|(Closure(string, array): Component) */ protected static ?Closure $componentsResolver = null; @@ -173,7 +173,7 @@ protected function createBladeViewFromString(Factory $factory, string $contents) $directory = Container::getInstance()['config']->get('view.compiled') ); - if (! is_file($viewFile = $directory.'/'.hash('xxh128', $contents).'.blade.php')) { + if (! is_file($viewFile = $directory . '/' . hash('xxh128', $contents) . '.blade.php')) { if (! is_dir($directory)) { mkdir($directory, 0755, true); } @@ -181,13 +181,11 @@ protected function createBladeViewFromString(Factory $factory, string $contents) file_put_contents($viewFile, $contents); } - return '__components::'.basename($viewFile, '.blade.php'); + return '__components::' . basename($viewFile, '.blade.php'); } /** * Get the data that should be supplied to the view. - * - * @return array */ public function data(): array { @@ -271,8 +269,8 @@ protected function createInvokableVariable(string $method): InvokableComponentVa */ protected function shouldIgnore(string $name): bool { - return str_starts_with($name, '__') || - in_array($name, $this->ignoredMethods()); + return str_starts_with($name, '__') + || in_array($name, $this->ignoredMethods()); } /** @@ -409,7 +407,7 @@ public static function forgetComponentsResolver(): void /** * Set the callback that should be used to resolve components within views. * - * @param Closure(string $component, array $data): Component $resolver + * @param Closure(string $component, array $data): Component $resolver * * @internal */ diff --git a/src/view/src/ComponentAttributeBag.php b/src/view/src/ComponentAttributeBag.php index c797adce9..5b20b4ab3 100644 --- a/src/view/src/ComponentAttributeBag.php +++ b/src/view/src/ComponentAttributeBag.php @@ -6,9 +6,9 @@ use ArrayAccess; use ArrayIterator; -use Hypervel\Support\Contracts\Htmlable; use Hypervel\Support\Arr; use Hypervel\Support\Collection; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\Support\HtmlString; use Hypervel\Support\Str; use Hypervel\Support\Traits\Conditionable; @@ -20,7 +20,8 @@ class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSerializable, Htmlable, Stringable { - use Conditionable, Macroable; + use Conditionable; + use Macroable; /** * The raw array of attributes. @@ -146,7 +147,7 @@ public function filter(callable $callback): static /** * Return a bag of attributes that have keys starting with the given value / pattern. * - * @param string|string[] $needles + * @param string|string[] $needles */ public function whereStartsWith(string|array $needles): static { @@ -158,7 +159,7 @@ public function whereStartsWith(string|array $needles): static /** * Return a bag of attributes with keys that do not start with the given value / pattern. * - * @param string|string[] $needles + * @param string|string[] $needles */ public function whereDoesntStartWith(string|array $needles): static { @@ -170,7 +171,7 @@ public function whereDoesntStartWith(string|array $needles): static /** * Return a bag of attributes that have keys starting with the given value / pattern. * - * @param string|string[] $needles + * @param string|string[] $needles */ public function thatStartWith(string|array $needles): static { @@ -227,8 +228,8 @@ public function merge(array $attributeDefaults = [], bool $escape = true): stati [$appendableAttributes, $nonAppendableAttributes] = (new Collection($this->attributes)) ->partition(function ($value, $key) use ($attributeDefaults) { return $key === 'class' || $key === 'style' || ( - isset($attributeDefaults[$key]) && - $attributeDefaults[$key] instanceof AppendableAttributeValue + isset($attributeDefaults[$key]) + && $attributeDefaults[$key] instanceof AppendableAttributeValue ); }); @@ -256,9 +257,9 @@ protected function shouldEscapeAttributeValue(bool $escape, mixed $value): bool return false; } - return ! is_object($value) && - ! is_null($value) && - ! is_bool($value); + return ! is_object($value) + && ! is_null($value) + && ! is_bool($value); } /** @@ -310,8 +311,8 @@ public function getAttributes(): array */ public function setAttributes(array $attributes): void { - if (isset($attributes['attributes']) && - $attributes['attributes'] instanceof self) { + if (isset($attributes['attributes']) + && $attributes['attributes'] instanceof self) { $parentBag = $attributes['attributes']; unset($attributes['attributes']); @@ -419,7 +420,7 @@ public function __toString(): string $value = $key === 'x-data' || str_starts_with($key, 'wire:') ? '' : $key; } - $string .= ' '.$key.'="'.str_replace('"', '\\"', trim((string) $value)).'"'; + $string .= ' ' . $key . '="' . str_replace('"', '\"', trim((string) $value)) . '"'; } return trim($string); diff --git a/src/view/src/ComponentSlot.php b/src/view/src/ComponentSlot.php index 1870042e4..b484cd944 100644 --- a/src/view/src/ComponentSlot.php +++ b/src/view/src/ComponentSlot.php @@ -71,7 +71,7 @@ public function hasActualContent(callable|string|null $callable = null): bool return filter_var( $this->contents, FILTER_CALLBACK, - ['options' => $callable ?? fn ($input) => trim(preg_replace("//", '', $input))] + ['options' => $callable ?? fn ($input) => trim(preg_replace('//', '', $input))] ) !== ''; } diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php index abb22b930..21a450d50 100644 --- a/src/view/src/Concerns/ManagesComponents.php +++ b/src/view/src/Concerns/ManagesComponents.php @@ -6,10 +6,10 @@ use Closure; use Hypervel\Context\Context; -use Hypervel\Support\Contracts\Htmlable; -use Hypervel\View\Contracts\View; use Hypervel\Support\Arr; +use Hypervel\Support\Contracts\Htmlable; use Hypervel\View\ComponentSlot; +use Hypervel\View\Contracts\View; trait ManagesComponents { @@ -114,11 +114,11 @@ public function renderComponent(): string if ($view instanceof View) { return $view->with($data)->render(); - } elseif ($view instanceof Htmlable) { + } + if ($view instanceof Htmlable) { return $view->toHtml(); - } else { - return $this->make($view, $data)->render(); } + return $this->make($view, $data)->render(); } finally { Context::set(static::CURRENT_COMPONENT_DATA_CONTEXT_KEY, $previousComponentData); } @@ -169,7 +169,7 @@ public function getConsumableComponentData(string $key, mixed $default = null): $componentData = Context::get(static::COMPONENT_DATA_CONTEXT_KEY, []); - for ($i = $currentComponent - 1; $i >= 0; $i--) { + for ($i = $currentComponent - 1; $i >= 0; --$i) { $data = $componentData[$i] ?? []; if (array_key_exists($key, $data)) { @@ -194,7 +194,7 @@ public function slot(string $name, ?string $content = null, array $attributes = } } - protected function setSlotData(string $name, null|string|ComponentSlot $content): void + protected function setSlotData(string $name, string|ComponentSlot|null $content): void { $currentComponent = $this->currentComponent(); @@ -233,7 +233,8 @@ public function endSlot(): void [$currentName, $currentAttributes] = $currentSlot; $this->setSlotData($currentName, new ComponentSlot( - trim(ob_get_clean()), $currentAttributes + trim(ob_get_clean()), + $currentAttributes )); } diff --git a/src/view/src/Concerns/ManagesEvents.php b/src/view/src/Concerns/ManagesEvents.php index bef364b49..233632a81 100644 --- a/src/view/src/Concerns/ManagesEvents.php +++ b/src/view/src/Concerns/ManagesEvents.php @@ -5,8 +5,8 @@ namespace Hypervel\View\Concerns; use Closure; -use Hypervel\View\Contracts\View as ViewContract; use Hypervel\Support\Str; +use Hypervel\View\Contracts\View as ViewContract; trait ManagesEvents { @@ -60,10 +60,11 @@ protected function addViewEvent(string $view, Closure|string $callback, string $ $view = $this->normalizeName($view); if ($callback instanceof Closure) { - $this->addEventListener($prefix.$view, $callback); + $this->addEventListener($prefix . $view, $callback); return $callback; - } elseif (is_string($callback)) { + } + if (is_string($callback)) { return $this->addClassEvent($view, $callback, $prefix); } } @@ -73,13 +74,14 @@ protected function addViewEvent(string $view, Closure|string $callback, string $ */ protected function addClassEvent(string $view, string $class, string $prefix): Closure { - $name = $prefix.$view; + $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 + $class, + $prefix ); $this->addEventListener($name, $callback); @@ -137,7 +139,7 @@ protected function addEventListener(string $name, Closure $callback): void */ public function callComposer(ViewContract $view): void { - if ($this->events->hasListeners($event = 'composing: '.$view->name())) { + if ($this->events->hasListeners($event = 'composing: ' . $view->name())) { $this->events->dispatch($event, [$view]); } } @@ -147,7 +149,7 @@ public function callComposer(ViewContract $view): void */ public function callCreator(ViewContract $view): void { - if ($this->events->hasListeners($event = 'creating: '.$view->name())) { + if ($this->events->hasListeners($event = 'creating: ' . $view->name())) { $this->events->dispatch($event, [$view]); } } diff --git a/src/view/src/Concerns/ManagesLayouts.php b/src/view/src/Concerns/ManagesLayouts.php index 98b4f1943..59dcbe7c3 100644 --- a/src/view/src/Concerns/ManagesLayouts.php +++ b/src/view/src/Concerns/ManagesLayouts.php @@ -5,8 +5,8 @@ namespace Hypervel\View\Concerns; use Hypervel\Context\Context; -use Hypervel\View\Contracts\View; use Hypervel\Support\Str; +use Hypervel\View\Contracts\View; use InvalidArgumentException; trait ManagesLayouts @@ -148,7 +148,9 @@ public function yieldContent(string $section, string|View $default = ''): string $sectionContent = str_replace('@@parent', '--parent--holder--', $sectionContent); return str_replace( - '--parent--holder--', '@parent', str_replace($this->getParentPlaceholder($section), '', $sectionContent) + '--parent--holder--', + '@parent', + str_replace($this->getParentPlaceholder($section), '', $sectionContent) ); } @@ -161,7 +163,7 @@ public function getParentPlaceholder(string $section = ''): string if (! isset($parentPlaceholder[$section])) { $salt = Str::random(40); - $parentPlaceholder[$section] = '##parent-placeholder-'.hash('xxh128', $salt.$section).'##'; + $parentPlaceholder[$section] = '##parent-placeholder-' . hash('xxh128', $salt . $section) . '##'; Context::set(static::PARENT_PLACEHOLDER_CONTEXT_KEY, $parentPlaceholder); } diff --git a/src/view/src/Concerns/ManagesStacks.php b/src/view/src/Concerns/ManagesStacks.php index ab0095037..28f17040b 100644 --- a/src/view/src/Concerns/ManagesStacks.php +++ b/src/view/src/Concerns/ManagesStacks.php @@ -142,7 +142,7 @@ protected function extendPrepend(string $section, string $content): void if (! isset($prepends[$section][$renderCount])) { $prepends[$section][$renderCount] = $content; } else { - $prepends[$section][$renderCount] = $content.$prepends[$section][$renderCount]; + $prepends[$section][$renderCount] = $content . $prepends[$section][$renderCount]; } Context::set(static::PREPENDS_CONTEXT_KEY, $prepends); diff --git a/src/view/src/Concerns/ManagesTranslations.php b/src/view/src/Concerns/ManagesTranslations.php index beaa9b49a..060c19cce 100644 --- a/src/view/src/Concerns/ManagesTranslations.php +++ b/src/view/src/Concerns/ManagesTranslations.php @@ -29,7 +29,8 @@ public function startTranslation(array $replacements = []): void public function renderTranslation(): string { return $this->container->make('translator')->get( - trim(ob_get_clean()), Context::get(static::TRANSLATION_REPLACEMENTS_CONTEXT_KEY, []) + trim(ob_get_clean()), + Context::get(static::TRANSLATION_REPLACEMENTS_CONTEXT_KEY, []) ); } } diff --git a/src/view/src/Contracts/ViewCompilationException.php b/src/view/src/Contracts/ViewCompilationException.php index d540290fa..4e3d1e10e 100644 --- a/src/view/src/Contracts/ViewCompilationException.php +++ b/src/view/src/Contracts/ViewCompilationException.php @@ -8,5 +8,4 @@ class ViewCompilationException extends Exception { - // } diff --git a/src/view/src/DynamicComponent.php b/src/view/src/DynamicComponent.php index 08247f7c9..5de2474b6 100644 --- a/src/view/src/DynamicComponent.php +++ b/src/view/src/DynamicComponent.php @@ -78,21 +78,18 @@ protected function compileProps(array $bindings): string return ''; } - return '@props('.'[\''.implode('\',\'', (new Collection($bindings))->map(function ($dataKey) { + return '@props([\'' . implode('\',\'', (new Collection($bindings))->map(function ($dataKey) { return Str::camel($dataKey); - })->all()).'\']'.')'; + })->all()) . '\'])'; } /** * Compile the bindings for the component. - * - * @param array $bindings - * @return string */ protected function compileBindings(array $bindings): string { return (new Collection($bindings)) - ->map(fn ($key) => ':'.$key.'="$'.Str::camel(str_replace([':', '.'], ' ', $key)).'"') + ->map(fn ($key) => ':' . $key . '="$' . Str::camel(str_replace([':', '.'], ' ', $key)) . '"') ->implode(' '); } @@ -102,7 +99,7 @@ protected function compileBindings(array $bindings): string protected function compileSlots(array $slots): string { return (new Collection($slots)) - ->map(fn ($slot, $name) => $name === '__default' ? null : 'attributes).'>{{ $'.$name.' }}') + ->map(fn ($slot, $name) => $name === '__default' ? null : 'attributes) . '>{{ $' . $name . ' }}') ->filter() ->implode(PHP_EOL); } @@ -116,8 +113,8 @@ protected function classForComponent(): string return static::$componentClasses[$this->component]; } - return static::$componentClasses[$this->component] = - $this->compiler()->componentClass($this->component); + return static::$componentClasses[$this->component] + = $this->compiler()->componentClass($this->component); } /** diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php index 983883452..1c56253be 100755 --- a/src/view/src/Engines/CompilerEngine.php +++ b/src/view/src/Engines/CompilerEngine.php @@ -30,7 +30,7 @@ public function __construct( protected CompilerInterface $compiler, ?Filesystem $files = null ) { - parent::__construct($files ?: new Filesystem); + parent::__construct($files ?: new Filesystem()); } /** @@ -91,11 +91,11 @@ protected function popCompiledPath(): void */ protected function handleViewException(Throwable $e, int $obLevel): void { - if ($e instanceof HttpException || - $e instanceof HttpResponseException || - $e instanceof MultipleRecordsFoundException || - $e instanceof RecordsNotFoundException || - $e instanceof ModelNotFoundException + if ($e instanceof HttpException + || $e instanceof HttpResponseException + || $e instanceof MultipleRecordsFoundException + || $e instanceof RecordsNotFoundException + || $e instanceof ModelNotFoundException ) { parent::handleViewException($e, $obLevel); } @@ -112,7 +112,7 @@ protected function getMessage(Throwable $e): string { $stack = Context::get(static::COMPILED_PATH_CONTEXT_KEY); - return $e->getMessage().' (View: '.realpath(last($stack)).')'; + return $e->getMessage() . ' (View: ' . realpath(last($stack)) . ')'; } /** diff --git a/src/view/src/Engines/EngineResolver.php b/src/view/src/Engines/EngineResolver.php index 07843a3d9..785cc2782 100755 --- a/src/view/src/Engines/EngineResolver.php +++ b/src/view/src/Engines/EngineResolver.php @@ -5,8 +5,8 @@ namespace Hypervel\View\Engines; use Closure; -use InvalidArgumentException; use Hypervel\View\Contracts\Engine; +use InvalidArgumentException; class EngineResolver { diff --git a/src/view/src/Engines/FileEngine.php b/src/view/src/Engines/FileEngine.php index b3c20133c..1e328e763 100644 --- a/src/view/src/Engines/FileEngine.php +++ b/src/view/src/Engines/FileEngine.php @@ -4,8 +4,8 @@ namespace Hypervel\View\Engines; -use Hypervel\View\Contracts\Engine; use Hypervel\Filesystem\Filesystem; +use Hypervel\View\Contracts\Engine; class FileEngine implements Engine { diff --git a/src/view/src/Engines/PhpEngine.php b/src/view/src/Engines/PhpEngine.php index 01d9b0ad6..7a70f2632 100755 --- a/src/view/src/Engines/PhpEngine.php +++ b/src/view/src/Engines/PhpEngine.php @@ -4,8 +4,8 @@ namespace Hypervel\View\Engines; -use Hypervel\View\Contracts\Engine; use Hypervel\Filesystem\Filesystem; +use Hypervel\View\Contracts\Engine; use Throwable; class PhpEngine implements Engine diff --git a/src/view/src/Factory.php b/src/view/src/Factory.php index 10951b950..487f576f3 100755 --- a/src/view/src/Factory.php +++ b/src/view/src/Factory.php @@ -8,17 +8,26 @@ use Hypervel\Container\Contracts\Container; use Hypervel\Context\Context; use Hypervel\Event\Contracts\Dispatcher; +use Hypervel\Support\Arr; use Hypervel\Support\Contracts\Arrayable; +use Hypervel\Support\Traits\Macroable; use Hypervel\View\Contracts\Engine; use Hypervel\View\Contracts\Factory as FactoryContract; use Hypervel\View\Contracts\View as ViewContract; -use Hypervel\Support\Arr; -use Hypervel\Support\Traits\Macroable; use Hypervel\View\Engines\EngineResolver; use InvalidArgumentException; class Factory implements FactoryContract { + use Macroable; + use Concerns\ManagesComponents; + use Concerns\ManagesEvents; + use Concerns\ManagesFragments; + use Concerns\ManagesLayouts; + use Concerns\ManagesLoops; + use Concerns\ManagesStacks; + use Concerns\ManagesTranslations; + /** * The number of active rendering operations. */ @@ -29,15 +38,6 @@ class Factory implements FactoryContract */ protected const RENDERED_ONCE_CONTEXT_KEY = 'rendered_once'; - use Macroable, - Concerns\ManagesComponents, - Concerns\ManagesEvents, - Concerns\ManagesFragments, - Concerns\ManagesLayouts, - Concerns\ManagesLoops, - Concerns\ManagesStacks, - Concerns\ManagesTranslations; - /** * The IoC container instance. */ @@ -167,7 +167,8 @@ public function renderEach(string $view, array $data, string $iterator, string $ if (count($data) > 0) { foreach ($data as $key => $value) { $result .= $this->make( - $view, ['key' => $key, $iterator => $value] + $view, + ['key' => $key, $iterator => $value] )->render(); } } @@ -250,7 +251,7 @@ protected function getExtension(string $path): ?string $extensions = array_keys($this->extensions); return Arr::first($extensions, function ($value) use ($path) { - return str_ends_with($path, '.'.$value); + return str_ends_with($path, '.' . $value); }); } diff --git a/src/view/src/FileViewFinder.php b/src/view/src/FileViewFinder.php index 6493bc441..28d7f2525 100755 --- a/src/view/src/FileViewFinder.php +++ b/src/view/src/FileViewFinder.php @@ -101,7 +101,7 @@ protected function findInPaths(string $name, array $paths): string { foreach ((array) $paths as $path) { foreach ($this->getPossibleViewFiles($name) as $file) { - $viewPath = $path.'/'.$file; + $viewPath = $path . '/' . $file; if (strlen($viewPath) < (PHP_MAXPATHLEN - 1) && $this->files->exists($viewPath)) { return $viewPath; @@ -117,7 +117,7 @@ protected function findInPaths(string $name, array $paths): string */ protected function getPossibleViewFiles(string $name): array { - return array_map(fn ($extension) => str_replace('.', '/', $name).'.'.$extension, $this->extensions); + return array_map(fn ($extension) => str_replace('.', '/', $name) . '.' . $extension, $this->extensions); } /** diff --git a/src/view/src/Middleware/ShareErrorsFromSession.php b/src/view/src/Middleware/ShareErrorsFromSession.php index 925763ffd..41701f51e 100644 --- a/src/view/src/Middleware/ShareErrorsFromSession.php +++ b/src/view/src/Middleware/ShareErrorsFromSession.php @@ -6,8 +6,8 @@ use Closure; use Hyperf\Contract\SessionInterface; -use Hypervel\View\Contracts\Factory as ViewFactory; use Hypervel\Support\ViewErrorBag; +use Hypervel\View\Contracts\Factory as ViewFactory; class ShareErrorsFromSession { @@ -29,7 +29,8 @@ public function handle(mixed $request, Closure $next): mixed // 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', $this->session->get('errors') ?: new ViewErrorBag + 'errors', + $this->session->get('errors') ?: new ViewErrorBag() ); // Putting the errors in the view for every view allows the developer to just diff --git a/src/view/src/View.php b/src/view/src/View.php index b76e61057..52f7aad8a 100755 --- a/src/view/src/View.php +++ b/src/view/src/View.php @@ -7,17 +7,17 @@ use ArrayAccess; use BadMethodCallException; use Hyperf\Contract\MessageProvider; +use Hypervel\Support\Collection; use Hypervel\Support\Contracts\Arrayable; use Hypervel\Support\Contracts\Htmlable; use Hypervel\Support\Contracts\Renderable; -use Hypervel\View\Contracts\Engine; -use Hypervel\View\Contracts\View as ViewContract; -use Hypervel\Support\Collection; use Hypervel\Support\HtmlString; use Hypervel\Support\MessageBag; use Hypervel\Support\Str; use Hypervel\Support\Traits\Macroable; use Hypervel\Support\ViewErrorBag; +use Hypervel\View\Contracts\Engine; +use Hypervel\View\Contracts\View as ViewContract; use Stringable; use Throwable; @@ -225,8 +225,9 @@ public function nest(string $key, string $view, array $data = []): static */ public function withErrors(MessageProvider|array|string $provider, string $bag = 'default'): static { - return $this->with('errors', (new ViewErrorBag)->put( - $bag, $this->formatErrors($provider) + return $this->with('errors', (new ViewErrorBag())->put( + $bag, + $this->formatErrors($provider) )); } @@ -375,7 +376,9 @@ public function __call(string $method, array $parameters): mixed if (! str_starts_with($method, 'with')) { throw new BadMethodCallException(sprintf( - 'Method %s::%s does not exist.', static::class, $method + 'Method %s::%s does not exist.', + static::class, + $method )); } diff --git a/src/view/src/ViewFinderInterface.php b/src/view/src/ViewFinderInterface.php index 70982d922..1a457e9c4 100755 --- a/src/view/src/ViewFinderInterface.php +++ b/src/view/src/ViewFinderInterface.php @@ -9,7 +9,7 @@ interface ViewFinderInterface /** * Hint path delimiter value. */ - const string HINT_PATH_DELIMITER = '::'; + public const string HINT_PATH_DELIMITER = '::'; /** * Get the fully qualified location of the view. diff --git a/src/view/src/ViewName.php b/src/view/src/ViewName.php index 658cd2411..4d88b8bdc 100644 --- a/src/view/src/ViewName.php +++ b/src/view/src/ViewName.php @@ -19,6 +19,6 @@ public static function normalize(string $name): string [$namespace, $name] = explode($delimiter, $name); - return $namespace.$delimiter.str_replace('/', '.', $name); + return $namespace . $delimiter . str_replace('/', '.', $name); } } diff --git a/src/view/src/ViewServiceProvider.php b/src/view/src/ViewServiceProvider.php index f6cc12d8f..49f7ff033 100755 --- a/src/view/src/ViewServiceProvider.php +++ b/src/view/src/ViewServiceProvider.php @@ -12,8 +12,8 @@ use Hypervel\Foundation\Application; use Hypervel\Support\Arr; use Hypervel\Support\ServiceProvider; -use Hypervel\View\Contracts\Factory as FactoryContract; use Hypervel\View\Compilers\BladeCompiler; +use Hypervel\View\Contracts\Factory as FactoryContract; use Hypervel\View\Engines\CompilerEngine; use Hypervel\View\Engines\EngineResolver; use Hypervel\View\Engines\FileEngine; @@ -105,13 +105,13 @@ protected function registerBladeCompiler(): void protected function registerEngineResolver(): void { $this->app->bind('view.engine.resolver', function () { - $resolver = new EngineResolver; + $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); + $this->{'register' . ucfirst($engine) . 'Engine'}($resolver); } return $resolver; @@ -146,12 +146,10 @@ protected function registerBladeEngine(EngineResolver $resolver): void $resolver->register('blade', function () { $app = Container::getInstance(); - $compiler = new CompilerEngine( + return new CompilerEngine( $app->get('blade.compiler'), $app->get('files'), ); - - return $compiler; }); } @@ -176,7 +174,7 @@ protected function compatibleWithHyperfView(): void 'components', ]); $hyperfViewConfig = $customHyperfViewConfig + [ - 'engine' => HyperfViewEngine::class, + 'engine' => HyperfViewEngine::class, 'mode' => Mode::SYNC, 'config' => [ 'view_path' => base_path('resources/views'), diff --git a/tests/View/Blade/AbstractBladeTestCase.php b/tests/View/Blade/AbstractBladeTestCase.php index becc606f8..a0cafae9b 100644 --- a/tests/View/Blade/AbstractBladeTestCase.php +++ b/tests/View/Blade/AbstractBladeTestCase.php @@ -1,5 +1,7 @@ check(\'update\', [$post])): ?> + $expected = 'check(\'update\', [$post])): ?> breeze -check(\'delete\', [$post])): ?> +check(\'delete\', [$post])): ?> sneeze '; $this->assertEquals($expected, $this->compiler->compileString($string)); diff --git a/tests/View/Blade/BladeCananyStatementsTest.php b/tests/View/Blade/BladeCananyStatementsTest.php index d6a89136d..aae18ffc3 100644 --- a/tests/View/Blade/BladeCananyStatementsTest.php +++ b/tests/View/Blade/BladeCananyStatementsTest.php @@ -1,7 +1,13 @@ any([\'create\', \'update\'], [$post])): ?> + $expected = 'any([\'create\', \'update\'], [$post])): ?> breeze -any([\'delete\', \'approve\'], [$post])): ?> +any([\'delete\', \'approve\'], [$post])): ?> sneeze '; $this->assertEquals($expected, $this->compiler->compileString($string)); diff --git a/tests/View/Blade/BladeCannotStatementsTest.php b/tests/View/Blade/BladeCannotStatementsTest.php index 207d34b95..cf0e49b82 100644 --- a/tests/View/Blade/BladeCannotStatementsTest.php +++ b/tests/View/Blade/BladeCannotStatementsTest.php @@ -1,7 +1,13 @@ denies(\'update\', [$post])): ?> + $expected = 'denies(\'update\', [$post])): ?> breeze -denies(\'delete\', [$post])): ?> +denies(\'delete\', [$post])): ?> sneeze '; $this->assertEquals($expected, $this->compiler->compileString($string)); diff --git a/tests/View/Blade/BladeCheckedStatementsTest.php b/tests/View/Blade/BladeCheckedStatementsTest.php index e54a37aa2..efd99439c 100644 --- a/tests/View/Blade/BladeCheckedStatementsTest.php +++ b/tests/View/Blade/BladeCheckedStatementsTest.php @@ -1,7 +1,13 @@ true, 'mr-2' => false])>"; - $expected = " true, 'mr-2' => false]); ?>\">"; + $expected = " true, 'mr-2' => false]); ?>\">"; $this->assertEquals($expected, $this->compiler->compileString($string)); } diff --git a/tests/View/Blade/BladeCommentsTest.php b/tests/View/Blade/BladeCommentsTest.php index f5f6da058..f2b7ab7bd 100644 --- a/tests/View/Blade/BladeCommentsTest.php +++ b/tests/View/Blade/BladeCommentsTest.php @@ -1,7 +1,13 @@ '); $this->assertSame( - "@slot('foo', null, []) \n".' @endslot', + "@slot('foo', null, []) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -40,7 +46,7 @@ public function testInlineSlotsCanBeCompiled()
'); $this->assertSame( - "@slot('foo', null, []) \n".' @endslot', + "@slot('foo', null, []) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -52,7 +58,7 @@ public function testDynamicSlotsCanBeCompiled()
'); $this->assertSame( - "@slot(\$foo, null, []) \n".' @endslot', + "@slot(\$foo, null, []) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -64,7 +70,7 @@ public function testDynamicSlotsCanBeCompiledWithKeyOfObjects() '); $this->assertSame( - "@slot(\$foo->name, null, []) \n".' @endslot', + "@slot(\$foo->name, null, []) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -76,7 +82,7 @@ public function testSlotsWithAttributesCanBeCompiled() '); $this->assertSame( - "@slot('foo', null, ['class' => 'font-bold']) \n".' @endslot', + "@slot('foo', null, ['class' => 'font-bold']) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -88,7 +94,7 @@ public function testInlineSlotsWithAttributesCanBeCompiled() '); $this->assertSame( - "@slot('foo', null, ['class' => 'font-bold']) \n".' @endslot', + "@slot('foo', null, ['class' => 'font-bold']) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -100,7 +106,7 @@ public function testSlotsWithDynamicAttributesCanBeCompiled() '); $this->assertSame( - "@slot('foo', null, ['class' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$classes)]) \n".' @endslot', + "@slot('foo', null, ['class' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute(\$classes)]) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -112,7 +118,7 @@ public function testSlotsWithClassDirectiveCanBeCompiled() '); $this->assertSame( - "@slot('foo', null, ['class' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssClasses(\$classes))]) \n".' @endslot', + "@slot('foo', null, ['class' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute(\\Hypervel\\Support\\Arr::toCssClasses(\$classes))]) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -124,7 +130,7 @@ public function testSlotsWithStyleDirectiveCanBeCompiled() '); $this->assertSame( - "@slot('foo', null, ['style' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssStyles(\$styles))]) \n".' @endslot', + "@slot('foo', null, ['style' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute(\\Hypervel\\Support\\Arr::toCssStyles(\$styles))]) \n" . ' @endslot', str_replace("\r\n", "\n", trim($result)) ); } @@ -135,17 +141,17 @@ public function testBasicComponentParsing() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes(['type' => 'foo','limit' => '5','@click' => 'foo','wire:click' => 'changePlan(\''.e(\$plan).'\')','required' => true,'x-intersect.margin.-50%.0px' => 'visibleSection = \'profile\'']); ?>\n". -"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> +withAttributes(['type' => 'foo','limit' => '5','@click' => 'foo','wire:click' => 'changePlan(\\''.e(\$plan).'\\')','required' => true,'x-intersect.margin.-50%.0px' => 'visibleSection = \\'profile\\'']); ?>\n" +. "@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); +withAttributes([]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); } public function testNestedDefaultComponentParsing() @@ -154,12 +160,12 @@ public function testNestedDefaultComponentParsing() $result = $this->compiler()->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('App\View\Components\Card\Card', 'card', []) - -except(\App\View\Components\Card\Card::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('App\\View\\Components\\Card\\Card', 'card', []) + +except(\\App\\View\\Components\\Card\\Card::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); +withAttributes([]); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); } public function testBasicComponentWithEmptyAttributesParsing() @@ -167,12 +173,12 @@ public function testBasicComponentWithEmptyAttributesParsing() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes(['type' => '','limit' => '','@click' => '','required' => true]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); +withAttributes(['type' => '','limit' => '','@click' => '','required' => true]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); } public function testDataCamelCasing() @@ -180,9 +186,9 @@ public function testDataCamelCasing() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => '1']) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', ['userId' => '1']) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -192,9 +198,9 @@ public function testColonData() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', ['userId' => 1]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -204,9 +210,9 @@ public function testColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', ['userId' => \$userId]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -216,9 +222,9 @@ public function testColonDataWithStaticClassProperty() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', ['userId' => User::\$id]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -228,17 +234,17 @@ public function testColonDataWithStaticClassPropertyAndMultipleAttributes() $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) - -except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) + +except(\\Hypervel\\Tests\\View\\Blade\\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) - -except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -248,12 +254,12 @@ public function testSelfClosingComponentWithColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', ['userId' => \$userId]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes([]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testSelfClosingComponentWithColonDataAndStaticClassPropertyShortSyntax() @@ -261,12 +267,12 @@ public function testSelfClosingComponentWithColonDataAndStaticClassPropertyShort $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', ['userId' => User::\$id]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes([]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testSelfClosingComponentWithColonDataMultipleAttributesAndStaticClassPropertyShortSyntax() @@ -274,21 +280,21 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) - -except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestInputComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes([]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) - -except(\Hypervel\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) + +except(\\Hypervel\\Tests\\View\\Blade\\TestInputComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes([]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testEscapedColonAttribute() @@ -296,9 +302,9 @@ public function testEscapedColonAttribute() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', ['userId' => 1]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([':title' => 'user.name']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -308,11 +314,11 @@ public function testColonAttributesIsEscapedIfStrings() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['src' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['src' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testClassDirective() @@ -320,11 +326,11 @@ public function testClassDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['class' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['class' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute(\\Hypervel\\Support\\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testStyleDirective() @@ -332,11 +338,11 @@ public function testStyleDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['style' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Hypervel\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['style' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute(\\Hypervel\\Support\\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testColonNestedComponentParsing() @@ -344,9 +350,9 @@ public function testColonNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'foo:alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -356,9 +362,9 @@ public function testColonStartingNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'foo:alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -368,12 +374,12 @@ public function testSelfClosingComponentsCanBeCompiled() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); +withAttributes([]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); } public function testClassNamesCanBeGuessed() @@ -382,7 +388,7 @@ public function testClassNamesCanBeGuessed() $result = $this->compiler()->guessClassName('alert'); - $this->assertSame("App\View\Components\Alert", trim($result)); + $this->assertSame('App\\View\\Components\\Alert', trim($result)); } public function testClassNamesCanBeGuessedWithNamespaces() @@ -391,7 +397,7 @@ public function testClassNamesCanBeGuessedWithNamespaces() $result = $this->compiler()->guessClassName('base.alert'); - $this->assertSame("App\View\Components\Base\Alert", trim($result)); + $this->assertSame('App\\View\\Components\\Base\\Alert', trim($result)); } public function testComponentsCanBeCompiledWithHyphenAttributes() @@ -400,12 +406,12 @@ public function testComponentsCanBeCompiledWithHyphenAttributes() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes(['class' => 'bar','wire:model' => 'foo','x-on:click' => 'bar','@click' => 'baz']); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['class' => 'bar','wire:model' => 'foo','x-on:click' => 'bar','@click' => 'baz']); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testSelfClosingComponentsCanBeCompiledWithDataAndAttributes() @@ -413,12 +419,12 @@ public function testSelfClosingComponentsCanBeCompiledWithDataAndAttributes() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', ['title' => 'foo']) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes(['class' => 'bar','wire:model' => 'foo']); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['class' => 'bar','wire:model' => 'foo']); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testComponentCanReceiveAttributeBag() @@ -427,11 +433,11 @@ public function testComponentCanReceiveAttributeBag() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> -withAttributes(['class' => 'bar','attributes' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes),'wire:model' => 'foo']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['class' => 'bar','attributes' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute(\$attributes),'wire:model' => 'foo']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testSelfClosingComponentCanReceiveAttributeBag() @@ -440,12 +446,12 @@ public function testSelfClosingComponentCanReceiveAttributeBag() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
merge([\'class\' => \'test\']) }} wire:model="foo" />
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', ['title' => 'foo']) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes(['class' => 'bar','attributes' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes->merge(['class' => 'test'])),'wire:model' => 'foo']); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); +withAttributes(['class' => 'bar','attributes' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute(\$attributes->merge(['class' => 'test'])),'wire:model' => 'foo']); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); } public function testComponentsCanHaveAttachedWord() @@ -453,9 +459,9 @@ public function testComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestProfileComponent', 'profile', []) - -except(\Hypervel\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestProfileComponent', 'profile', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##Words", trim($result)); } @@ -465,12 +471,12 @@ public function testSelfClosingComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##Words', trim($result)); +withAttributes([]); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##Words', trim($result)); } public function testSelfClosingComponentsCanBeCompiledWithBoundData() @@ -478,12 +484,12 @@ public function testSelfClosingComponentsCanBeCompiledWithBoundData() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => \$title]) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', ['title' => \$title]) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> -withAttributes(['class' => 'bar']); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['class' => 'bar']); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testPairedComponentTags() @@ -492,9 +498,9 @@ public function testPairedComponentTags() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(' '); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\Tests\View\Blade\TestAlertComponent', 'alert', []) - -except(\Hypervel\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\Tests\\View\\Blade\\TestAlertComponent', 'alert', []) + +except(\\Hypervel\\Tests\\View\\Blade\\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); @@ -506,12 +512,12 @@ public function testClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['name' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessComponentsWithIndexView() @@ -520,12 +526,12 @@ public function testClasslessComponentsWithIndexView() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['name' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessComponentsWithComponentView() @@ -534,12 +540,12 @@ public function testClasslessComponentsWithComponentView() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['name' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testPackagesClasslessComponents() @@ -548,12 +554,12 @@ public function testPackagesClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". -'@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['name' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n" +. '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessComponentsWithAnonymousComponentNamespace() @@ -574,12 +580,12 @@ public function testClasslessComponentsWithAnonymousComponentNamespace() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['name' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexView() @@ -600,12 +606,12 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['name' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessComponentsWithAnonymousComponentNamespaceWithComponentView() @@ -626,18 +632,18 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithCompon $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes(['name' => \Hypervel\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes(['name' => \\Hypervel\\View\\Compilers\\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessComponentsWithAnonymousComponentPath() { $this->mockViewFactory(function ($arg) { - return $arg === md5('test-directory').'::panel.index'; + return $arg === md5('test-directory') . '::panel.index'; }); $blade = m::mock(BladeCompiler::class)->makePartial(); @@ -650,18 +656,18 @@ public function testClasslessComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'panel', ['view' => '" . md5('test-directory') . "::panel.index','data' => []]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes([]); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessComponentsWithAnonymousComponentPathComponentName() { $this->mockViewFactory(function ($arg) { - return $arg === md5('test-directory').'::panel.panel'; + return $arg === md5('test-directory') . '::panel.panel'; }); $blade = m::mock(BladeCompiler::class)->makePartial(); @@ -674,18 +680,18 @@ public function testClasslessComponentsWithAnonymousComponentPathComponentName() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.panel','data' => []]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'panel', ['view' => '" . md5('test-directory') . "::panel.panel','data' => []]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes([]); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testClasslessIndexComponentsWithAnonymousComponentPath() { $this->mockViewFactory(function ($arg) { - return $arg === md5('test-directory').'::panel'; + return $arg === md5('test-directory') . '::panel'; }); $blade = m::mock(BladeCompiler::class)->makePartial(); @@ -698,27 +704,25 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) - -except(\Hypervel\View\AnonymousComponent::ignoredParameterNames()); ?> + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Hypervel\\View\\AnonymousComponent', 'panel', ['view' => '" . md5('test-directory') . "::panel','data' => []]) + +except(\\Hypervel\\View\\AnonymousComponent::ignoredParameterNames()); ?> -withAttributes([]); ?>\n". - '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); +withAttributes([]); ?>\n" + . '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } public function testAttributeSanitization() { $this->mockViewFactory(); - $class = new class implements Stringable - { + $class = new class implements Stringable { public function __toString() { return ''; } }; - $model = new class extends Model - { + $model = new class extends Model { public function getEventDispatcher(): ?EventDispatcherInterface { return null; @@ -788,7 +792,7 @@ public function testAttributesTreatedAsPropsAreRemovedFromFinalAttributes() $template = $this->compiler->compileString($template); ob_start(); - eval(" ?> $template {$template} assertSame($attributes->get('userId'), 'bar'); @@ -846,7 +850,7 @@ public function testOriginalAttributesAreRestoredAfterRenderingChildComponentWit $template = $this->compiler->compileString($template); ob_start(); - eval(" ?> $template {$template} assertSame($attributes->get('userId'), 'bar'); @@ -881,7 +885,9 @@ protected function mockViewFactory(...$exists) protected function compiler(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null) { return new ComponentTagCompiler( - $aliases, $namespaces, $blade + $aliases, + $namespaces, + $blade ); } } diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php index 87c0d1a54..89be67c12 100644 --- a/tests/View/Blade/BladeComponentsTest.php +++ b/tests/View/Blade/BladeComponentsTest.php @@ -1,13 +1,19 @@ compiler->compileString('@component(\'Hypervel\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])'); ob_start(); - eval(" ?> $template {$template} assertNull($this->compiler->directive('custom', function () { - // })); $this->assertNull($this->compiler->directive('custom_custom', function () { - // })); $this->assertNull($this->compiler->directive('customCustom', function () { - // })); $this->assertNull($this->compiler->directive('custom::custom', function () { - // })); } @@ -73,7 +75,6 @@ public function testInvalidCustomNames() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('The directive name [custom-custom] is not valid.'); $this->compiler->directive('custom-custom', function () { - // }); } @@ -82,7 +83,6 @@ public function testInvalidCustomNames2() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('The directive name [custom:custom] is not valid.'); $this->compiler->directive('custom:custom', function () { - // }); } diff --git a/tests/View/Blade/BladeEchoHandlerTest.php b/tests/View/Blade/BladeEchoHandlerTest.php index f5c2249da..b10cc4110 100644 --- a/tests/View/Blade/BladeEchoHandlerTest.php +++ b/tests/View/Blade/BladeEchoHandlerTest.php @@ -1,5 +1,7 @@ createApplication(); $app->instance('blade.compiler', $this->compiler); @@ -72,6 +77,16 @@ public function testHandlerLogicWorksCorrectly($blade) eval((new Stringable($this->compiler->compileString($blade)))->remove([''])); } + public static function handlerLogicDataProvider() + { + return [ + ['{{$exampleObject}}'], + ['{{$exampleObject;}}'], + ['{{{$exampleObject;}}}'], + ['{!!$exampleObject;!!}'], + ]; + } + protected function createApplication() { $container = new Application( @@ -83,16 +98,6 @@ protected function createApplication() return $container; } - public static function handlerLogicDataProvider() - { - return [ - ['{{$exampleObject}}'], - ['{{$exampleObject;}}'], - ['{{{$exampleObject;}}}'], - ['{!!$exampleObject;!!}'], - ]; - } - #[DataProvider('handlerWorksWithIterableDataProvider')] public function testHandlerWorksWithIterables($blade, $closure, $expectedOutput) { @@ -115,7 +120,7 @@ public static function handlerWorksWithIterableDataProvider() [ '{{[1,"two",3]}}', fn (iterable $arr) => implode(', ', $arr), - '1, two, 3' + '1, two, 3', ], ]; } diff --git a/tests/View/Blade/BladeEchoTest.php b/tests/View/Blade/BladeEchoTest.php index 28234d6b7..751e14923 100644 --- a/tests/View/Blade/BladeEchoTest.php +++ b/tests/View/Blade/BladeEchoTest.php @@ -1,7 +1,13 @@ assertSame("\n\n", $this->compiler->compileString("{{ \$name }}\n")); $this->assertSame("\r\n\r\n", $this->compiler->compileString("{{ \$name }}\r\n")); - $this->assertSame('', - $this->compiler->compileString('{{ "Hello world or foo" }}')); - $this->assertSame('', - $this->compiler->compileString('{{"Hello world or foo"}}')); + $this->assertSame( + '', + $this->compiler->compileString('{{ "Hello world or foo" }}') + ); + $this->assertSame( + '', + $this->compiler->compileString('{{"Hello world or foo"}}') + ); $this->assertSame('', $this->compiler->compileString('{{$foo + $or + $baz}}')); $this->assertSame('', $this->compiler->compileString('{{ "Hello world or foo" }}')); - $this->assertSame('', - $this->compiler->compileString('{{ \'Hello world or foo\' }}')); - $this->assertSame('', - $this->compiler->compileString('{{\'Hello world or foo\'}}')); + $this->assertSame( + '', + $this->compiler->compileString('{{ \'Hello world or foo\' }}') + ); + $this->assertSame( + '', + $this->compiler->compileString('{{\'Hello world or foo\'}}') + ); $this->assertSame('', $this->compiler->compileString('{{ \'Hello world or foo\' }}')); - $this->assertSame('', - $this->compiler->compileString('{{ myfunc(\'foo or bar\') }}')); - $this->assertSame('', - $this->compiler->compileString('{{ myfunc("foo or bar") }}')); - $this->assertSame('', - $this->compiler->compileString('{{ myfunc("$name or \'foo\'") }}')); + $this->assertSame( + '', + $this->compiler->compileString('{{ myfunc(\'foo or bar\') }}') + ); + $this->assertSame( + '', + $this->compiler->compileString('{{ myfunc("foo or bar") }}') + ); + $this->assertSame( + '', + $this->compiler->compileString('{{ myfunc("$name or \'foo\'") }}') + ); } public function testEscapedWithAtEchosAreCompiled() { $this->assertSame('{{$name}}', $this->compiler->compileString('@{{$name}}')); $this->assertSame('{{ $name }}', $this->compiler->compileString('@{{ $name }}')); - $this->assertSame('{{ + $this->assertSame( + '{{ $name }}', $this->compiler->compileString('@{{ $name - }}')); - $this->assertSame('{{ $name }} + }}') + ); + $this->assertSame( + '{{ $name }} ', $this->compiler->compileString('@{{ $name }} - ')); + ') + ); } } diff --git a/tests/View/Blade/BladeElseAuthStatementsTest.php b/tests/View/Blade/BladeElseAuthStatementsTest.php index bec098223..d6a572f67 100644 --- a/tests/View/Blade/BladeElseAuthStatementsTest.php +++ b/tests/View/Blade/BladeElseAuthStatementsTest.php @@ -1,7 +1,13 @@ make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $expected = "test\n" . 'make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); - $string = '@extends(name(foo))'."\n".'test'; - $expected = "test\n".'make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $string = '@extends(name(foo))' . "\n" . 'test'; + $expected = "test\n" . 'make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -20,12 +26,12 @@ public function testSequentialCompileStringCalls() { $string = '@extends(\'foo\') test'; - $expected = "test\n".'make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $expected = "test\n" . 'make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); // use the same compiler instance to compile another template with @extends directive $string = "@extends(name(foo))\ntest"; - $expected = "test\n".'make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $expected = "test\n" . 'make(name(foo), array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -33,11 +39,11 @@ public function testExtendsFirstAreCompiled() { $string = '@extendsFirst([\'foo\', \'milwad\']) test'; - $expected = "test\n".'first([\'foo\', \'milwad\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $expected = "test\n" . 'first([\'foo\', \'milwad\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); - $string = '@extendsFirst([name(foo), name(milwad)])'."\n".'test'; - $expected = "test\n".'first([name(foo), name(milwad)], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; + $string = '@extendsFirst([name(foo), name(milwad)])' . "\n" . 'test'; + $expected = "test\n" . 'first([name(foo), name(milwad)], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); } } diff --git a/tests/View/Blade/BladeForStatementsTest.php b/tests/View/Blade/BladeForStatementsTest.php index 603272960..eb7d7bd2f 100644 --- a/tests/View/Blade/BladeForStatementsTest.php +++ b/tests/View/Blade/BladeForStatementsTest.php @@ -1,7 +1,13 @@ expectException(ViewCompilationException::class); $this->expectExceptionMessage('Malformed @foreach statement.'); - $string = "$initialStatement + $string = "{$initialStatement} test @endforeach"; $this->compiler->compileString($string); diff --git a/tests/View/Blade/BladeForelseStatementsTest.php b/tests/View/Blade/BladeForelseStatementsTest.php index 9108ad3d5..9d10a841d 100644 --- a/tests/View/Blade/BladeForelseStatementsTest.php +++ b/tests/View/Blade/BladeForelseStatementsTest.php @@ -1,10 +1,16 @@ expectException(ViewCompilationException::class); $this->expectExceptionMessage('Malformed @forelse statement.'); - $string = "$initialStatement + $string = "{$initialStatement} breeze @empty tag empty diff --git a/tests/View/Blade/BladeFragmentTest.php b/tests/View/Blade/BladeFragmentTest.php index 078370c7f..fcddf1b9d 100644 --- a/tests/View/Blade/BladeFragmentTest.php +++ b/tests/View/Blade/BladeFragmentTest.php @@ -1,7 +1,13 @@ bar"; + $string = "Foo @inject('baz', 'SomeNamespace\\SomeClass') bar"; + $expected = "Foo bar"; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -20,15 +26,15 @@ public function testDependenciesInjectedAsStringsAreCompiledWhenInjectedWithDoub public function testDependenciesAreCompiled() { - $string = "Foo @inject('baz', SomeNamespace\SomeClass::class) bar"; - $expected = "Foo bar"; + $string = "Foo @inject('baz', SomeNamespace\\SomeClass::class) bar"; + $expected = 'Foo bar'; $this->assertEquals($expected, $this->compiler->compileString($string)); } public function testDependenciesAreCompiledWithDoubleQuotes() { $string = 'Foo @inject("baz", SomeNamespace\SomeClass::class) bar'; - $expected = "Foo bar"; + $expected = 'Foo bar'; $this->assertEquals($expected, $this->compiler->compileString($string)); } } diff --git a/tests/View/Blade/BladeJsTest.php b/tests/View/Blade/BladeJsTest.php index 6c385548f..3e819e1b4 100644 --- a/tests/View/Blade/BladeJsTest.php +++ b/tests/View/Blade/BladeJsTest.php @@ -1,7 +1,13 @@ " - ."\n\n"; + . "\n@php echo 'And I'm not PHP!' @endphp" + . "\n " + . "\n\n"; $this->assertEquals($expected, $this->compiler->compileString($string)); } @@ -122,12 +128,12 @@ public function testStringWithEscapingDataValue() public function testUnclosedParenthesisForBladeTags() { $string = ""; - $expected = "\"(['(']>"; + $expected = "\"(['(']>"; $this->assertEquals($expected, $this->compiler->compileString($string)); $string = ""; - $expected = "\"(['']>"; + $expected = "\"(['']>"; $this->assertEquals($expected, $this->compiler->compileString($string)); @@ -175,7 +181,7 @@ public function testNestedTagCalls() public function testItDoesNotCompileInvalidSyntax() { - $template = " ()])>"; + $template = " ()])>"; $this->assertEquals($template, $this->compiler->compileString($template)); } } diff --git a/tests/View/Blade/BladePrependTest.php b/tests/View/Blade/BladePrependTest.php index c48b0af1f..451df97e6 100644 --- a/tests/View/Blade/BladePrependTest.php +++ b/tests/View/Blade/BladePrependTest.php @@ -1,13 +1,18 @@ compiler->compileString('@props([\'test1\' => \'default\', \'test2\', \'test4\' => \'default\'])'); ob_start(); - eval(" ?> $template {$template} assertSame($test1, 'value1'); diff --git a/tests/View/Blade/BladePushTest.php b/tests/View/Blade/BladePushTest.php index d13622bce..1077be5a0 100644 --- a/tests/View/Blade/BladePushTest.php +++ b/tests/View/Blade/BladePushTest.php @@ -1,13 +1,18 @@ true, 'margin-top: 10px' => false])>"; - $expected = " true, 'margin-top: 10px' => false]) ?>\">"; + $expected = " true, 'margin-top: 10px' => false]) ?>\">"; $this->assertEquals($expected, $this->compiler->compileString($string)); } diff --git a/tests/View/Blade/BladeUnlessStatementsTest.php b/tests/View/Blade/BladeUnlessStatementsTest.php index 3e3c48270..87855a130 100644 --- a/tests/View/Blade/BladeUnlessStatementsTest.php +++ b/tests/View/Blade/BladeUnlessStatementsTest.php @@ -1,7 +1,13 @@ bar"; + $string = "Foo @use('SomeNamespace\\SomeClass', 'Foo') bar"; + $expected = 'Foo bar'; $this->assertEquals($expected, $this->compiler->compileString($string)); } public function testUseStatementsWithoutAsAreCompiled() { - $string = "Foo @use('SomeNamespace\SomeClass') bar"; - $expected = "Foo bar"; + $string = "Foo @use('SomeNamespace\\SomeClass') bar"; + $expected = 'Foo bar'; $this->assertEquals($expected, $this->compiler->compileString($string)); } public function testUseStatementsWithBackslashAtBeginningAreCompiled() { - $string = "Foo @use('\SomeNamespace\SomeClass') bar"; - $expected = "Foo bar"; + $string = "Foo @use('\\SomeNamespace\\SomeClass') bar"; + $expected = 'Foo bar'; $this->assertEquals($expected, $this->compiler->compileString($string)); } public function testUseStatementsWithBackslashAtBeginningAndAliasedAreCompiled() { - $string = "Foo @use('\SomeNamespace\SomeClass', 'Foo') bar"; - $expected = "Foo bar"; + $string = "Foo @use('\\SomeNamespace\\SomeClass', 'Foo') bar"; + $expected = 'Foo bar'; $this->assertEquals($expected, $this->compiler->compileString($string)); } } diff --git a/tests/View/Blade/BladeVerbatimTest.php b/tests/View/Blade/BladeVerbatimTest.php index acf7d3a0b..73f7072f9 100644 --- a/tests/View/Blade/BladeVerbatimTest.php +++ b/tests/View/Blade/BladeVerbatimTest.php @@ -1,7 +1,13 @@ viewFactory->shouldReceive('exists')->once()->andReturn(false); $this->viewFactory->shouldReceive('addNamespace')->once()->with('__components', '/tmp'); - $component = new TestInlineViewComponent; + $component = new TestInlineViewComponent(); $this->assertSame('__components::57b7a54afa0eb51fd9b88eec031c9e9e', $component->resolveView()); } @@ -68,7 +74,7 @@ public function testRegularViewsGetReturnedUsingViewHelper() $view = m::mock(View::class); $this->viewFactory->shouldReceive('make')->once()->with('alert', [], [])->andReturn($view); - $component = new TestRegularViewComponentUsingViewHelper; + $component = new TestRegularViewComponentUsingViewHelper(); $this->assertSame($view, $component->resolveView()); } @@ -79,8 +85,7 @@ public function testRenderingStringClosureFromComponent() $this->viewFactory->shouldReceive('exists')->once()->andReturn(false); $this->viewFactory->shouldReceive('addNamespace')->once()->with('__components', '/tmp'); - $component = new class() extends Component - { + $component = new class extends Component { protected $title; public function __construct($title = 'World') @@ -114,7 +119,7 @@ public function testRegularViewsGetReturnedUsingViewMethod() $view = m::mock(View::class); $this->viewFactory->shouldReceive('make')->once()->with('alert', [], [])->andReturn($view); - $component = new TestRegularViewComponentUsingViewMethod; + $component = new TestRegularViewComponentUsingViewMethod(); $this->assertSame($view, $component->resolveView()); } @@ -124,14 +129,14 @@ public function testRegularViewNamesGetReturned() $this->viewFactory->shouldReceive('exists')->once()->andReturn(true); $this->viewFactory->shouldReceive('addNamespace')->never(); - $component = new TestRegularViewNameViewComponent; + $component = new TestRegularViewNameViewComponent(); $this->assertSame('alert', $component->resolveView()); } public function testHtmlableGetReturned() { - $component = new TestHtmlableReturningViewComponent; + $component = new TestHtmlableReturningViewComponent(); $view = $component->resolveView(); @@ -151,13 +156,12 @@ public function testResolveDependenciesWithoutContainer() $component = TestInlineViewComponentWhereRenderDependsOnProps::resolve(['content' => 'foo']); $this->assertSame('foo', $component->render()); - $component = new class extends Component - { + $component = new class extends Component { public $content; public function __construct($a = null, $b = null) { - $this->content = $a.$b; + $this->content = $a . $b; } public function render(): ViewContract|Htmlable|Closure|string @@ -182,7 +186,7 @@ public function testResolveDependenciesWithContainerIfNecessary() public function testResolveComponentsUsing() { - $component = new TestInlineViewComponent; + $component = new TestInlineViewComponent(); Component::resolveComponentsUsing(function ($class, $data) use ($component) { $this->assertSame(Component::class, $class, 'It takes the component class name as the first parameter.'); @@ -196,7 +200,7 @@ public function testResolveComponentsUsing() public function testBladeViewCacheWithRegularViewNameViewComponent() { - $component = new TestRegularViewNameViewComponent; + $component = new TestRegularViewNameViewComponent(); $this->viewFactory->shouldReceive('exists')->twice()->andReturn(true); @@ -206,7 +210,7 @@ public function testBladeViewCacheWithRegularViewNameViewComponent() $this->assertSame('alert', $component->resolveView()); $cache = (fn () => $component::$bladeViewCache)->call($component); - $this->assertSame([$component::class.'::alert' => 'alert'], $cache); + $this->assertSame([$component::class . '::alert' => 'alert'], $cache); $component::flushCache(); @@ -221,7 +225,7 @@ public function testBladeViewCacheWithRegularViewNameViewComponent() public function testBladeViewCacheWithInlineViewComponent() { - $component = new TestInlineViewComponent; + $component = new TestInlineViewComponent(); $this->viewFactory->shouldReceive('exists')->twice()->andReturn(false); @@ -233,7 +237,7 @@ public function testBladeViewCacheWithInlineViewComponent() $compiledViewName = '__components::57b7a54afa0eb51fd9b88eec031c9e9e'; $contents = '::Hello {{ $title }}'; - $cacheKey = $component::class.$contents; + $cacheKey = $component::class . $contents; $this->assertSame($compiledViewName, $component->resolveView()); $this->assertSame($compiledViewName, $component->resolveView()); @@ -269,8 +273,8 @@ public function testBladeViewCacheWithInlineViewComponentWhereRenderDependsOnPro $compiledViewNameA = '__components::9b0498cbe3839becd0d496e05c553485'; $compiledViewNameB = '__components::9d1b9bc4078a3e7274d3766ca02423f3'; - $cacheAKey = $componentA::class.'::A'; - $cacheBKey = $componentB::class.'::B'; + $cacheAKey = $componentA::class . '::A'; + $cacheBKey = $componentB::class . '::B'; $this->assertSame($compiledViewNameA, $componentA->resolveView()); $this->assertSame($compiledViewNameA, $componentA->resolveView()); @@ -295,8 +299,8 @@ public function testBladeViewCacheWithInlineViewComponentWhereRenderDependsOnPro public function testFactoryGetsSharedBetweenComponents() { - $regular = new TestRegularViewNameViewComponent; - $inline = new TestInlineViewComponent; + $regular = new TestRegularViewNameViewComponent(); + $inline = new TestInlineViewComponent(); $getFactory = fn ($component) => (fn () => $component->factory())->call($component); diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php index ff8b1376c..1b75089de 100644 --- a/tests/View/ViewBladeCompilerTest.php +++ b/tests/View/ViewBladeCompilerTest.php @@ -1,5 +1,7 @@ getFiles(), __DIR__); - $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); + $files->shouldReceive('exists')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php')->andReturn(false); $this->assertTrue($compiler->isExpired('foo')); } public function testIsExpiredReturnsTrueWhenModificationTimesWarrant() { $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php')->andReturn(true); $files->shouldReceive('lastModified')->once()->with('foo')->andReturn(100); - $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(0); + $files->shouldReceive('lastModified')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php')->andReturn(0); $this->assertTrue($compiler->isExpired('foo')); } public function testIsExpiredReturnsFalseWhenUseCacheIsTrueAndNoFileModification() { $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php')->andReturn(true); $files->shouldReceive('lastModified')->once()->with('foo')->andReturn(0); - $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(100); + $files->shouldReceive('lastModified')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php')->andReturn(100); $this->assertFalse($compiler->isExpired('foo')); } @@ -49,7 +55,7 @@ public function testIsExpiredReturnsTrueWhenUseCacheIsFalse() public function testCompilePathIsProperlyCreated() { $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', $compiler->getCompiledPath('foo')); + $this->assertEquals(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php', $compiler->getCompiledPath('foo')); } public function testCompileCompilesFileAndReturnsContents() @@ -57,7 +63,7 @@ public function testCompileCompilesFileAndReturnsContents() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php', 'Hello World'); $compiler->compile('foo'); } @@ -67,7 +73,7 @@ public function testCompileCompilesFileAndReturnsContentsCreatingDirectory() $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(false); $files->shouldReceive('makeDirectory')->once()->with(__DIR__, 0777, true, true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php', 'Hello World'); $compiler->compile('foo'); } @@ -84,8 +90,8 @@ public function testRawTagsCanBeSetToLegacyValues() } /** - * @param string $content - * @param string $compiled + * @param string $content + * @param string $compiled */ #[DataProvider('appendViewPathDataProvider')] public function testIncludePathToTemplate($content, $compiled) @@ -93,7 +99,7 @@ public function testIncludePathToTemplate($content, $compiled) $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn($content); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', $compiled); + $files->shouldReceive('put')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2foo') . '.php', $compiled); $compiler->compile('foo'); } @@ -148,7 +154,7 @@ public function testDontIncludeEmptyPath() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__ . '/' . hash('xxh128', 'v2') . '.php', 'Hello World'); $compiler->compile(''); } @@ -156,8 +162,11 @@ public function testShouldStartFromStrictTypesDeclaration() { $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $strictTypeDecl = "assertSame(substr($compiler->compileString("assertSame(substr( + $compiler->compileString("getEngine(); - $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); - $engine->getCompiler()->shouldReceive('isExpired')->once()->with(__DIR__.'/fixtures/foo.php')->andReturn(true); - $engine->getCompiler()->shouldReceive('compile')->once()->with(__DIR__.'/fixtures/foo.php'); - $results = $engine->get(__DIR__.'/fixtures/foo.php'); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__ . '/fixtures/foo.php')->andReturn(__DIR__ . '/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('isExpired')->once()->with(__DIR__ . '/fixtures/foo.php')->andReturn(true); + $engine->getCompiler()->shouldReceive('compile')->once()->with(__DIR__ . '/fixtures/foo.php'); + $results = $engine->get(__DIR__ . '/fixtures/foo.php'); $this->assertSame('Hello World ', $results); @@ -35,10 +41,10 @@ public function testViewsMayBeRecompiledAndRendered() public function testViewsAreNotRecompiledIfTheyAreNotExpired() { $engine = $this->getEngine(); - $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__ . '/fixtures/foo.php')->andReturn(__DIR__ . '/fixtures/basic.php'); $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false); $engine->getCompiler()->shouldReceive('compile')->never(); - $results = $engine->get(__DIR__.'/fixtures/foo.php'); + $results = $engine->get(__DIR__ . '/fixtures/foo.php'); $this->assertSame('Hello World ', $results); @@ -47,42 +53,42 @@ public function testViewsAreNotRecompiledIfTheyAreNotExpired() public function testRegularExceptionsAreReThrownAsViewExceptions() { $engine = $this->getEngine(); - $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/regular-exception.php'); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__ . '/fixtures/foo.php')->andReturn(__DIR__ . '/fixtures/regular-exception.php'); $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false); $this->expectException(ViewException::class); $this->expectExceptionMessage('regular exception message'); - $engine->get(__DIR__.'/fixtures/foo.php'); + $engine->get(__DIR__ . '/fixtures/foo.php'); } public function testHttpExceptionsAreNotReThrownAsViewExceptions() { $engine = $this->getEngine(); - $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/http-exception.php'); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__ . '/fixtures/foo.php')->andReturn(__DIR__ . '/fixtures/http-exception.php'); $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false); $this->expectException(HttpException::class); $this->expectExceptionMessage('http exception message'); - $engine->get(__DIR__.'/fixtures/foo.php'); + $engine->get(__DIR__ . '/fixtures/foo.php'); } public function testThatViewsAreNotAskTwiceIfTheyAreExpired() { $engine = $this->getEngine(); - $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__ . '/fixtures/foo.php')->andReturn(__DIR__ . '/fixtures/basic.php'); $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false); $engine->getCompiler()->shouldReceive('compile')->never(); - $engine->get(__DIR__.'/fixtures/foo.php'); - $engine->get(__DIR__.'/fixtures/foo.php'); + $engine->get(__DIR__ . '/fixtures/foo.php'); + $engine->get(__DIR__ . '/fixtures/foo.php'); } public function testViewsAreRecompiledWhenCompiledViewIsMissingViaFileNotFoundException() { - $compiled = __DIR__.'/fixtures/basic.php'; - $path = __DIR__.'/fixtures/foo.php'; + $compiled = __DIR__ . '/fixtures/basic.php'; + $path = __DIR__ . '/fixtures/foo.php'; $files = m::mock(Filesystem::class); $engine = $this->getEngine($files); @@ -126,8 +132,8 @@ public function testViewsAreRecompiledWhenCompiledViewIsMissingViaFileNotFoundEx public function testViewsAreRecompiledWhenCompiledViewIsMissingViaRequireException() { - $compiled = __DIR__.'/fixtures/basic.php'; - $path = __DIR__.'/fixtures/foo.php'; + $compiled = __DIR__ . '/fixtures/basic.php'; + $path = __DIR__ . '/fixtures/foo.php'; $files = m::mock(Filesystem::class); $engine = $this->getEngine($files); @@ -171,8 +177,8 @@ public function testViewsAreRecompiledWhenCompiledViewIsMissingViaRequireExcepti public function testViewsAreRecompiledJustOnceWhenCompiledViewIsMissing() { - $compiled = __DIR__.'/fixtures/basic.php'; - $path = __DIR__.'/fixtures/foo.php'; + $compiled = __DIR__ . '/fixtures/basic.php'; + $path = __DIR__ . '/fixtures/foo.php'; $files = m::mock(Filesystem::class); $engine = $this->getEngine($files); @@ -221,8 +227,8 @@ public function testViewsAreRecompiledJustOnceWhenCompiledViewIsMissing() public function testViewsAreNotRecompiledOnRegularViewException() { - $compiled = __DIR__.'/fixtures/basic.php'; - $path = __DIR__.'/fixtures/foo.php'; + $compiled = __DIR__ . '/fixtures/basic.php'; + $path = __DIR__ . '/fixtures/foo.php'; $files = m::mock(Filesystem::class); $engine = $this->getEngine($files); @@ -256,8 +262,8 @@ public function testViewsAreNotRecompiledOnRegularViewException() public function testViewsAreNotRecompiledIfTheyWereJustCompiled() { - $compiled = __DIR__.'/fixtures/basic.php'; - $path = __DIR__.'/fixtures/foo.php'; + $compiled = __DIR__ . '/fixtures/basic.php'; + $path = __DIR__ . '/fixtures/foo.php'; $files = m::mock(Filesystem::class); $engine = $this->getEngine($files); @@ -292,6 +298,6 @@ public function testViewsAreNotRecompiledIfTheyWereJustCompiled() protected function getEngine($filesystem = null) { - return new CompilerEngine(m::mock(CompilerInterface::class), $filesystem ?: new Filesystem); + return new CompilerEngine(m::mock(CompilerInterface::class), $filesystem ?: new Filesystem()); } } diff --git a/tests/View/ViewComponentAttributeBagTest.php b/tests/View/ViewComponentAttributeBagTest.php index dfb347f16..81e2ce84b 100644 --- a/tests/View/ViewComponentAttributeBagTest.php +++ b/tests/View/ViewComponentAttributeBagTest.php @@ -1,10 +1,16 @@ assertSame('test-string="ok" test-true="test-true" test-0="0" test-0-string="0" test-empty-string=""', (string) $bag); $this->assertSame('test-string="ok" test-true="test-true" test-0="0" test-0-string="0" test-empty-string=""', (string) $bag->merge()); - $bag = (new ComponentAttributeBag) + $bag = (new ComponentAttributeBag()) ->merge([ 'test-escaped' => '', ]); $this->assertSame('test-escaped="<tag attr="attr">"', (string) $bag); - $bag = (new ComponentAttributeBag) + $bag = (new ComponentAttributeBag()) ->merge([ 'test-string' => 'ok', 'test-null' => null, @@ -76,7 +82,7 @@ public function testAttributeRetrieval() $this->assertSame('test-string="ok" test-true="test-true" test-0="0" test-0-string="0" test-empty-string=""', (string) $bag); - $bag = (new ComponentAttributeBag) + $bag = (new ComponentAttributeBag()) ->merge([ 'test-extract-1' => 'extracted-1', 'test-extract-2' => 'extracted-2', @@ -89,7 +95,7 @@ public function testAttributeRetrieval() 'test-discard-2' => 'defaultValue', ])); - $bag = (new ComponentAttributeBag) + $bag = (new ComponentAttributeBag()) ->merge([ 'test-extract-1' => 'extracted-1', 'test-extract-2' => 'extracted-2', diff --git a/tests/View/ViewComponentTest.php b/tests/View/ViewComponentTest.php index b908be5a9..4d980ac43 100644 --- a/tests/View/ViewComponentTest.php +++ b/tests/View/ViewComponentTest.php @@ -1,21 +1,27 @@ data(); @@ -26,8 +32,7 @@ public function testDataExposure() public function testIgnoredMethodsAreNotExposedToViewData() { - $component = new class extends Component - { + $component = new class extends Component { protected array $except = ['goodbye']; public function render(): ViewContract|Htmlable|Closure|string @@ -62,7 +67,7 @@ public function goodbye() public function testAttributeParentInheritance(): void { - $component = new TestViewComponent; + $component = new TestViewComponent(); $attributes = new ComponentAttributeBag(['class' => 'bar', 'type' => 'button']); $component->withAttributes(['class' => 'foo', 'attributes' => $attributes]); @@ -92,7 +97,7 @@ public function testSlotAttributeParentInheritance(): void public function testPublicMethodsWithNoArgsAreConvertedToStringableCallablesInvokedAndNotCached() { - $component = new TestSampleViewComponent; + $component = new TestSampleViewComponent(); $this->assertEquals(0, $component->counter); $this->assertEquals(0, TestSampleViewComponent::$publicStaticCounter); @@ -124,7 +129,7 @@ public function testPublicMethodsWithNoArgsAreConvertedToStringableCallablesInvo public function testItIgnoresExceptedMethodsAndProperties() { - $component = new TestExceptedViewComponent; + $component = new TestExceptedViewComponent(); $variables = $component->data(); // Ignored methods (with no args) are not invoked behind the scenes. @@ -137,7 +142,7 @@ public function testItIgnoresExceptedMethodsAndProperties() public function testMethodsOverridePropertyValues() { - $component = new TestHelloPropertyHelloMethodComponent; + $component = new TestHelloPropertyHelloMethodComponent(); $variables = $component->data(); $this->assertArrayHasKey('hello', $variables); $this->assertSame('world', $variables['hello']()); @@ -187,19 +192,19 @@ public function publicHello($string = 'world') public function noArgs() { - $this->counter++; + ++$this->counter; return 'noArgs val'; } protected function protectedHello() { - $this->counter++; + ++$this->counter; } private function privateHello() { - $this->counter++; + ++$this->counter; } } diff --git a/tests/View/ViewEngineResolverTest.php b/tests/View/ViewEngineResolverTest.php index c3856eb04..7678d345f 100755 --- a/tests/View/ViewEngineResolverTest.php +++ b/tests/View/ViewEngineResolverTest.php @@ -1,5 +1,7 @@ register('foo', function () { return new FakeEngine(); }); @@ -24,7 +30,7 @@ public function testResolverThrowsExceptionOnUnknownEngine() { $this->expectException(InvalidArgumentException::class); - $resolver = new EngineResolver; + $resolver = new EngineResolver(); $resolver->resolve('foo'); } } diff --git a/tests/View/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php index 4ecf94f12..b97722f1c 100755 --- a/tests/View/ViewFactoryTest.php +++ b/tests/View/ViewFactoryTest.php @@ -1,13 +1,13 @@ getFactoryArgs()); + $factory = m::mock(Factory::class . '[make]', $this->getFactoryArgs()); $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock(ViewContract::class)); $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock(ViewContract::class)); $mockView1->shouldReceive('render')->once()->andReturn('dayle'); @@ -134,7 +140,7 @@ public function testRenderEachCreatesViewForEachItemInArray() public function testEmptyViewsCanBeReturnedFromRenderEach() { - $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs()); + $factory = m::mock(Factory::class . '[make]', $this->getFactoryArgs()); $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock(ViewContract::class)); $mockView->shouldReceive('render')->once()->andReturn('empty'); @@ -151,7 +157,6 @@ public function testEnvironmentAddsExtensionWithCustomResolver() $factory = $this->getFactory(); $resolver = function () { - // }; $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo'); @@ -629,7 +634,7 @@ public function testSectionExtending() $factory = $this->getFactory(); $placeholder = $factory->getParentPlaceholder('foo'); $factory->startSection('foo'); - echo 'hi '.$placeholder; + echo 'hi ' . $placeholder; $factory->stopSection(); $factory->startSection('foo'); echo 'there'; @@ -643,10 +648,10 @@ public function testSectionMultipleExtending() $factory = $this->getFactory(); $placeholder = $factory->getParentPlaceholder('foo'); $factory->startSection('foo'); - echo 'hello '.$placeholder.' nice to see you '.$placeholder; + echo 'hello ' . $placeholder . ' nice to see you ' . $placeholder; $factory->stopSection(); $factory->startSection('foo'); - echo 'my '.$placeholder; + echo 'my ' . $placeholder; $factory->stopSection(); $factory->startSection('foo'); echo 'friend'; @@ -658,8 +663,8 @@ public function testSectionMultipleExtending() public function testComponentHandling() { $factory = $this->getFactory(); - $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); + $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__ . '/fixtures/component.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem())); $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->startComponent('component', ['name' => 'Taylor']); $factory->slot('title'); @@ -674,8 +679,8 @@ public function testComponentHandling() public function testComponentHandlingUsingViewObject() { $factory = $this->getFactory(); - $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); + $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__ . '/fixtures/component.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem())); $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->startComponent($factory->make('component'), ['name' => 'Taylor']); $factory->slot('title'); @@ -690,8 +695,8 @@ public function testComponentHandlingUsingViewObject() public function testComponentHandlingUsingClosure() { $factory = $this->getFactory(); - $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); + $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__ . '/fixtures/component.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem())); $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->startComponent(function ($data) use ($factory) { $this->assertArrayHasKey('name', $data); @@ -895,15 +900,15 @@ public function testExceptionsInSectionsAreThrown() $this->expectException(ErrorException::class); $this->expectExceptionMessage('section exception message'); - $engine = new CompilerEngine(m::mock(CompilerInterface::class), new Filesystem); + $engine = new CompilerEngine(m::mock(CompilerInterface::class), new Filesystem()); $engine->getCompiler()->shouldReceive('getCompiledPath')->andReturnUsing(function ($path) { return $path; }); $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false); $factory = $this->getFactory(); $factory->getEngineResolver()->shouldReceive('resolve')->twice()->andReturn($engine); - $factory->getFinder()->shouldReceive('find')->once()->with('layout')->andReturn(__DIR__.'/fixtures/section-exception-layout.php'); - $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn(__DIR__.'/fixtures/section-exception.php'); + $factory->getFinder()->shouldReceive('find')->once()->with('layout')->andReturn(__DIR__ . '/fixtures/section-exception-layout.php'); + $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn(__DIR__ . '/fixtures/section-exception.php'); $factory->getDispatcher()->shouldReceive('hasListeners')->times(4); // 2 "creating" + 2 "composing"... $factory->make('view')->render(); @@ -981,11 +986,10 @@ public function testAddingLoopDoesNotCloseGenerator() { $factory = $this->getFactory(); - $data = (new class - { + $data = (new class { public function generate() { - for ($count = 0; $count < 3; $count++) { + for ($count = 0; $count < 3; ++$count) { yield ['a', 'b']; } } diff --git a/tests/View/ViewFileViewFinderTest.php b/tests/View/ViewFileViewFinderTest.php index 5ac121626..7adb7f4a3 100755 --- a/tests/View/ViewFileViewFinderTest.php +++ b/tests/View/ViewFileViewFinderTest.php @@ -1,5 +1,7 @@ getFinder(); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(true); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.blade.php')->andReturn(true); - $this->assertEquals(__DIR__.'/foo.blade.php', $finder->find('foo')); + $this->assertEquals(__DIR__ . '/foo.blade.php', $finder->find('foo')); } public function testCascadingFileLoading() { $finder = $this->getFinder(); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(true); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.php')->andReturn(true); - $this->assertEquals(__DIR__.'/foo.php', $finder->find('foo')); + $this->assertEquals(__DIR__ . '/foo.php', $finder->find('foo')); } public function testDirectoryCascadingFileLoading() { $finder = $this->getFinder(); - $finder->addLocation(__DIR__.'/nested'); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.css')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.html')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/nested/foo.blade.php')->andReturn(true); + $finder->addLocation(__DIR__ . '/nested'); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.css')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.html')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/nested/foo.blade.php')->andReturn(true); - $this->assertEquals(__DIR__.'/nested/foo.blade.php', $finder->find('foo')); + $this->assertEquals(__DIR__ . '/nested/foo.blade.php', $finder->find('foo')); } public function testNamespacedBasicFileLoading() { $finder = $this->getFinder(); - $finder->addNamespace('foo', __DIR__.'/foo'); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(true); + $finder->addNamespace('foo', __DIR__ . '/foo'); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo/bar/baz.blade.php')->andReturn(true); - $this->assertEquals(__DIR__.'/foo/bar/baz.blade.php', $finder->find('foo::bar.baz')); + $this->assertEquals(__DIR__ . '/foo/bar/baz.blade.php', $finder->find('foo::bar.baz')); } public function testCascadingNamespacedFileLoading() { $finder = $this->getFinder(); - $finder->addNamespace('foo', __DIR__.'/foo'); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.php')->andReturn(true); + $finder->addNamespace('foo', __DIR__ . '/foo'); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo/bar/baz.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo/bar/baz.php')->andReturn(true); - $this->assertEquals(__DIR__.'/foo/bar/baz.php', $finder->find('foo::bar.baz')); + $this->assertEquals(__DIR__ . '/foo/bar/baz.php', $finder->find('foo::bar.baz')); } public function testDirectoryCascadingNamespacedFileLoading() { $finder = $this->getFinder(); - $finder->addNamespace('foo', [__DIR__.'/foo', __DIR__.'/bar']); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.css')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.html')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/bar/bar/baz.blade.php')->andReturn(true); + $finder->addNamespace('foo', [__DIR__ . '/foo', __DIR__ . '/bar']); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo/bar/baz.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo/bar/baz.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo/bar/baz.css')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo/bar/baz.html')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/bar/bar/baz.blade.php')->andReturn(true); - $this->assertEquals(__DIR__.'/bar/bar/baz.blade.php', $finder->find('foo::bar.baz')); + $this->assertEquals(__DIR__ . '/bar/bar/baz.blade.php', $finder->find('foo::bar.baz')); } public function testExceptionThrownWhenViewNotFound() @@ -83,10 +89,10 @@ public function testExceptionThrownWhenViewNotFound() $this->expectException(InvalidArgumentException::class); $finder = $this->getFinder(); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.css')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.html')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.blade.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.php')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.css')->andReturn(false); + $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__ . '/foo.html')->andReturn(false); $finder->find('foo'); } @@ -147,13 +153,6 @@ public function testPassingViewWithFalseHintReturnsFalse() $this->assertFalse($finder->hasHintInformation('::foo.bar')); } - public static function pathsProvider() - { - return [ - ['incorrect_path', 'incorrect_path'], - ]; - } - #[DataProvider('pathsProvider')] public function testNormalizedPaths($originalPath, $exceptedPath) { @@ -163,6 +162,13 @@ public function testNormalizedPaths($originalPath, $exceptedPath) $this->assertSame($exceptedPath, $normalizedPath); } + public static function pathsProvider() + { + return [ + ['incorrect_path', 'incorrect_path'], + ]; + } + protected function getFinder() { return new FileViewFinder(m::mock(Filesystem::class), [__DIR__]); diff --git a/tests/View/ViewPhpEngineTest.php b/tests/View/ViewPhpEngineTest.php index 11e342273..3cc566fc8 100755 --- a/tests/View/ViewPhpEngineTest.php +++ b/tests/View/ViewPhpEngineTest.php @@ -1,16 +1,22 @@ assertSame('Hello World' . PHP_EOL, $engine->get(__DIR__.'/fixtures/basic.php')); + $engine = new PhpEngine(new Filesystem()); + $this->assertSame('Hello World' . PHP_EOL, $engine->get(__DIR__ . '/fixtures/basic.php')); } } diff --git a/tests/View/ViewTest.php b/tests/View/ViewTest.php index 85f0ee457..30f9e11f8 100755 --- a/tests/View/ViewTest.php +++ b/tests/View/ViewTest.php @@ -1,19 +1,25 @@ assertSame('contents', $view->render(function () { - // })); } @@ -152,7 +157,7 @@ public function testViewArrayAccess() public function testViewConstructedWithObjectData() { - $view = $this->getView(new DataObjectStub); + $view = $this->getView(new DataObjectStub()); $this->assertInstanceOf(ArrayAccess::class, $view); $this->assertTrue($view->offsetExists('foo')); $this->assertSame('bar', $view->offsetGet('foo')); diff --git a/tests/View/fixtures/http-exception.php b/tests/View/fixtures/http-exception.php index 62b231004..7c4e205f0 100644 --- a/tests/View/fixtures/http-exception.php +++ b/tests/View/fixtures/http-exception.php @@ -1,5 +1,7 @@ yieldContent('content'); diff --git a/tests/View/fixtures/section-exception.php b/tests/View/fixtures/section-exception.php index ce553295e..0c7a1203b 100644 --- a/tests/View/fixtures/section-exception.php +++ b/tests/View/fixtures/section-exception.php @@ -1,4 +1,4 @@ make('layout', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?> startSection('content'); ?> - + stopSection(); ?>