Completed
Pull Request — develop (#368)
by Adrian
13:00 queued 06:33
created

DocumentModel::getDefaultLimit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625
Metric Value
dl 0
loc 8
rs 9.4285
ccs 3
cts 4
cp 0.75
cc 2
eloc 4
nc 2
nop 0
crap 2.0625
1
<?php
2
/**
3
 * Use doctrine odm as backend
4
 */
5
6
namespace Graviton\RestBundle\Model;
7
8
use Doctrine\ODM\MongoDB\DocumentRepository;
9
use Graviton\SchemaBundle\Model\SchemaModel;
10
use Graviton\SecurityBundle\Entities\SecurityUser;
11
use Symfony\Component\HttpFoundation\Request;
12
use Doctrine\ODM\MongoDB\Query\Builder;
13
use Graviton\Rql\Visitor\MongoOdm as Visitor;
14
use Xiag\Rql\Parser\Query;
15
use Graviton\ExceptionBundle\Exception\RecordOriginModifiedException;
16
use Xiag\Rql\Parser\Exception\SyntaxErrorException as RqlSyntaxErrorException;
17
18
/**
19
 * Use doctrine odm as backend
20
 *
21
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
22
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
23
 * @link     http://swisscom.ch
24
 */
25
class DocumentModel extends SchemaModel implements ModelInterface
26
{
27
    /**
28
     * @var string
29
     */
30
    protected $description;
31
    /**
32
     * @var string[]
33
     */
34
    protected $fieldTitles;
35
    /**
36
     * @var string[]
37
     */
38
    protected $fieldDescriptions;
39
    /**
40
     * @var string[]
41
     */
42
    protected $requiredFields = array();
43
    /**
44
     * @var DocumentRepository
45
     */
46
    private $repository;
47
    /**
48
     * @var Visitor
49
     */
50
    private $visitor;
51
    /**
52
     * @var array
53
     */
54
    protected $notModifiableOriginRecords;
55
    /**
56
     * @var  integer
57
     */
58
    private $paginationDefaultLimit;
59
60
    /**
61
     * @var boolean
62
     */
63
    protected $filterByAuthUser;
64
65
    /**
66
     * @var string
67
     */
68
    protected $filterByAuthField;
69
70
    /**
71
     * @param Visitor $visitor                    rql query visitor
72
     * @param array   $notModifiableOriginRecords strings with not modifiable recordOrigin values
73
     * @param integer $paginationDefaultLimit     amount of data records to be returned when in pagination context.
74
     */
75 2
    public function __construct(Visitor $visitor, $notModifiableOriginRecords, $paginationDefaultLimit)
76
    {
77 2
        parent::__construct();
78 2
        $this->visitor = $visitor;
79 2
        $this->notModifiableOriginRecords = $notModifiableOriginRecords;
80 2
        $this->paginationDefaultLimit = (int) $paginationDefaultLimit;
81 2
    }
82
83
    /**
84
     * get repository instance
85
     *
86
     * @return DocumentRepository
87
     */
88
    public function getRepository()
89
    {
90
        return $this->repository;
91
    }
92
93
    /**
94
     * create new app model
95
     *
96
     * @param DocumentRepository $repository Repository of countries
97
     *
98
     * @return \Graviton\RestBundle\Model\DocumentModel
99
     */
100 2
    public function setRepository(DocumentRepository $repository)
101
    {
102 2
        $this->repository = $repository;
103
104 2
        return $this;
105
    }
106
107
    /**
108
     * {@inheritDoc}
109
     *
110
     * @param Request      $request The request object
111
     * @param SecurityUser $user    SecurityUser Object
112
     *
113
     * @return array
114
     */
115 1
    public function findAll(Request $request, SecurityUser $user = null)
116
    {
117 1
        $pageNumber = $request->query->get('page', 1);
118 1
        $numberPerPage = (int) $request->query->get('perPage', $this->getDefaultLimit());
119 1
        $startAt = ($pageNumber - 1) * $numberPerPage;
120
121
        /** @var \Doctrine\ODM\MongoDB\Query\Builder $queryBuilder */
122 1
        $queryBuilder = $this->repository
123 1
            ->createQueryBuilder();
124
125 1
        if ($this->filterByAuthUser && $user && $user->hasRole(SecurityUser::ROLE_USER)) {
126
            $queryBuilder->field($this->filterByAuthField)->equals($user->getUser()->getId());
127
        }
128
129
        // *** do we have an RQL expression, do we need to filter data?
130 1
        if ($request->attributes->get('hasRql', false)) {
131
            $queryBuilder = $this->doRqlQuery(
132
                $queryBuilder,
133
                $request->attributes->get('rqlQuery')
134
            );
135
        } else {
136
            // @todo [lapistano]: seems the offset is missing for this query.
137
            /** @var \Doctrine\ODM\MongoDB\Query\Builder $qb */
138 1
            $queryBuilder->find($this->repository->getDocumentName());
139
        }
140
141
        // define offset and limit
142 1
        if (!array_key_exists('skip', $queryBuilder->getQuery()->getQuery())) {
143 1
            $queryBuilder->skip($startAt);
0 ignored issues
show
Bug introduced by
The method skip does only exist in Doctrine\ODM\MongoDB\Query\Builder, but not in Doctrine\ODM\MongoDB\Query\Expr.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
144 1
        } else {
145
            $startAt = (int) $queryBuilder->getQuery()->getQuery()['skip'];
146
        }
147
148 1
        if (!array_key_exists('limit', $queryBuilder->getQuery()->getQuery())) {
149 1
            $queryBuilder->limit($numberPerPage);
0 ignored issues
show
Bug introduced by
The method limit does only exist in Doctrine\ODM\MongoDB\Query\Builder, but not in Doctrine\ODM\MongoDB\Query\Expr.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
150 1
        } else {
151
            $numberPerPage = (int) $queryBuilder->getQuery()->getQuery()['limit'];
152
        }
153
154
        // Limit can not be negative nor null.
155 1
        if ($numberPerPage < 1) {
156
            throw new RqlSyntaxErrorException('negative or null limit in rql');
157
        }
158
159
        /**
160
         * add a default sort on id if none was specified earlier
161
         *
162
         * not specifying something to sort on leads to very weird cases when fetching references
163
         */
164 1
        if (!array_key_exists('sort', $queryBuilder->getQuery()->getQuery())) {
165 1
            $queryBuilder->sort('_id');
166 1
        }
167
168
        // run query
169 1
        $query = $queryBuilder->getQuery();
170 1
        $records = array_values($query->execute()->toArray());
171
172 1
        $totalCount = $query->count();
173 1
        $numPages = (int) ceil($totalCount / $numberPerPage);
174 1
        $page = (int) ceil($startAt / $numberPerPage) + 1;
175 1
        if ($numPages > 1) {
176
            $request->attributes->set('paging', true);
177
            $request->attributes->set('page', $page);
178
            $request->attributes->set('numPages', $numPages);
179
            $request->attributes->set('startAt', $startAt);
180
            $request->attributes->set('perPage', $numberPerPage);
181
            $request->attributes->set('totalCount', $totalCount);
182
        }
183
184 1
        return $records;
185
    }
186
187
    /**
188
     * @param \Graviton\I18nBundle\Document\Translatable $entity entity to insert
189
     *
190
     * @return Object
191
     */
192 View Code Duplication
    public function insertRecord($entity)
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...
193
    {
194
        $this->checkIfOriginRecord($entity);
195
        $manager = $this->repository->getDocumentManager();
196
        $manager->persist($entity);
197
        $manager->flush($entity);
198
199
        return $this->find($entity->getId());
200
    }
201
202
    /**
203
     * @param string $documentId id of entity to find
204
     *
205
     * @return Object
206
     */
207 1
    public function find($documentId)
208
    {
209 1
        return $this->repository->find($documentId);
210
    }
211
212
    /**
213
     * {@inheritDoc}
214
     *
215
     * @param string $documentId id of entity to update
216
     * @param Object $entity     new entity
217
     *
218
     * @return Object
219
     */
220 1 View Code Duplication
    public function updateRecord($documentId, $entity)
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...
221
    {
222 1
        $manager = $this->repository->getDocumentManager();
223
        // In both cases the document attribute named originRecord must not be 'core'
224 1
        $this->checkIfOriginRecord($entity);
225 1
        $this->checkIfOriginRecord($this->find($documentId));
226 1
        $entity = $manager->merge($entity);
227 1
        $manager->flush();
228
229 1
        return $entity;
230
    }
231
232
    /**
233
     * {@inheritDoc}
234
     *
235
     * @param string $documentId id of entity to delete
236
     *
237
     * @return null|Object
238
     */
239 1
    public function deleteRecord($documentId)
240
    {
241 1
        $manager = $this->repository->getDocumentManager();
242 1
        $entity = $this->find($documentId);
243
244 1
        $return = $entity;
245 1
        if ($entity) {
246 1
            $this->checkIfOriginRecord($entity);
247 1
            $manager->remove($entity);
248 1
            $manager->flush();
249 1
            $return = null;
250 1
        }
251
252 1
        return $return;
253
    }
254
255
    /**
256
     * get classname of entity
257
     *
258
     * @return string
259
     */
260 1
    public function getEntityClass()
261
    {
262 1
        return $this->repository->getDocumentName();
263
    }
264
265
    /**
266
     * {@inheritDoc}
267
     *
268
     * Currently this is being used to build the route id used for redirecting
269
     * to newly made documents. It might benefit from having a different name
270
     * for those purposes.
271
     *
272
     * We might use a convention based mapping here:
273
     * Graviton\CoreBundle\Document\App -> mongodb://graviton_core
274
     * Graviton\CoreBundle\Entity\Table -> mysql://graviton_core
275
     *
276
     * @todo implement this in a more convention based manner
277
     *
278
     * @return string
279
     */
280
    public function getConnectionName()
281
    {
282
        $bundle = strtolower(substr(explode('\\', get_class($this))[1], 0, -6));
283
284
        return 'graviton.' . $bundle;
285
    }
286
287
    /**
288
     * Does the actual query using the RQL Bundle.
289
     *
290
     * @param Builder $queryBuilder Doctrine ODM QueryBuilder
291
     * @param Query   $query        query from parser
292
     *
293
     * @return array
294
     */
295
    protected function doRqlQuery($queryBuilder, Query $query)
296
    {
297
        $this->visitor->setBuilder($queryBuilder);
298
299
        return $this->visitor->visit($query);
300
    }
301
302
    /**
303
     * Checks the recordOrigin attribute of a record and will throw an exception if value is not allowed
304
     *
305
     * @param Object $record record
306
     *
307
     * @return void
308
     */
309 8
    protected function checkIfOriginRecord($record)
310
    {
311
        if ($record instanceof RecordOriginInterface
312 8
            && !$record->isRecordOriginModifiable()
313 7
        ) {
314 3
            $values = $this->notModifiableOriginRecords;
315 3
            $originValue = strtolower(trim($record->getRecordOrigin()));
316
317 3
            if (in_array($originValue, $values)) {
318 1
                $msg = sprintf("Must not be one of the following keywords: %s", implode(', ', $values));
319
320 1
                throw new RecordOriginModifiedException($msg);
321
            }
322 2
        }
323 6
    }
324
325
    /**
326
     * Determines the configured amount fo data records to be returned in pagination context.
327
     *
328
     * @return int
329
     */
330 1
    private function getDefaultLimit()
331
    {
332 1
        if (0 < $this->paginationDefaultLimit) {
333 1
            return $this->paginationDefaultLimit;
334
        }
335
336
        return 10;
337
    }
338
339
    /**
340
     * @param Boolean $active active
341
     * @param String  $field  field
342
     * @return void
343
     */
344 2
    public function setFilterByAuthUser($active, $field)
345
    {
346 2
        $this->filterByAuthUser = is_bool($active) ? $active : false;
347 2
        $this->filterByAuthField = $field;
348 2
    }
349
}
350