Passed
Push — develop ( 97b747...98d07f )
by Ludwig
05:36
created

AbstractGrid::addSearch()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.5125
c 0
b 0
f 0
cc 5
eloc 12
nc 8
nop 2
1
<?php
2
/*
3
 * This file is part of CwdBootgridBundle
4
 *
5
 * (c)2016 cwd.at GmbH <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Cwd\FancyGridBundle\Grid;
11
12
use Cwd\FancyGridBundle\Column\AbstractColumn;
13
use Cwd\FancyGridBundle\Column\ColumnInterface;
14
use Doctrine\Common\Persistence\ObjectManager;
15
use Doctrine\ORM\QueryBuilder;
16
use JMS\Serializer\Serializer;
17
use JMS\Serializer\SerializerBuilder;
18
use Pagerfanta\Adapter\DoctrineORMAdapter;
19
use Pagerfanta\Pagerfanta;
20
use Symfony\Component\OptionsResolver\OptionsResolver;
21
use Symfony\Component\PropertyAccess\PropertyAccess;
22
use Symfony\Component\Translation\TranslatorInterface;
23
24
/**
25
 * Class AbstractGrid
26
 * @package Cwd\FancyGridBundle\Grid
27
 * @author Ludwig Ruderstaler <[email protected]>
28
 */
29
abstract class AbstractGrid implements GridInterface, \IteratorAggregate
30
{
31
    /**
32
     * @var array
33
     */
34
    protected $options;
35
36
    /**
37
     * @var array
38
     */
39
    protected $children = [];
40
41
    /**
42
     * @var ObjectManager
43
     */
44
    protected $objectManager;
45
46
    /**
47
     * @var TranslatorInterface
48
     */
49
    protected $translator;
50
51
    /**
52
     * @var \Symfony\Component\PropertyAccess\PropertyAccessor
53
     */
54
    protected $accessor;
55
56
    /**
57
     * @var null|string
58
     */
59
    protected $primary = null;
60
61
    /**
62
     * @var \Twig_Environment
63
     */
64
    protected $twig;
65
66
    /**
67
     * AbstractGrid constructor.
68
     * @param array $options
69
     */
70
    public function __construct(TranslatorInterface $translator, array $options = array())
71
    {
72
        $resolver = new OptionsResolver();
73
        $this->configureOptions($resolver);
74
75
        $this->options = $resolver->resolve($options);
76
        $this->translator = $translator;
77
        $this->accessor = PropertyAccess::createPropertyAccessor();
78
    }
79
80
    /**
81
     * @param \Twig_Environment $twig
82
     */
83
    public function setTwig(\Twig_Environment $twig)
84
    {
85
        $this->twig = $twig;
86
    }
87
88
    /**
89
     * @param ObjectManager $objectManager
90
     * @return $this
91
     */
92
    public function setObjectManager($objectManager)
93
    {
94
        $this->objectManager = $objectManager;
95
96
        return $this;
97
    }
98
99
    /**
100
     * generate gridid
101
     * @return string
102
     */
103
    public function getId()
104
    {
105
        $data = [
106
            $this->getOption('data_route'),
107
            $this->getOption('data_route_options'),
108
            $this->getOption('template'),
109
        ];
110
111
        return md5(serialize($data));
112
    }
113
114
    /**
115
     * @return array
116
     */
117
    public function getOptions()
118
    {
119
        return $this->options;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function buildGrid(GridBuilderInterface $builder, array $options)
126
    {
127
    }
128
129
    /**
130
     * @return array
131
     */
132
    public function getData()
133
    {
134
        $queryBuilder = $this->getQueryBuilder($this->objectManager, $this->getOptions());
135
136
        if ($this->getOption('sortField') !== null) {
137
            $field = $this->getOption('sortField');
138
            if ($this->has($field)) {
139
                $column = $this->get($field);
140
                $queryBuilder->orderBy($column->getField(), $this->getOption('sortDir'));
141
            }
142
        }
143
144
        if ($this->getOption('filter', false)) {
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
145
            $this->addSearch($queryBuilder, $this->all());
0 ignored issues
show
Documentation introduced by
$queryBuilder is of type object<Doctrine\ORM\QueryBuilder>, but the function expects a object<Doctrine\DBAL\Query\QueryBuilder>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
146
        }
147
148
        $pager = $this->getPager($queryBuilder);
149
150
        return [
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('totalCount...)), 'success' => true); (array) is incompatible with the return type declared by the interface Cwd\FancyGridBundle\Grid\GridInterface::getData of type string.

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...
151
            //'current' => $pager->getCurrentPage(),
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
152
            //'rowCount' => $this->getOption('rowCount', 10),
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
153
            'totalCount' => $pager->getNbResults(),
154
            'data'  => $this->parseData($pager->getCurrentPageResults()),
155
            'success' => true,
156
        ];
157
    }
158
159
    /**
160
     * @param \Doctrine\DBAL\Query\QueryBuilder      $queryBuilder
161
     * @param ColumnInterface[] $columns
162
     */
163
    protected function addSearch($queryBuilder, $columns)
0 ignored issues
show
Unused Code introduced by
The parameter $columns is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
164
    {
165
166
        $filter = json_decode($this->getOption('filter'));
167
        $where = $queryBuilder->expr()->andX();
168
169
        foreach ($filter as $filterSearch) {
170
            if (!$this->has($filterSearch->property)) {
171
                continue;
172
            }
173
174
            $column = $this->get($filterSearch->property);
175
176
            if ($filterSearch->operator == 'like') {
177
                $where->add($queryBuilder->expr()->like($column->getField(), ':'.$filterSearch->property));
178
                $queryBuilder->setParameter(':'.$filterSearch->property, '%'.$filterSearch->value.'%');
179
            }
180
181
        }
182
183
        if (count($where->getParts()) > 0) {
184
            $queryBuilder->having($where);
185
        }
186
    }
187
188
    /**
189
     * @param array|\Traversable $rows
190
     * @return array
191
     */
192
    protected function parseData($rows)
193
    {
194
        $data = [];
195
        foreach ($rows as $row) {
196
            $rowData = [];
197
198
            foreach ($this->all() as $column) {
199
                /** @var ColumnInterface $column */
200
                $value = $column->getValue($row, $column->getName(), $this->findPrimary(), $this->accessor);
201
                $value = $column->render($value, $row, $this->getPrimaryValue($row), $this->twig);
202
203
                if ($column->getOption('translatable', false)) {
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
204
                    $value = $this->translator->trans($value, [], $column->getOption('translation_domain'));
205
                }
206
207
                $rowData[$column->getName()] = $value;
208
            }
209
210
            $data[] = $rowData;
211
        }
212
213
        return $data;
214
    }
215
216
    /**
217
     * @param QueryBuilder $queryBuilder
218
     * @return Pagerfanta
219
     */
220
    public function getPager(QueryBuilder $queryBuilder)
221
    {
222
        $adapter = new DoctrineORMAdapter($queryBuilder, false);
223
        $pager = new Pagerfanta($adapter);
224
        $page = $this->getOption('page', 1);
225
        if ($page < 1) {
226
            $page = 1;
227
        }
228
229
        $pager->setCurrentPage($page)
230
              ->setMaxPerPage($this->getOption('limit', 10));
231
232
        return $pager;
233
    }
234
235
    /**
236
     * Get value of primary column
237
     *
238
     * @param mixed $object
239
     *
240
     * @return mixed
241
     */
242
    public function getPrimaryValue($object)
243
    {
244
        if ($this->primary === null) {
245
            $this->primary = $this->findPrimary();
246
        }
247
248
        /** special case when counting */
249
        if (is_array($object)) {
250
            $object = $object[0];
251
        }
252
253
        return $this->accessor->getValue($object, $this->primary);
254
    }
255
256
    /**
257
     * @return null|string
258
     */
259
    public function findPrimary()
260
    {
261
        foreach ($this->all() as $column) {
262
            if (true === $column->getOption('identifier')) {
263
                return $column->getName();
264
            }
265
        }
266
267
        return null;
268
    }
269
270
    /**
271
     * @param OptionsResolver $resolver
272
     */
273
    public function configureOptions(OptionsResolver $resolver)
274
    {
275
        $resolver->setDefaults([
276
            'template' => 'CwdBootgridBundle:Grid:template.html.twig',
277
            'current' => 1,
278
            'filter' => null,
279
            'sortField' => null,
280
            'sortDir' => null,
281
            'data_route_options' => [],
282
            'page' => 1,
283
            'limit' => 20,
284
        ]);
285
286
        $resolver->setRequired([
287
            'template',
288
            'data_route',
289
        ]);
290
    }
291
292
    public function getColumnDefinition()
293
    {
294
        $columns = [];
295
        /** @var AbstractColumn $column */
296
        foreach ($this->children as $column) {
297
            $column->setTranslator($this->translator);
298
            $columns[] = $column->renderOptions();
299
        }
300
301
        return $columns;
302
    }
303
304
    /**
305
     * @param string $name
306
     * @return bool
307
     */
308
    public function hasOption($name)
309
    {
310
        return array_key_exists($name, $this->options);
311
    }
312
313
    /**
314
     * @param string      $name
315
     * @param string|null $default
316
     * @return misc
317
     */
318
    public function getOption($name, $default = null)
319
    {
320
        return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
321
    }
322
323
    /**
324
     * @param string $name
325
     * @return ColumnInterface
326
     */
327 View Code Duplication
    public function get($name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
328
    {
329
        if (isset($this->children[$name])) {
330
            return $this->children[$name];
331
        }
332
333
        throw new InvalidArgumentException(sprintf('The child with the name "%s" does not exist.', $name));
334
    }
335
336
    /**
337
     * @param string $name
338
     * @return $this
339
     */
340
    public function remove($name)
341
    {
342
        unset($this->children[$name]);
343
344
        return $this;
345
    }
346
347
    /**
348
     * @param string $name
349
     * @return bool
350
     */
351
    public function has($name)
352
    {
353
        return isset($this->children[$name]);
354
    }
355
356
    /**
357
     * @return \Cwd\FancyGridBundle\Column\ColumnInterface[]
358
     */
359
    public function all()
360
    {
361
        return $this->children;
362
    }
363
364
    /**
365
     * @param array<ColumnInterface> $children
366
     * @return $this
367
     */
368
    public function setChildren($children)
369
    {
370
        $this->children = $children;
371
372
        return $this;
373
    }
374
375
    /**
376
     *
377
     * @return \ArrayIterator
378
     */
379
    public function getIterator()
380
    {
381
        return new \ArrayIterator($this->all());
382
    }
383
}
384