Skip to content

Commit

Permalink
Add support for resource routes (#3)
Browse files Browse the repository at this point in the history
* Initial resource route implmentation

* Remove some dead code
  • Loading branch information
inxilpro authored Oct 20, 2021
1 parent b63b336 commit 2334944
Show file tree
Hide file tree
Showing 10 changed files with 588 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ format. This project adheres to [Semantic Versioning](https://semver.org/spec/v2

## [Unreleased]

### Added

- Added support for `Route::resource()`

## [1.3.0]

## Added
### Added

- Added support for Inertia.js

## [1.2.0]
### [1.2.0]

## Added
### Added

- Added `Gretel` facade
- Added options for handling missing or mis-configured breadcrumbs (see README)
- Added additional exceptions for more granular handling

## Fixed
### Fixed

- Calling `Collection` methods on `RequestBreadcrumbs` now automatically populates the collection first

Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,35 @@ Route::get('/users/{user}', [UserController::class, 'show'])

![Shallow Nested Example](https://user-images.githubusercontent.com/21592/134791638-fbb87040-e27f-4749-9175-0f5dce995924.png)

#### Resource Routes

You can also define breadcrumbs for resource controllers. The `index()`, `create()`,
`show()`, and `edit()` methods behave exactly like the regular breadcrumb helper except that
they automatically set up the parent for you if you don’t provide one.

```php
Route::resource('users', UserController::class)
->breadcrumbs(function(ResourceBreadcrumbs $breadcrumbs) {
$breadcrumbs
->index('Users')
->create('New User')
->show(fn(User $user) => $user->name)
->edit('Edit');
});
```

If you prefer, you can also use an array syntax for simple resource routes:

```php
Route::resource('users', UserController::class)
->breadcrumbs([
'index' => 'Users',
'create' => 'New User',
'show' => fn(User $user) => $user->name,
'edit' => 'Edit',
]);
```

### Displaying Breadcrumbs

You can display the breadcrumbs for the current route with the `<x-breadcrumbs />` Blade component. The Blade component
Expand Down
24 changes: 24 additions & 0 deletions src/Macros.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
use Glhd\Gretel\Resolvers\TitleResolver;
use Glhd\Gretel\Resolvers\UrlResolver;
use Glhd\Gretel\Routing\RequestBreadcrumbs;
use Glhd\Gretel\Routing\ResourceBreadcrumbs;
use Glhd\Gretel\Routing\RouteBreadcrumb;
use Illuminate\Routing\PendingResourceRegistration;
use Illuminate\Routing\Route;
use InvalidArgumentException;

class Macros
{
Expand All @@ -24,6 +27,12 @@ public static function register(Registry $registry): void
Route::macro('breadcrumbs', function() use ($registry) {
return Macros::breadcrumbs($registry, $this);
});

PendingResourceRegistration::macro('breadcrumbs', function($breadcrumbs) use ($registry) {
Macros::resourceBreadcrumbs($registry, $this->name, $this->options, $breadcrumbs);

return $this;
});
}

public static function breadcrumb(
Expand Down Expand Up @@ -52,4 +61,19 @@ public static function breadcrumbs(Registry $registry, Route $route): RequestBre
{
return new RequestBreadcrumbs($registry, $route);
}

public static function resourceBreadcrumbs(Registry $registry, string $name, array $options, $setup): void
{
$breadcrumbs = new ResourceBreadcrumbs($name, $options);

if (is_array($setup)) {
$breadcrumbs->configure($setup);
} elseif ($setup instanceof Closure) {
$setup($breadcrumbs);
} else {
throw new InvalidArgumentException('Route::resource()->breadcrumbs() expects an array or closure.');
}

$breadcrumbs->register($registry);
}
}
2 changes: 1 addition & 1 deletion src/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function __construct()
public function withExceptionHandling(Closure $callback)
{
try {
return $callback();
return $callback($this);
} catch (MissingBreadcrumbException $exception) {
$this->callHandler(static::HANDLER_MISSING, $exception);
} catch (Throwable $exception) {
Expand Down
116 changes: 116 additions & 0 deletions src/Routing/ResourceBreadcrumbs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Glhd\Gretel\Routing;

use Closure;
use Glhd\Gretel\Registry;
use Glhd\Gretel\Resolvers\ParentResolver;
use Glhd\Gretel\Resolvers\TitleResolver;
use Glhd\Gretel\Resolvers\UrlResolver;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use stdClass;

class ResourceBreadcrumbs
{
protected static array $parents = [
'create' => '.index',
'show' => '.index',
'edit' => '.show',
];

protected string $name;

protected array $options;

protected array $actions = [];

public function __construct(string $name, array $options = [])
{
$this->name = $name;
$this->options = $options;
}

public function configure(array $config): self
{
foreach ($config as $action => $title) {
$this->configureAction($action, $title);
}

return $this;
}

public function index($title, $parent = null, Closure $relation = null): self
{
return $this->configureAction('index', $title, $parent, $relation);
}

public function create($title, $parent = null, Closure $relation = null): self
{
return $this->configureAction('create', $title, $parent, $relation);
}

public function show($title, $parent = null, Closure $relation = null): self
{
return $this->configureAction('show', $title, $parent, $relation);
}

public function edit($title, $parent = null, Closure $relation = null): self
{
return $this->configureAction('edit', $title, $parent, $relation);
}

public function register(Registry $registry): void
{
$registry->withExceptionHandling(function(Registry $registry) {
foreach ($this->actions as $action => $config) {
$registry->register($this->makeBreadcrumbForAction($action, $config));
}
});
}

protected function configureAction(string $action, $title, $parent = null, ?Closure $relation = null): self
{
if (null === $parent && isset(static::$parents[$action])) {
$parent = static::$parents[$action];
}

$this->actions[$action] = (object) compact('title', 'parent', 'relation');

return $this;
}

protected function makeBreadcrumbForAction(string $action, stdClass $config): RouteBreadcrumb
{
if (is_string($config->parent)) {
$config->parent = preg_replace_callback('/^\.([a-z_-]+)$/', fn($matches) => $this->getRouteNameForAction($matches[1]), $config->parent);
}

$name = $this->getRouteNameForAction($action);
$title = TitleResolver::make($config->title);
$parent = ParentResolver::make($config->parent, $name, $config->relation);
$url = UrlResolver::make($name, $this->getParameterNamesForAction($action));

return new RouteBreadcrumb($name, $title, $parent, $url);
}

/** @see \Illuminate\Routing\ResourceRegistrar::getResourceRouteName() */
protected function getRouteNameForAction(string $action): string
{
$names = $this->options['names'] ?? [];
$action = $names[$action] ?? "{$this->name}.{$action}";

return trim(Route::mergeWithLastGroup(['as' => $action])['as'], '.');
}

protected function getParameterNamesForAction(string $action): array
{
$parameters = [];

if (in_array($action, ['show', 'edit'])) {
$parameters[] = $this->options['parameters'][$this->name] ?? Str::singular($this->name);
}

return $parameters;
}
}
Loading

0 comments on commit 2334944

Please sign in to comment.