Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@
->tag(
'kernel.event_listener',
[
'event' => 'kernel.controller',
'method' => 'onKernelController',
'event' => 'kernel.controller_arguments',
'method' => 'onKernelControllerArguments',
'priority' => -1
]
)
Expand All @@ -138,8 +138,8 @@
->tag(
'kernel.event_listener',
[
'event' => 'kernel.controller',
'method' => 'onKernelController',
'event' => 'kernel.controller_arguments',
'method' => 'onKernelControllerArguments',
'priority' => -1
]
)
Expand Down
174 changes: 76 additions & 98 deletions docs/development/breadcrumb.md
Original file line number Diff line number Diff line change
@@ -1,193 +1,171 @@
# Using the breadcrumb

The breadcrumb is a nice way to indicate where a user is in the application.
## Basics

## Manual breadcrumbs
Adding a breadcrumb is as simple as adding a `#[Breadcrumb()]` attribute.
Add a `#[Breadcrumb]` attribute to a controller method to register a crumb. The attribute is repeatable — each one
appends a crumb to the trail in declaration order.

Example: single
```php
#[Route('single', name:'single')]
#[Breadcrumb('single')]
public function __invoke(): Response
{
}
use SumoCoders\FrameworkCoreBundle\Attribute\Breadcrumb;
```

Example: attributes on the class
Single crumb:

```php
#[Route('single', name:'single')]
#[Breadcrumb('single')]
class Controller
#[Route('/books', name: 'books_overview')]
#[Breadcrumb('books')]
public function __invoke(): Response
{
public function __invoke(): Response
{
}
}
```

Adding multiple attributes will chain the breadcrumb.
Chained crumbs on one controller:

Example: single > multiple
```php
#[Route('/single/multiple', name:'multiple')]
#[Breadcrumb('single')]
#[Breadcrumb('multiple')]
#[Route('/books/genres', name: 'genres_overview')]
#[Breadcrumb('books')]
#[Breadcrumb('genres')]
public function __invoke(): Response
{
}
```

When needed a route can be added to the breadcrumb.
## Class-level attributes

`#[Breadcrumb]` can be placed on the class instead of the method. Class attributes are only picked up for `__invoke`
controllers, or for named methods that also have at least one `#[Breadcrumb]` attribute of their own.

Example: [single](#) > multiple
```php
#[Route('/single/multiple', name:'multiple')]
#[Breadcrumb('single', route:['name' => 'single'])]
#[Breadcrumb('multiple')]
public function __invoke(): Response
#[Breadcrumb('books')]
class BooksController
{
public function __invoke(): Response
{
}
}
```
Route parameters are also supported, but can only accept fixed values so are less useful to build dynamic breadcrumbs.

## Linked breadcrumbs (`route:`)

Pass `route:` to make the crumb a link. Required route parameters are automatically resolved from the current
controller's named arguments and request attributes — you do not need to specify them manually.

```php
#[Route('/single/{foo}', name:'multiple')]
#[Breadcrumb('single', route:['name' => 'single', 'parameters' => ['foo' => 'bar']])]
public function __invoke(string $foo): Response
#[Route('/books/genres', name: 'genres_overview')]
#[Breadcrumb('books', route: ['name' => 'books_overview'])]
#[Breadcrumb('genres')]
public function __invoke(): Response
{
}
```

## Automated breadcrumbs
A breadcrumb trail can be generated by passing the name of another route to the attribute.
When a parameter cannot be resolved automatically, you can supply a fixed value. This is rarely needed and ties the
breadcrumb to a hardcoded value:

```php
#[Breadcrumb('section', route: ['name' => 'section_detail', 'parameters' => ['id' => 42]])]
```

The parent route **must have a breadcrumb attribute as well**, as the chain is built by parsing and resolving the attribute in the given parent route.
## Parent route chaining (`parent:`)

Parents are always rendered as a link. Parameters of the parent route are copied from the base route and the current request.
Pass `parent:` to automatically prepend the full breadcrumb trail of another route. The parent route must have its own
`#[Breadcrumb]` attribute. Parameters are resolved from the current request.

Example: [books](#) > genres
```php
#[Route('/books', name:'books_overview')]
#[Route('/books', name: 'books_overview')]
#[Breadcrumb('books')]
public function __invoke(): Response
{
}

#[Route('/books/genres', name:'genres_overview')]
#[Breadcrumb('genres', parent:['name' => 'books_overview'])]
#[Route('/books/genres', name: 'genres_overview')]
#[Breadcrumb('genres', parent: ['name' => 'books_overview'])]
public function __invoke(): Response
{
}
```

## Rendering parameters in the breadcrumb
You can reference the passed parameter in your breadcrumb by putting it between curly brackets in the breadcrumb title.
The chain is resolved recursively, so parents of parents work as long as each route in the chain has `#[Breadcrumb]`.

Example: Authors > whatever-you-pass-as-$name
```php
#[Route('/author/{name}')]
#[Breadcrumb('authors')]
#[Breadcrumb('{name}')]
public function __invoke(string $name): Response
{
}
```
When ParamConverters are used it's possible to use a property of the object as breadcrumb value.
## Dynamic titles from object properties

Use `{object.property}` to read a value from a controller argument at request time:

Example: Books > Harry Potter
```php
#[Route('/book/{book}')]
#[Breadcrumb('books')]
#[Route('/books/{book}', name: 'book_detail')]
#[Breadcrumb('books', route: ['name' => 'books_overview'])]
#[Breadcrumb('{book.title}')]
public function __invoke(Book $book): Response
{
}
```

This also works with chained parent attributes, as long as the parameter names are still present in the route.

Example: J.K. Rowling > Harry Potter
```php
#[Route('/{author}', name:'author_detail')]
#[Breadcrumb('{author.name}')]
public function __invoke(Author $author): Response
{
}

// ! If we'd omit the /{author} from this route, this example would no longer work !
#[Route('/{author}/{book}', name:'book_detail')]
#[Breadcrumb('{book.title}', parent:['name' => 'author_detail'])]
public function __invoke(Author $author, Book $book): Response
{
}
```
If you reference a parent route with required parameters, and the required parameter cannot be resolved from the current request, an error will be thrown.
This also works when combined with `parent:`, as long as the required route parameters are present in the URL:

You can also supply parameters yourself, just like you would with a route, but this has the same drawbacks. You can only pass fixed values, which kind of defeats the purpose of building dynamic breadcrumbs. It is strongly recommended to **match your breadcrumbs to your URL structure**.
```php
#[Route('/{book}', name:'book_detail')]
#[Breadcrumb('{book.title}', parent:['name' => 'author_detail', 'parameters' => ['author' => 4]])]
// ! /{author} must be in the route for parameter resolution to work
#[Route('/{author}/{book}', name: 'book_detail')]
#[Breadcrumb('{book.title}', parent: ['name' => 'author_detail'])]
public function __invoke(Author $author, Book $book): Response
{
}
```

Custom ParamConverters are **not** supported. Only basic Doctrine Entity -> ID will work out of the box.
> Scalar parameters (e.g. `string $name`) cannot be used with the `{name}` syntax — only objects with a property path
> are supported. Using a scalar silently omits the breadcrumb.

## Translations
It's possible to use translations as these get translated in the template.

Example: Authors
All breadcrumb titles pass through the `|trans` Twig filter when rendered. Translation keys work out of the box:

```yaml
breadcrumb.authors: 'Authors'
# translations/messages.en.yaml
breadcrumb.books: 'Books'
```

```php
#[Route('/authors/{author}', name:'authors')]
#[Breadcrumb('breadcrumb.authors')]
public function book(Author $author): Response
{
}
#[Breadcrumb('breadcrumb.books')]
```


It's also possible to add paramters to the translation.
For parameterized translations, pass `parameters:` as an array where keys are the translation placeholders and values
are `object.property` paths resolved from the current named arguments:

```yaml
breadcrumb.authors: 'Author: %name%'
breadcrumb.author_detail: 'Author: %name%'
```

```php
#[Route('/authors/{author}', name:'authors')]
#[Breadcrumb('breadcrumb.authors', parameters: ['%name%' => 'author.name
public function book(Author $author): Response
#[Route('/author/{author}', name: 'author_detail')]
#[Breadcrumb('breadcrumb.author_detail', parameters: ['%name%' => 'author.name'])]
public function __invoke(Author $author): Response
{
}
```

## Full example
Auteurs > J.K. Rowling > Harry Potter

Trail: Authors > J.K. Rowling > Harry Potter

```php
#[Route('/author', name:'author_overview')]
#[Breadcrumb('authors')]
#[Route('/authors', name: 'author_overview')]
#[Breadcrumb('breadcrumb.authors')]
public function __invoke(): Response
{
}

#[Route('/author/{author}', name:'author_detail')]
#[Breadcrumb('{author.name}', parent:['name' => 'author_overview']))]
#[Route('/authors/{author}', name: 'author_detail')]
#[Breadcrumb('{author.name}', parent: ['name' => 'author_overview'])]
public function __invoke(Author $author): Response
{
}

#[Route('/{author}/{book}', name:'book_detail')]
#[Breadcrumb('{book.title}', parent:['name' => 'author_detail'])]
#[Route('/authors/{author}/{book}', name: 'book_detail')]
#[Breadcrumb('{book.title}', parent: ['name' => 'author_detail'])]
public function __invoke(Author $author, Book $book): Response
{
}
```

```yaml
authors: 'Auteurs'
breadcrumb.authors: 'Authors'
```
Loading