This repository has been archived by the owner on Nov 17, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AbstractFixture.php
288 lines (244 loc) · 9.04 KB
/
AbstractFixture.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
<?php
/**
* This file is part of the Orbitale DoctrineTools package.
*
* (c) Alexandre Rock Ancelet <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Orbitale\Component\DoctrineTools;
use Closure;
use Doctrine\Common\DataFixtures\AbstractFixture as BaseAbstractFixture;
use Doctrine\ORM\Id\AssignedGenerator;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use Doctrine\Instantiator\Instantiator;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use ReflectionClass;
use function count;
use function method_exists;
use RuntimeException;
use function sprintf;
/**
* This class is used mostly for inserting "fixed" data, especially with their primary keys forced on insert.
* Two methods are mandatory to insert new data, and you can create them both as indexed array or as objects.
* Objects are directly persisted to the database, and once they're all, the EntityManager is flushed with all objects.
* You can override the `getOrder` and `getReferencePrefix` for more flexibility on how to link fixtures together.
* Other methods can be overriden, notably `flushEveryXIterations` and `searchForMatchingIds`. See their docs to know more.
*
* @package Orbitale\Component\DoctrineTools
*/
abstract class AbstractFixture extends BaseAbstractFixture implements OrderedFixtureInterface
{
/** @var EntityManagerInterface */
protected $manager;
/** @var EntityRepository */
protected $repo;
/** @var int */
private $totalNumberOfObjects = 0;
/** @var int */
private $numberOfIteratedObjects = 0;
/** @var bool */
private $clearEMOnFlush;
/** @var ReflectionClass|null */
private $reflection;
/** @var Instantiator|null */
private static $instantiator;
public function __construct()
{
$this->clearEMOnFlush = $this->clearEntityManagerOnFlush();
}
/**
* Returns the class of the entity you're managing.
*
* @return string
*/
abstract protected function getEntityClass(): string;
/**
* Returns a nested array containing the list of objects that should be persisted.
*
* @return array[]
*/
abstract protected function getObjects(): array;
/**
* {@inheritdoc}
*/
public function load(ObjectManager $manager)
{
$this->manager = $manager;
if ($this->manager instanceof EntityManagerInterface && $this->disableLogger()) {
$this->manager->getConnection()->getConfiguration()->setSQLLogger(null);
}
$this->repo = $this->manager->getRepository($this->getEntityClass());
$objects = $this->getObjects();
$this->totalNumberOfObjects = count($objects);
$this->numberOfIteratedObjects = 0;
foreach ($objects as $data) {
$this->fixtureObject($data);
$this->numberOfIteratedObjects++;
}
// Flush if we performed a "whole" fixture load,
// or if we flushed with batches but have not flushed all items.
if (
!$this->flushEveryXIterations()
|| ($this->flushEveryXIterations() && $this->numberOfIteratedObjects !== $this->totalNumberOfObjects)
) {
$this->manager->flush();
if ($this->clearEMOnFlush) {
$this->manager->clear();
}
}
}
/**
* Creates the object and persist it in database.
*
* @param array $data
*/
private function fixtureObject(array $data): void
{
$obj = $this->createNewInstance($data);
// Sometimes keys are specified in fixtures.
// We must make sure Doctrine will force them to be saved.
// Support for non-composite primary keys only.
// /!\ Be careful, this will override the generator type for ALL objects of the same entity class!
// This means that it _may_ break objects for which ids are not provided in the fixtures.
$metadata = $this->manager->getClassMetadata($this->getEntityClass());
$primaryKey = $metadata->getIdentifierFieldNames();
if (1 === count($primaryKey) && isset($data[$primaryKey[0]])) {
$metadata->setIdGeneratorType($metadata::GENERATOR_TYPE_NONE);
$metadata->setIdGenerator(new AssignedGenerator());
}
// And finally we persist the item
$this->manager->persist($obj);
// If we need to flush it, then we do it too.
if (
$this->flushEveryXIterations() > 0
&& $this->numberOfIteratedObjects % $this->flushEveryXIterations() === 0
) {
$this->manager->flush();
if ($this->clearEMOnFlush) {
$this->manager->clear();
}
}
// If we have to add a reference, we do it
if ($prefix = $this->getReferencePrefix()) {
$methodName = $this->getMethodNameForReference();
$reference = null;
if (method_exists($obj, $methodName)) {
$reference = $obj->{$methodName}();
} elseif (method_exists($obj, '__toString')) {
$reference = (string) $obj;
}
if (!$reference) {
throw new RuntimeException(sprintf(
'If you want to specify a reference with prefix "%s", method "%s" or "%s" must exist in the class, or you can override the "%s" method and add your own.',
$prefix, $methodName, '__toString()', 'getMethodNameForReference'
));
}
$this->addReference($prefix.$reference, $obj);
}
}
/**
* Get the order of this fixture.
* Default null means 0, so the fixture will be run at the beginning in order of appearance.
* Is to be overriden if used.
*
* @return int
*/
public function getOrder(): int
{
return 0;
}
/**
* If true, the SQL logger will be disabled, and therefore will avoid memory leaks and save memory during execution.
* Very useful for big batches of entities.
*
* @return bool
*/
protected function disableLogger(): bool
{
return true;
}
/**
* Returns the prefix used to create fixtures reference.
* If returns `null`, no reference will be created for the object.
* NOTE: To create references of an object, it must have an ID, and if not, implement __toString(), because
* each object is referenced BEFORE flushing the database.
* NOTE2: If you specified a "flushEveryXIterations" value, then the object will be provided with an ID every time.
*/
protected function getReferencePrefix(): ?string
{
return null;
}
/**
* When set, you can customize the method that will be used
* to determine the second part of the reference prefix.
* For example, if reference prefix is "my-entity-" and the
* method is "getIdentifier()", the reference will be:
* "$reference = 'my-entity-'.$obj->getIdentifier()".
*
* Only used when getReferencePrefix() returns non-empty value.
*
* Always tries to fall back to "__toString()".
*/
protected function getMethodNameForReference(): string
{
return 'getId';
}
/**
* If specified, the entity manager will be flushed every X times, depending on your specified values.
* Default is null, so the database is flushed only at the end of all persists.
*/
protected function flushEveryXIterations(): int
{
return 0;
}
/**
* If true, will run $em->clear() after having run $em->flush().
* This allows saving some memory when using huge sets of non-referenced fixtures.
*
* @return bool
*/
protected function clearEntityManagerOnFlush(): bool
{
return true;
}
/**
* Creates a new instance of the class associated with the fixture.
* Override this method if you have constructor arguments to manage yourself depending on input data.
*
* @param array $data
*
* @return object
*/
protected function createNewInstance(array $data): object
{
$instance = self::getInstantiator()->instantiate($this->getEntityClass());
$refl = $this->getReflection();
foreach ($data as $key => $value) {
$prop = $refl->getProperty($key);
$prop->setAccessible(true);
if ($value instanceof Closure) {
$value = $value($instance, $this, $this->manager);
}
$prop->setValue($instance, $value);
}
return $instance;
}
private function getReflection(): ReflectionClass
{
if (!$this->reflection) {
return $this->reflection = new ReflectionClass($this->getEntityClass());
}
return $this->reflection;
}
private static function getInstantiator(): Instantiator
{
if (!self::$instantiator) {
return self::$instantiator = new Instantiator();
}
return self::$instantiator;
}
}