BreadcrumbsBuilder::getBreadcrumbs()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 3
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Admin;
15
16
use Knp\Menu\ItemInterface;
17
use Symfony\Component\OptionsResolver\OptionsResolver;
18
19
/**
20
 * Stateless breadcrumbs builder (each method needs an Admin object).
21
 *
22
 * @author Grégoire Paris <[email protected]>
23
 */
24
final class BreadcrumbsBuilder implements BreadcrumbsBuilderInterface
25
{
26
    /**
27
     * @var string[]
28
     */
29
    private $config = [];
30
31
    /**
32
     * @param string[] $config
33
     */
34
    public function __construct(array $config = [])
35
    {
36
        $resolver = new OptionsResolver();
37
        $this->configureOptions($resolver);
38
39
        $this->config = $resolver->resolve($config);
40
    }
41
42
    public function configureOptions(OptionsResolver $resolver): void
43
    {
44
        $resolver->setDefaults([
45
            'child_admin_route' => 'edit',
46
        ]);
47
    }
48
49
    public function getBreadcrumbs(AdminInterface $admin, string $action): iterable
50
    {
51
        $breadcrumbs = [];
52
        if ($admin->isChild()) {
53
            return $this->getBreadcrumbs($admin->getParent(), $action);
54
        }
55
56
        $menu = $this->buildBreadcrumbs($admin, $action);
57
58
        do {
59
            $breadcrumbs[] = $menu;
60
        } while ($menu = $menu->getParent());
61
62
        $breadcrumbs = array_reverse($breadcrumbs);
63
        array_shift($breadcrumbs);
64
65
        return $breadcrumbs;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $breadcrumbs; (array) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...terface::getBreadcrumbs of type Sonata\AdminBundle\Admin\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
66
    }
67
68
    /**
69
     * Builds breadcrumbs for $action, starting from $menu.
70
     *
71
     * Note: the method will be called by the top admin instance (parent => child)
72
     */
73
    public function buildBreadcrumbs(
74
        AdminInterface $admin,
75
        string $action,
76
        ?ItemInterface $menu = null
77
    ): ItemInterface {
78
        if (!$menu) {
79
            $menu = $admin->getMenuFactory()->createItem('root');
80
81
            $menu = $menu->addChild(
82
                'link_breadcrumb_dashboard',
83
                [
84
                    'uri' => $admin->getRouteGenerator()->generate('sonata_admin_dashboard'),
85
                    'extras' => ['translation_domain' => 'SonataAdminBundle'],
86
                ]
87
            );
88
        }
89
90
        $menu = $this->createMenuItem(
91
            $admin,
92
            $menu,
93
            sprintf('%s_list', $admin->getClassnameLabel()),
94
            $admin->getTranslationDomain(),
95
            [
96
                'uri' => $admin->hasRoute('list') && $admin->hasAccess('list') ?
97
                $admin->generateUrl('list') :
98
                null,
99
            ]
100
        );
101
102
        $childAdmin = $admin->getCurrentChildAdmin();
103
104
        if ($childAdmin && $admin->hasSubject()) {
105
            $id = $admin->getRequest()->get($admin->getIdParameter());
106
107
            $menu = $menu->addChild(
108
                $admin->toString($admin->getSubject()),
109
                [
110
                    'uri' => $admin->hasRoute($this->config['child_admin_route']) && $admin->hasAccess($this->config['child_admin_route'], $admin->getSubject()) ?
111
                    $admin->generateUrl($this->config['child_admin_route'], ['id' => $id]) :
112
                    null,
113
                    'extras' => [
114
                        'translation_domain' => false,
115
                    ],
116
                ]
117
            );
118
119
            $menu->setExtra('safe_label', false);
120
121
            return $this->buildBreadcrumbs($childAdmin, $action, $menu);
122
        }
123
124
        if ('list' === $action) {
125
            $menu->setUri(null);
126
127
            return $menu;
128
        }
129
        if ('create' !== $action && $admin->hasSubject()) {
130
            return $menu->addChild($admin->toString($admin->getSubject()), [
131
                'extras' => [
132
                    'translation_domain' => false,
133
                ],
134
            ]);
135
        }
136
137
        return $this->createMenuItem(
138
            $admin,
139
            $menu,
140
            sprintf('%s_%s', $admin->getClassnameLabel(), $action),
141
            $admin->getTranslationDomain()
142
        );
143
    }
144
145
    /**
146
     * Creates a new menu item from a simple name. The name is normalized and
147
     * translated with the specified translation domain.
148
     *
149
     * @param AdminInterface $admin             used for translation
150
     * @param ItemInterface  $menu              will be modified and returned
151
     * @param string         $name              the source of the final label
152
     * @param string         $translationDomain for label translation
153
     * @param array          $options           menu item options
154
     */
155
    private function createMenuItem(
156
        AdminInterface $admin,
157
        ItemInterface $menu,
158
        string $name,
159
        ?string $translationDomain = null,
160
        array $options = []
161
    ): ItemInterface {
162
        $options = array_merge([
163
            'extras' => [
164
                'translation_domain' => $translationDomain,
165
            ],
166
        ], $options);
167
168
        return $menu->addChild(
169
            $admin->getLabelTranslatorStrategy()->getLabel(
170
                $name,
171
                'breadcrumb',
172
                'link'
173
            ),
174
            $options
175
        );
176
    }
177
}
178