diff --git a/README.md b/README.md index b642162..71fb1c0 100644 --- a/README.md +++ b/README.md @@ -383,3 +383,31 @@ Widget::group('sidebar')->removeAll(); // Widget group is empty now `Widget::group('sidebar')->any(); // bool` `Widget::group('sidebar')->count(); // int` + +## Namespaces for third party packages (extra) + +In some cases, it may be useful to deliver widgets with your own packages. For example, if your package allows +you to manage news, it would be convenient to have immediately configurable widgets, ready for display, directly +delivered with your package. + +To avoid having to use the fqcn each time, you can set a widget namespace into your package provider. This way the +widgets from your package can be more easily identified, and especially the syntax will be shorter. + +To do that, all you have to do is to register the namespace in your package service provider : + +```php +public function boot() +{ + app('arrilot.widget-namespaces')->registerNamespace('my-package-name', '\VendorName\PackageName\Path\To\Widgets'); +} +``` + +After that you can use the namespace in your views : + +```php +@widget('my-package-name::foo.bar') + +// is equivalent to +@widget('\VendorName\PackageName\Path\To\Widgets\Foo\Bar') +``` + \ No newline at end of file diff --git a/src/Console/WidgetMakeCommand.php b/src/Console/WidgetMakeCommand.php index d4eecfb..fcec1e7 100644 --- a/src/Console/WidgetMakeCommand.php +++ b/src/Console/WidgetMakeCommand.php @@ -224,7 +224,7 @@ protected function makeViewName() // convert to snake_case part by part to avoid unexpected underscores. $nameArray = explode('/', $name); array_walk($nameArray, function (&$part) { - $part = snake_case($part); + $part = Str::snake($part); }); return implode('/', $nameArray); diff --git a/src/Contracts/ApplicationWrapperContract.php b/src/Contracts/ApplicationWrapperContract.php index d48191d..f35165a 100644 --- a/src/Contracts/ApplicationWrapperContract.php +++ b/src/Contracts/ApplicationWrapperContract.php @@ -54,4 +54,13 @@ public function getNamespace(); * @return mixed */ public function make($abstract, array $parameters = []); + + /** + * Wrapper around app()->get(). + * + * @param string $id + * + * @return mixed + */ + public function get($id); } diff --git a/src/Factories/AbstractWidgetFactory.php b/src/Factories/AbstractWidgetFactory.php index f450e9a..c27b80c 100644 --- a/src/Factories/AbstractWidgetFactory.php +++ b/src/Factories/AbstractWidgetFactory.php @@ -8,6 +8,7 @@ use Arrilot\Widgets\Misc\InvalidWidgetClassException; use Arrilot\Widgets\Misc\ViewExpressionTrait; use Arrilot\Widgets\WidgetId; +use Illuminate\Support\Str; abstract class AbstractWidgetFactory { @@ -115,12 +116,21 @@ protected function instantiateWidget(array $params = []) { WidgetId::increment(); - $this->widgetName = $this->parseFullWidgetNameFromString(array_shift($params)); + $str = array_shift($params); + + if (preg_match('#^(.*?)::(.*?)$#', $str, $m)) { + $rootNamespace = $this->app->get('arrilot.widget-namespaces')->getNamespace($m[1]); + $str = $m[2]; + } + + $this->widgetName = $this->parseFullWidgetNameFromString($str); $this->widgetFullParams = $params; $this->widgetConfig = (array) array_shift($params); $this->widgetParams = $params; - $rootNamespace = $this->app->config('laravel-widgets.default_namespace', $this->app->getNamespace().'Widgets'); + if (!isset($rootNamespace)) { + $rootNamespace = $this->app->config('laravel-widgets.default_namespace', $this->app->getNamespace().'Widgets'); + } $fqcn = $rootNamespace.'\\'.$this->widgetName; $widgetClass = class_exists($fqcn) ? $fqcn : $this->widgetName; @@ -149,7 +159,7 @@ protected function instantiateWidget(array $params = []) */ protected function parseFullWidgetNameFromString($widgetName) { - return studly_case(str_replace('.', '\\_', $widgetName)); + return Str::studly(str_replace('.', '\\_', $widgetName)); } /** diff --git a/src/Misc/LaravelApplicationWrapper.php b/src/Misc/LaravelApplicationWrapper.php index 50f14dd..901b464 100644 --- a/src/Misc/LaravelApplicationWrapper.php +++ b/src/Misc/LaravelApplicationWrapper.php @@ -90,4 +90,18 @@ public function make($abstract, array $parameters = []) { return $this->app->make($abstract, $parameters); } + + /** + * Wrapper around app()->get(). + * + * @param string $id + * + * @throws \Illuminate\Container\EntryNotFoundException + * + * @return mixed + */ + public function get($id) + { + return $this->app->get($id); + } } diff --git a/src/Misc/NamespaceNotFoundException.php b/src/Misc/NamespaceNotFoundException.php new file mode 100644 index 0000000..3034a0e --- /dev/null +++ b/src/Misc/NamespaceNotFoundException.php @@ -0,0 +1,9 @@ +namespaces[$alias] = rtrim($namespace, '\\'); + + return $this; + } + + /** + * Get namespace by his alias. + * + * @param string $label + * + * @throws \Exception + * + * @return string + */ + public function getNamespace($alias) + { + if (!isset($this->namespaces[$alias])) { + throw new NamespaceNotFoundException('Namespace not found with the alias "'.$alias.'"'); + } + + return $this->namespaces[$alias]; + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 868a340..520e772 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -33,6 +33,10 @@ public function register() return new WidgetGroupCollection(new LaravelApplicationWrapper()); }); + $this->app->singleton('arrilot.widget-namespaces', function () { + return new NamespacesRepository(); + }); + $this->app->singleton('command.widget.make', function ($app) { return new WidgetMakeCommand($app['files']); }); diff --git a/tests/Support/TestApplicationWrapper.php b/tests/Support/TestApplicationWrapper.php index 38e8ca6..e857ba2 100644 --- a/tests/Support/TestApplicationWrapper.php +++ b/tests/Support/TestApplicationWrapper.php @@ -6,9 +6,10 @@ use Arrilot\Widgets\Contracts\ApplicationWrapperContract; use Arrilot\Widgets\Factories\AsyncWidgetFactory; use Arrilot\Widgets\Factories\WidgetFactory; -use Illuminate\Container\Container; +use Arrilot\Widgets\NamespacesRepository; use Closure; use Doctrine\Instantiator\Exception\InvalidArgumentException; +use Illuminate\Container\Container; class TestApplicationWrapper implements ApplicationWrapperContract { @@ -107,4 +108,18 @@ public function make($abstract, array $parameters = []) throw new InvalidArgumentException("Binding {$abstract} cannot be resolved while testing"); } + + /** + * Wrapper around app()->get(). + * + * @param string $id + * + * @return mixed + */ + public function get($id) + { + if ($id == 'arrilot.widget-namespaces') { + return (new NamespacesRepository())->registerNamespace('dummy', '\Arrilot\Widgets\Test\Dummies'); + } + } } diff --git a/tests/WidgetFactoryTest.php b/tests/WidgetFactoryTest.php index 909dd2c..23d3685 100644 --- a/tests/WidgetFactoryTest.php +++ b/tests/WidgetFactoryTest.php @@ -68,6 +68,20 @@ public function testItCanRunWidgetsUsingFQCN() $this->assertEquals('Default test slider was executed with $slides = 6', $output); } + public function testItThrowsExceptionForNamespaceNotFound() + { + $this->expectException('Arrilot\Widgets\Misc\NamespaceNotFoundException'); + + $output = $this->factory->run('notfound::TestDefaultSlider'); + } + + public function testItCanRunWidgetsUsingNamespace() + { + $output = $this->factory->run('dummy::TestDefaultSlider'); + + $this->assertEquals('Default test slider was executed with $slides = 6', $output); + } + public function testItLoadsWidgetsFromRootNamespaceFirst() { $output = $this->factory->run('Exception');