Completed
Push — master ( fbe5fe...174720 )
by Vitaliy
04:48
created

Grid::makeDataInjector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace ViewComponents\Grids;
4
5
use Nayjest\Collection\Extended\ObjectCollectionReadInterface;
6
use RuntimeException;
7
use ViewComponents\Grids\Component\Column;
8
use ViewComponents\ViewComponents\Base\ComponentInterface;
9
use ViewComponents\ViewComponents\Component\Html\Tag;
10
use ViewComponents\ViewComponents\Component\ManagedList;
11
use ViewComponents\ViewComponents\Component\ManagedList\RecordView;
12
use ViewComponents\ViewComponents\Component\Part;
13
use ViewComponents\Grids\Component\SolidRow;
14
15
/**
16
 * Grid component.
17
 */
18
class Grid extends ManagedList
19
{
20
    const TABLE_ID = 'table';
21
    const TABLE_HEADING_ID = 'table_heading';
22
    const TABLE_FOOTER_ID = 'table_footer';
23
    const TABLE_BODY_ID = 'table_body';
24
    const TITLE_ROW_ID = 'title_row';
25
    const CONTROL_ROW_ID = 'control_row';
26
27
    use GridPartsAccessTrait;
28
29
    /** @var  mixed|null */
30
    protected $currentRow;
31
32
    /**
33
     * Returns column collection (readonly).
34
     *
35
     * @return Column[]|ObjectCollectionReadInterface
36
     */
37
    public function getColumns()
38
    {
39
        return $this->getComponents()->filterByType(Column::class);
40
    }
41
42
    /**
43
     * Returns column with specified id,
44
     * throws exception if grid does not have specified column.
45
     *
46
     * @param string $id
47
     * @return Column
48
     */
49
    public function getColumn($id)
50
    {
51
        $column = $this->getComponent($id);
52
        if (!$column) {
53
            throw new RuntimeException("Column '$id' is not defined.");
54
        }
55
        if (!$column instanceof Column) {
56
            throw new RuntimeException("'$id' is not a column.");
57
        }
58
        return $column;
59
    }
60
61
    public function setControlContainer(ComponentInterface $component)
62
    {
63
        return $this->setComponent($component, static::CONTROL_CONTAINER_ID, static::CONTROL_ROW_ID);
64
    }
65
66
    public function setSubmitButton(ComponentInterface $component)
67
    {
68
        return $this->setComponent($component, static::SUBMIT_BUTTON_ID, static::CONTROL_ROW_ID);
69
    }
70
71
    public function setListContainer(ComponentInterface $component)
72
    {
73
        return $this->setComponent($component, static::LIST_CONTAINER_ID, static::TABLE_BODY_ID);
74
    }
75
76
    /**
77
     * Returns current grid row, used internally for grid rendering.
78
     *
79
     * @internal
80
     * @return mixed
81
     */
82
    public function getCurrentRow()
83
    {
84
        return $this->currentRow;
85
    }
86
87
    /**
88
     * Sets current grid row for rendering, used internally for grid rendering.
89
     *
90
     * @internal
91
     * @param mixed $currentRow
92
     * @return $this
93
     */
94
    public function setCurrentRow($currentRow)
95
    {
96
        $this->currentRow = $currentRow;
97
        return $this;
98
    }
99
100
    protected function makeDataInjector()
101
    {
102
        return [$this, 'setCurrentRow'];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($this, 'setCurrentRow'); (array<ViewComponents\Grids\Grid|string>) is incompatible with the return type of the parent method ViewComponents\ViewCompo...dList::makeDataInjector of type Closure.

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...
103
    }
104
105
    protected function makeDefaultComponents()
106
    {
107
        $components = parent::makeDefaultComponents();
108
        unset($components[ManagedList::RECORD_VIEW_ID]);
109
        $components[ManagedList::CONTROL_CONTAINER_ID]->setDestinationParentId(static::CONTROL_ROW_ID);
110
        $components[ManagedList::LIST_CONTAINER_ID]->setDestinationParentId(static::TABLE_BODY_ID);
111
        $components[ManagedList::SUBMIT_BUTTON_ID]->setDestinationParentId(static::CONTROL_ROW_ID);
112
        return array_merge(
113
            $components,
114
            [
115
                static::TABLE_ID => new Part(
116
                    new Tag('table'),
117
                    static::TABLE_ID,
118
                    static::FORM_ID
119
                ),
120
                static::TABLE_HEADING_ID => new Part(
121
                    new Tag('thead'),
122
                    static::TABLE_HEADING_ID,
123
                    static::TABLE_ID
124
                ),
125
                static::TITLE_ROW_ID => new Part(
126
                    new Tag('tr'),
127
                    static::TITLE_ROW_ID,
128
                    static::TABLE_HEADING_ID
129
                ),
130
                static::CONTROL_ROW_ID => new Part(
131
                    new SolidRow(),
132
                    static::CONTROL_ROW_ID,
133
                    static::TABLE_HEADING_ID
134
                ),
135
                static::TABLE_BODY_ID => new Part(new Tag('tbody'), static::TABLE_BODY_ID, static::TABLE_ID),
136
                static::RECORD_VIEW_ID => new RecordView(new Tag('tr')),
137
                static::TABLE_FOOTER_ID => new Part(new Tag('tfoot'), static::TABLE_FOOTER_ID, static::TABLE_ID),
138
            ]
139
        );
140
    }
141
142
    /**
143
     * Prepares component for rendering.
144
     */
145
    protected function prepare()
146
    {
147
        parent::prepare();
148
        $this->hideControlRowIfEmpty();
149
    }
150
151
    protected function hideControlRowIfEmpty()
152
    {
153
        $row = $this->getControlRow();
154
        $container = $this->getControlContainer();
155
        if (!$row
156
            || !$container
157
            || !$container->children()->isEmpty()
158
            || ($row->children()->count() > 2) // submit button + control_container
159
        ) {
160
            return;
161
        }
162
        /** @var Part $rowPart */
163
        $rowPart = $this->getComponent(static::CONTROL_ROW_ID, false);
164
        $rowPart->setView(null);
165
    }
166
}
167