Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Discover anonymous Blade components in other folders #41637

Merged
merged 8 commits into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Illuminate/Support/Facades/Blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* @method static void compile(string|null $path = null)
* @method static void component(string $class, string|null $alias = null, string $prefix = '')
* @method static void components(array $components, string $prefix = '')
* @method static void anonymousComponentNamespace(string $directory, string $prefix)
* @method static void componentNamespace(string $namespace, string $prefix)
* @method static void directive(string $name, callable $handler)
* @method static void extend(callable $compiler)
Expand Down
30 changes: 30 additions & 0 deletions src/Illuminate/View/Compilers/BladeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ class BladeCompiler extends Compiler implements CompilerInterface
*/
protected $customDirectives = [];

/**
* The array of anonymous component namespaces to autoload from.
*
* @var array
*/
protected $anonymousComponentNamespaces = [];

/**
* All custom "condition" handlers.
*
Expand Down Expand Up @@ -672,6 +679,19 @@ public function getClassComponentAliases()
return $this->classComponentAliases;
}

/**
* Register an anonymous component namespace.
*
* @param string $directory
* @param string $prefix
* @return void
*/
public function anonymousComponentNamespace($directory, $prefix)
{
// Store the directory as dot notation.
$this->anonymousComponentNamespaces[$prefix] = Str::replace('/', '.', $directory);
}

/**
* Register a class-based component namespace.
*
Expand All @@ -684,6 +704,16 @@ public function componentNamespace($namespace, $prefix)
$this->classComponentNamespaces[$prefix] = $namespace;
}

/**
* Get the registered anonymous component namespaces.
*
* @return array
*/
public function getAnonymousComponentNamespaces()
{
return $this->anonymousComponentNamespaces;
}

/**
* Get the registered class component namespaces.
*
Expand Down
47 changes: 39 additions & 8 deletions src/Illuminate/View/Compilers/ComponentTagCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,40 @@ public function componentClass(string $component)
return $class;
}

if ($viewFactory->exists($view = $this->guessViewName($component))) {
return $view;
}

if ($viewFactory->exists($view = $this->guessViewName($component).'.index')) {
return $view;
// The following code serves to guess the view name for this component.
// First, we'll create a collection with the custom component paths provided
// by the developer. We can use these folders to guess the correct view name.
$guess = collect($this->blade->getAnonymousComponentNamespaces())
// Next, we'll filter out the component paths that cannot be used here, based on their prefix.
// For example, for the anonymous component "<x-admin::dashboard>", we should look for the 'dashboard'
// view in the folder associated with the 'admin' prefix.
->filter(function ($directory, $prefix) use ($component): bool {
return Str::startsWith($component, $prefix.'::');
})
// Next, we'll prepend the default components directory and our full component name. This is convention.
// Prepending it ensures that the /components always has precedence over the cutom directories.
->prepend('components', $component)
// Finally, we'll go over the components and prefixes and check whether we can find a matching view name.
->reduce(function($carry, $directory, $prefix) use ($component, $viewFactory) {
$componentName = Str::after($component, $prefix.'::');

if ($carry !== null) {
return $carry;
}

if ($viewFactory->exists($view = $this->guessViewName($componentName, $directory))) {
return $view;
}

if ($viewFactory->exists($view = $this->guessViewName($componentName).'.index')) {
return $view;
}

return null;
});

if ($guess !== null) {
return $guess;
}

throw new InvalidArgumentException(
Expand Down Expand Up @@ -341,11 +369,14 @@ 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)
public function guessViewName($name, $prefix = 'components.')
{
$prefix = 'components.';
if (! Str::endsWith($prefix, '.')) {
$prefix .= '.';
}

$delimiter = ViewFinderInterface::HINT_PATH_DELIMITER;

Expand Down