Completed
Push — master ( e56eb0...e881a3 )
by Julián
01:57
created

RepositoryTrait::remove()   C

Complexity

Conditions 7
Paths 14

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 12
nc 14
nop 2
1
<?php
2
3
/*
4
 * doctrine-base-repositories (https://github.com/juliangut/doctrine-base-repositories).
5
 * Doctrine2 utility repositories.
6
 *
7
 * @license MIT
8
 * @link https://github.com/juliangut/doctrine-base-repositories
9
 * @author Julián Gutiérrez <[email protected]>
10
 */
11
12
declare(strict_types=1);
13
14
namespace Jgut\Doctrine\Repository;
15
16
use Doctrine\Common\Util\Inflector;
17
18
/**
19
 * Repository trait.
20
 */
21
trait RepositoryTrait
22
{
23
    /**
24
     * Auto flush changes.
25
     *
26
     * @var bool
27
     */
28
    protected $autoFlush = false;
29
30
    /**
31
     * New object factory.
32
     *
33
     * @var callable
34
     */
35
    protected $objectFactory;
36
37
    /**
38
     * Get automatic manager flushing.
39
     *
40
     * @return bool
41
     */
42
    public function isAutoFlush(): bool
43
    {
44
        return $this->autoFlush;
45
    }
46
47
    /**
48
     * Set automatic manager flushing.
49
     *
50
     * @param bool $autoFlush
51
     */
52
    public function setAutoFlush(bool $autoFlush = true)
53
    {
54
        $this->autoFlush = $autoFlush;
55
    }
56
57
    /**
58
     * Manager flush.
59
     */
60
    public function flush()
61
    {
62
        $this->getManager()->flush();
63
    }
64
65
    /**
66
     * Set object factory.
67
     *
68
     * @param callable $objectFactory
69
     */
70
    public function setObjectFactory(callable $objectFactory)
71
    {
72
        $this->objectFactory = $objectFactory;
73
    }
74
75
    /**
76
     * Find one object by a set of criteria or create a new one.
77
     *
78
     * @param array $criteria
79
     *
80
     * @throws \RuntimeException
81
     *
82
     * @return object
83
     */
84
    public function findOneByOrGetNew(array $criteria)
85
    {
86
        $object = $this->findOneBy($criteria);
0 ignored issues
show
Bug introduced by
The method findOneBy() does not exist on Jgut\Doctrine\Repository\RepositoryTrait. Did you maybe mean findOneByOrGetNew()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
87
88
        if ($object === null) {
89
            $object = $this->getNew();
90
        }
91
92
        return $object;
93
    }
94
95
    /**
96
     * Get a new managed object instance.
97
     *
98
     * @throws \RuntimeException
99
     *
100
     * @return object
101
     */
102
    public function getNew()
103
    {
104
        $className = $this->getClassName();
0 ignored issues
show
Documentation Bug introduced by
The method getClassName does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
105
106
        if ($this->objectFactory === null) {
107
            return new $className();
108
        }
109
110
        $object = call_user_func($this->objectFactory);
111
112
        if (!$this->canBeManaged($object)) {
113
            throw new \RuntimeException(
114
                sprintf(
115
                    'Object factory must return an instance of %s. "%s" returned',
116
                    $className,
117
                    is_object($object) ? get_class($object) : gettype($object)
118
                )
119
            );
120
        }
121
122
        return $object;
123
    }
124
125
    /**
126
     * Add objects.
127
     *
128
     * @param object|object[] $objects
129
     * @param bool            $flush
130
     *
131
     * @throws \InvalidArgumentException
132
     */
133
    public function add($objects, bool $flush = false)
134
    {
135
        if (!is_array($objects)) {
136
            $objects = [$objects];
137
        }
138
139
        $manager = $this->getManager();
140
141
        foreach ($objects as $object) {
142
            if (!$this->canBeManaged($object)) {
143
                throw new \InvalidArgumentException(sprintf('Managed object must be a %s', $this->getClassName()));
0 ignored issues
show
Documentation Bug introduced by
The method getClassName does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
144
            }
145
146
            $manager->persist($object);
147
        }
148
149
        $this->flushObject($objects, $flush);
150
    }
151
152
    /**
153
     * Remove all objects.
154
     *
155
     * @param bool $flush
156
     */
157
    public function removeAll(bool $flush = false)
158
    {
159
        $manager = $this->getManager();
160
161
        $objects = $this->findAll();
0 ignored issues
show
Documentation Bug introduced by
The method findAll does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
162
163
        foreach ($objects as $object) {
164
            $manager->remove($object);
165
        }
166
167
        $this->flushObject($objects, $flush);
168
    }
169
170
    /**
171
     * Remove object filtered by a set of criteria.
172
     *
173
     * @param array $criteria
174
     * @param bool  $flush
175
     */
176
    public function removeBy(array $criteria, bool $flush = false)
177
    {
178
        $manager = $this->getManager();
179
180
        $objects = $this->findBy($criteria);
0 ignored issues
show
Documentation Bug introduced by
The method findBy does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
181
182
        foreach ($objects as $object) {
183
            $manager->remove($object);
184
        }
185
186
        $this->flushObject($objects, $flush);
187
    }
188
189
    /**
190
     * Remove first object filtered by a set of criteria.
191
     *
192
     * @param array $criteria
193
     * @param bool  $flush
194
     */
195
    public function removeOneBy(array $criteria, bool $flush = false)
196
    {
197
        $object = $this->findOneBy($criteria);
0 ignored issues
show
Bug introduced by
The method findOneBy() does not exist on Jgut\Doctrine\Repository\RepositoryTrait. Did you maybe mean findOneByOrGetNew()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
198
199
        if ($object !== null) {
200
            $this->getManager()->remove($object);
201
202
            $this->flushObject($object, $flush);
203
        }
204
    }
205
206
    /**
207
     * Remove objects.
208
     *
209
     * @param object|object[]|string|int $objects
210
     * @param bool                       $flush
211
     *
212
     * @throws \InvalidArgumentException
213
     */
214
    public function remove($objects, bool $flush = false)
215
    {
216
        $manager = $this->getManager();
217
218
        if (!is_object($objects) && !is_array($objects)) {
219
            $objects = $this->find($objects);
0 ignored issues
show
Documentation Bug introduced by
The method find does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
220
        }
221
222
        if ($objects !== null) {
223
            if (!is_array($objects)) {
224
                $objects = [$objects];
225
            }
226
227
            foreach ($objects as $object) {
228
                if (!$this->canBeManaged($object)) {
229
                    throw new \InvalidArgumentException(sprintf('Managed object must be a %s', $this->getClassName()));
0 ignored issues
show
Documentation Bug introduced by
The method getClassName does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
230
                }
231
232
                $manager->remove($object);
233
            }
234
235
            $this->flushObject($objects, $flush);
236
        }
237
    }
238
239
    /**
240
     * Get all objects count.
241
     *
242
     * @return int
243
     */
244
    public function countAll(): int
245
    {
246
        return $this->countBy([]);
247
    }
248
249
    /**
250
     * Get object count filtered by a set of criteria.
251
     *
252
     * @param mixed $criteria
253
     *
254
     * @return int
255
     */
256
    abstract public function countBy($criteria): int;
257
258
    /**
259
     * Adds support for magic finders and removers.
260
     *
261
     * @param string $method
262
     * @param array  $arguments
263
     *
264
     * @throws \BadMethodCallException
265
     *
266
     * @return mixed
267
     */
268
    public function __call($method, $arguments)
269
    {
270
        static $supportedMethods = ['findBy', 'findOneBy', 'findPaginatedBy', 'removeBy', 'removeOneBy'];
271
272
        if (count($arguments) === 0) {
273
            throw new \BadMethodCallException(sprintf(
274
                'You need to pass a parameter to %s::%s',
275
                $this->getClassName(),
0 ignored issues
show
Documentation Bug introduced by
The method getClassName does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
276
                $method
277
            ));
278
        }
279
280
        foreach ($supportedMethods as $supportedMethod) {
281
            if (strpos($method, $supportedMethod) === 0) {
282
                $field = substr($method, strlen($supportedMethod));
283
                $method = substr($method, 0, strlen($supportedMethod));
284
285
                return $this->callSupportedMethod($method, Inflector::camelize($field), $arguments);
286
            }
287
        }
288
289
        throw new \BadMethodCallException(sprintf(
290
            'Undefined method "%s". Method name must start with one of "%s"!',
291
            $method,
292
            implode('", "', $supportedMethods)
293
        ));
294
    }
295
296
    /**
297
     * Internal remove magic finder.
298
     *
299
     * @param string $method
300
     * @param string $fieldName
301
     * @param array  $arguments
302
     *
303
     * @throws \BadMethodCallException
304
     *
305
     * @return mixed
306
     */
307
    protected function callSupportedMethod(string $method, string $fieldName, array $arguments)
308
    {
309
        $classMetadata = $this->getClassMetadata();
310
311
        if ($classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName)) {
312
            // @codeCoverageIgnoreStart
313
            $parameters = array_merge(
314
                [$fieldName => $arguments[0]],
315
                array_slice($arguments, 1)
316
            );
317
318
            return call_user_func_array([$this, $method], $parameters);
319
            // @codeCoverageIgnoreEnd
320
        }
321
322
        throw new \BadMethodCallException(sprintf(
323
            'Invalid call to %s::%s. Field "%s" does not exist',
324
            $this->getClassName(),
0 ignored issues
show
Documentation Bug introduced by
The method getClassName does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
325
            $method,
326
            $fieldName
327
        ));
328
    }
329
330
    /**
331
     * Check if the object is of the proper type.
332
     *
333
     * @param object $object
334
     *
335
     * @return bool
336
     */
337
    protected function canBeManaged($object): bool
338
    {
339
        $managedClass = $this->getClassName();
0 ignored issues
show
Documentation Bug introduced by
The method getClassName does not exist on object<Jgut\Doctrine\Repository\RepositoryTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
340
341
        return $object instanceof $managedClass;
342
    }
343
344
    /**
345
     * Flush managed object.
346
     *
347
     * @param object|array $object
0 ignored issues
show
Documentation introduced by
There is no parameter named $object. Did you maybe mean $objects?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
348
     * @param bool         $flush
349
     *
350
     * @throws \InvalidArgumentException
351
     */
352
    protected function flushObject($objects, bool $flush)
353
    {
354
        if ($flush || $this->autoFlush) {
355
            if (!is_object($objects) && !is_array($objects)) {
356
                throw new \InvalidArgumentException(
357
                    sprintf(
358
                        'Invalid flush objects provided. Must be an array or object, "%s" given',
359
                        gettype($objects)
360
                    )
361
                );
362
            }
363
364
            $this->getManager()->flush($objects);
365
        }
366
    }
367
368
    /**
369
     * Get object manager.
370
     *
371
     * @return \Doctrine\Common\Persistence\ObjectManager
372
     */
373
    abstract protected function getManager();
374
375
    /**
376
     * Get class metadata.
377
     *
378
     * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata
379
     */
380
    abstract protected function getClassMetadata();
381
}
382