BreadcrumbsManager::clearCurrentRoute()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Rivalex\Breadcrumbs;
4
5
use Rivalex\Breadcrumbs\Exceptions\DuplicateBreadcrumbException;
6
use Rivalex\Breadcrumbs\Exceptions\InvalidBreadcrumbException;
7
use Rivalex\Breadcrumbs\Exceptions\UnnamedRouteException;
8
use Rivalex\Breadcrumbs\Exceptions\ViewNotSetException;
9
use Illuminate\Contracts\View\Factory as ViewFactory;
10
use Illuminate\Routing\Router;
11
use Illuminate\Support\Collection;
12
use Illuminate\Support\HtmlString;
13
use Illuminate\Support\Traits\Macroable;
14
15
/**
16
 * The main Breadcrumbs singleton class, responsible for registering, generating and rendering breadcrumbs.
17
 */
18
class BreadcrumbsManager
19
{
20
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by Rivalex\Breadcrumbs\BreadcrumbsManager.
Loading history...
21
22
    /**
23
     * @var BreadcrumbsGenerator
24
     */
25
    protected $generator;
26
27
    /**
28
     * @var Router
29
     */
30
    protected $router;
31
32
    /**
33
     * @var ViewFactory
34
     */
35
    protected $viewFactory;
36
37
    /**
38
     * @var array The registered breadcrumb-generating callbacks.
39
     */
40
    protected $callbacks = [];
41
42
    /**
43
     * @var array Closures to call before generating breadcrumbs for the current page.
44
     */
45
    protected $before = [];
46
47
    /**
48
     * @var array Closures to call after generating breadcrumbs for the current page.
49
     */
50
    protected $after = [];
51
52
    /**
53
     * @var array|null The current route name and parameters.
54
     */
55
    protected $route;
56
57
    public function __construct(BreadcrumbsGenerator $generator, Router $router, ViewFactory $viewFactory)
58
    {
59
        $this->generator = $generator;
60
        $this->router = $router;
61
        $this->viewFactory = $viewFactory;
62
    }
63
64
    /**
65
     * Register a breadcrumb-generating callback for a page.
66
     *
67
     * @param string $name The name of the page.
68
     * @param callable $callback The callback, which should accept a Generator instance as the first parameter and may
69
     *     accept additional parameters.
70
     * @return void
71
     * @throws \Rivalex\Breadcrumbs\Exceptions\DuplicateBreadcrumbException If the given name has already been
72
     *     used.
73
     */
74
    public function for(string $name, callable $callback): void
75
    {
76
        if (isset($this->callbacks[$name])) {
77
            throw new DuplicateBreadcrumbException($name);
78
        }
79
80
        $this->callbacks[$name] = $callback;
81
    }
82
83
    /**
84
     * Register a breadcrumb-generating callback for a page.
85
     *
86
     * For backwards-compatibility with v5.0.0 and below.
87
     *
88
     * @param string $name The name of the page.
89
     * @param callable $callback The callback, which should accept a Generator instance as the first parameter and may
90
     *     accept additional parameters.
91
     * @return void
92
     * @throws \Rivalex\Breadcrumbs\Exceptions\DuplicateBreadcrumbException If the given name has already been
93
     *     used.
94
     * @see self::for()
95
     */
96
    public function register(string $name, callable $callback): void
97
    {
98
        $this->for($name, $callback);
99
    }
100
101
    /**
102
     * Register a closure to call before generating breadcrumbs for the current page.
103
     *
104
     * For example, this can be used to always prepend the homepage without needing to manually add it to each page.
105
     *
106
     * @param callable $callback The callback, which should accept a Generator instance as the first and only parameter.
107
     * @return void
108
     */
109
    public function before(callable $callback): void
110
    {
111
        $this->before[] = $callback;
112
    }
113
114
    /**
115
     * Register a closure to call after generating breadcrumbs for the current page.
116
     *
117
     * For example, this can be used to append the current page number when using pagination.
118
     *
119
     * @param callable $callback The callback, which should accept a Generator instance as the first and only parameter.
120
     * @return void
121
     */
122
    public function after(callable $callback): void
123
    {
124
        $this->after[] = $callback;
125
    }
126
127
    /**
128
     * Check if a breadcrumb with the given name exists.
129
     *
130
     * If no name is given, defaults to the current route name.
131
     *
132
     * @param string|null $name The page name.
133
     * @return bool Whether there is a registered callback with that name.
134
     */
135
    public function exists(string $name = null): bool
136
    {
137
        if (is_null($name)) {
138
            try {
139
                [$name] = $this->getCurrentRoute();
140
            } catch (UnnamedRouteException $e) {
141
                return false;
142
            }
143
        }
144
145
        return isset($this->callbacks[$name]);
146
    }
147
148
    /**
149
     * Generate a set of breadcrumbs for a page.
150
     *
151
     * @param string|null $name The name of the current page.
152
     * @param mixed ...$params The parameters to pass to the closure for the current page.
153
     * @return \Illuminate\Support\Collection The generated breadcrumbs.
154
     * @throws \Rivalex\Breadcrumbs\Exceptions\UnnamedRouteException if no name is given and the current route
155
     *     doesn't have an associated name.
156
     * @throws \Rivalex\Breadcrumbs\Exceptions\InvalidBreadcrumbException if the name is (or any ancestor names
157
     *     are) not registered.
158
     */
159
    public function generate(string $name = null, ...$params): Collection
160
    {
161
        $origName = $name;
162
163
        // Route-bound breadcrumbs
164
        if ($name === null) {
165
            try {
166
                [$name, $params] = $this->getCurrentRoute();
167
            } catch (UnnamedRouteException $e) {
168
                if (config('breadcrumbs.unnamed-route-exception')) {
169
                    throw $e;
170
                }
171
172
                return new Collection;
173
            }
174
        }
175
176
        // Generate breadcrumbs
177
        try {
178
            return $this->generator->generate($this->callbacks, $this->before, $this->after, $name, $params);
179
        } catch (InvalidBreadcrumbException $e) {
180
            if ($origName === null && config('breadcrumbs.missing-route-bound-breadcrumb-exception')) {
181
                $e->setIsRouteBound();
182
                throw $e;
183
            }
184
185
            if ($origName !== null && config('breadcrumbs.invalid-named-breadcrumb-exception')) {
186
                throw $e;
187
            }
188
189
            return new Collection;
190
        }
191
    }
192
193
    /**
194
     * Render breadcrumbs for a page with the specified view.
195
     *
196
     * @param string $view The name of the view to render.
197
     * @param string|null $name The name of the current page.
198
     * @param mixed ...$params The parameters to pass to the closure for the current page.
199
     * @return \Illuminate\Support\HtmlString The generated HTML.
200
     * @throws \Rivalex\Breadcrumbs\Exceptions\InvalidBreadcrumbException if the name is (or any ancestor names are) not registered.
201
     * @throws \Rivalex\Breadcrumbs\Exceptions\UnnamedRouteException if no name is given and the current route doesn't have an associated name.
202
     * @throws \Rivalex\Breadcrumbs\Exceptions\ViewNotSetException if no view has been set.
203
     */
204
    public function view(string $view, string $name = null, ...$params): HtmlString
205
    {
206
        $breadcrumbs = $this->generate($name, ...$params);
207
208
        // TODO: After dropping support for Laravel 5.8 and below, change this to return the view directly
209
        // https://github.com/laravel/framework/pull/29600
210
        $html = $this->viewFactory->make($view, compact('breadcrumbs'))->render();
211
212
        return new HtmlString($html);
0 ignored issues
show
Bug introduced by
It seems like $html can also be of type array; however, parameter $html of Illuminate\Support\HtmlString::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

212
        return new HtmlString(/** @scrutinizer ignore-type */ $html);
Loading history...
213
    }
214
215
    /**
216
     * Render breadcrumbs for a page with the default view.
217
     *
218
     * @param string|null $name The name of the current page.
219
     * @param mixed ...$params The parameters to pass to the closure for the current page.
220
     * @return \Illuminate\Support\HtmlString The generated HTML.
221
     * @throws \Rivalex\Breadcrumbs\Exceptions\InvalidBreadcrumbException if the name is (or any ancestor names are) not registered.
222
     * @throws \Rivalex\Breadcrumbs\Exceptions\UnnamedRouteException if no name is given and the current route doesn't have an associated name.
223
     * @throws \Rivalex\Breadcrumbs\Exceptions\ViewNotSetException if no view has been set.
224
     */
225
    public function render(string $name = null, ...$params): HtmlString
226
    {
227
        $view = config('breadcrumbs.view');
228
229
        if (!$view) {
230
            throw new ViewNotSetException('Breadcrumbs view not specified (check config/breadcrumbs.php)');
231
        }
232
233
        return $this->view($view, $name, ...$params);
234
    }
235
236
    /**
237
     * Get the last breadcrumb for the current page.
238
     *
239
     * Optionally pass a
240
     *
241
     * @return \stdClass|null The breadcrumb for the current page.
242
     * @throws \Rivalex\Breadcrumbs\Exceptions\UnnamedRouteException if the current route doesn't have an associated name.
243
     * @throws \Rivalex\Breadcrumbs\Exceptions\InvalidBreadcrumbException if the name is (or any ancestor names are) not registered.
244
     */
245
    public function current(): ?\stdClass
246
    {
247
        return $this->generate()->where('current', '!==', false)->last();
248
    }
249
250
    /**
251
     * Get the current route name and parameters.
252
     *
253
     * This may be the route set manually with the setCurrentRoute() method, but normally is the route retrieved from
254
     * the Laravel Router.
255
     *
256
     * #### Example
257
     * ```php
258
     * [$name, $params] = $this->getCurrentRoute();
259
     * ```
260
     *
261
     * @return array A two-element array consisting of the route name (string) and any parameters (array).
262
     * @throws \Rivalex\Breadcrumbs\Exceptions\UnnamedRouteException if the current route doesn't have an associated name.
263
     */
264
    protected function getCurrentRoute()
265
    {
266
        // Manually set route
267
        if ($this->route) {
268
            return $this->route;
269
        }
270
271
        // Determine the current route
272
        $route = $this->router->current();
273
274
        // No current route - must be the 404 page
275
        if ($route === null) {
276
            return ['errors.404', []];
277
        }
278
279
        // Convert route to name
280
        $name = $route->getName();
281
282
        if ($name === null) {
283
            throw new UnnamedRouteException($route);
284
        }
285
286
        // Get the current route parameters
287
        $params = array_values($route->parameters());
288
289
        return [$name, $params];
290
    }
291
292
    /**
293
     * Set the current route name and parameters to use when calling render() or generate() with no parameters.
294
     *
295
     * @param string $name The name of the current page.
296
     * @param mixed ...$params The parameters to pass to the closure for the current page.
297
     * @return void
298
     */
299
    public function setCurrentRoute(string $name, ...$params): void
300
    {
301
        $this->route = [$name, $params];
302
    }
303
304
    /**
305
     * Clear the previously set route name and parameters to use when calling render() or generate() with no parameters.
306
     *
307
     * Next time it will revert to the default behaviour of using the current route from Laravel.
308
     *
309
     * @return void
310
     */
311
    public function clearCurrentRoute(): void
312
    {
313
        $this->route = null;
314
    }
315
}
316