Skip to content
This repository was archived by the owner on Nov 17, 2020. It is now read-only.

Commit cf5bf89

Browse files
committed
Add new methods for flexibility, reorganised code, allow memory saving
1 parent dee84f7 commit cf5bf89

File tree

2 files changed

+151
-32
lines changed

2 files changed

+151
-32
lines changed

AbstractFixture.php

Lines changed: 130 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/*
34
* This file is part of the Orbitale DoctrineTools package.
45
*
@@ -18,18 +19,19 @@
1819
use Doctrine\ORM\EntityRepository;
1920
use Doctrine\ORM\Mapping\ClassMetadata;
2021
use Symfony\Component\PropertyAccess\PropertyAccess;
22+
use Symfony\Component\PropertyAccess\PropertyAccessor;
2123

2224
/**
2325
* This class is used mostly for inserting "fixed" datas, especially with their primary keys forced on insert.
2426
* Two methods are mandatory to insert new datas, and you can create them both as indexed array or as objects.
2527
* Objects are directly persisted to the database, and once they're all, the EntityManager is flushed with all objects.
2628
* You can override the `getOrder` and `getReferencePrefix` for more flexibility on how to link fixtures together.
29+
* Other methods can be overriden, notably `flushEveryXIterations` and `searchForMatchingIds`. See their docs to know more.
2730
*
2831
* @package Orbitale\Component\DoctrineTools
2932
*/
3033
abstract class AbstractFixture extends BaseAbstractFixture implements OrderedFixtureInterface
3134
{
32-
3335
/**
3436
* @var EntityManager
3537
*/
@@ -40,28 +42,83 @@ abstract class AbstractFixture extends BaseAbstractFixture implements OrderedFix
4042
*/
4143
private $repo;
4244

45+
/**
46+
* @var int
47+
*/
48+
private $order;
49+
50+
/**
51+
* @var string
52+
*/
53+
private $entityClass;
54+
55+
/**
56+
* @var PropertyAccessor
57+
*/
58+
private $propertyAccessor;
59+
60+
/**
61+
* @var null|string
62+
*/
63+
private $referencePrefix;
64+
65+
/**
66+
* @var bool
67+
*/
68+
private $searchForMatchingIds = false;
69+
70+
/**
71+
* @var int
72+
*/
73+
private $numberOfIteratedObjects = 0;
74+
75+
/**
76+
* @var int
77+
*/
78+
private $flushEveryXIterations = 0;
79+
80+
public function __construct()
81+
{
82+
$this->order = $this->getOrder();
83+
$this->flushEveryXIterations = $this->flushEveryXIterations();
84+
$this->searchForMatchingIds = $this->searchForMatchingIds();
85+
$this->entityClass = $this->getEntityClass();
86+
$this->referencePrefix = $this->getReferencePrefix();
87+
$this->propertyAccessor = class_exists('Symfony\Component\PropertyAccess\PropertyAccess') ? PropertyAccess::createPropertyAccessor() : null;
88+
}
89+
4390
/**
4491
* {@inheritdoc}
4592
*/
4693
public function load(ObjectManager $manager)
4794
{
4895
$this->manager = $manager;
4996

97+
if ($this->disableLogger()) {
98+
$this->manager->getConnection()->getConfiguration()->setSQLLogger(null);
99+
}
100+
50101
$this->repo = $this->manager->getRepository($this->getEntityClass());
51102

103+
$this->numberOfIteratedObjects = 0;
52104
foreach ($this->getObjects() as $data) {
53105
$this->fixtureObject($data);
106+
$this->numberOfIteratedObjects++;
54107
}
108+
$this->numberOfIteratedObjects = 0;
55109

56-
$this->manager->flush();
110+
if (!$this->flushEveryXIterations) {
111+
$this->manager->flush();
112+
$this->manager->clear();
113+
}
57114
}
58115

59116
/**
60117
* Creates the object and persist it in database.
61118
*
62119
* @param array|object $datas
63120
*/
64-
protected function fixtureObject($datas)
121+
private function fixtureObject($datas)
65122
{
66123
// The ID is taken in account to force its use in the database.
67124
$id = (is_object($datas) && method_exists($datas, 'getId') && $datas->getId())
@@ -72,15 +129,15 @@ protected function fixtureObject($datas)
72129
$newObject = false;
73130
$addRef = false;
74131

75-
// If the user specifies an ID
76-
if ($id) {
77-
// Checks that the object ID exists in database
132+
// If the user specifies an ID and the fixture class wants it to be merged, we search for an object.
133+
if ($id && $this->searchForMatchingIds) {
134+
// Checks that the object ID exists in database.
78135
$obj = $this->repo->find($id);
79136
if ($obj) {
80-
// If so, the object is not overwritten
137+
// If so, the object is not overwritten.
81138
$addRef = true;
82139
} else {
83-
// Else, we create a new object
140+
// Else, we create a new object.
84141
$newObject = true;
85142
}
86143
} else {
@@ -89,23 +146,21 @@ protected function fixtureObject($datas)
89146

90147
if ($newObject === true) {
91148

92-
$accessor = class_exists('Symfony\Component\PropertyAccess\PropertyAccess') ? PropertyAccess::createPropertyAccessor() : null;
93-
94-
// If the datas are in an array, we instanciate a new object
149+
// If the datas are in an array, we instanciate a new object.
95150
if (is_array($datas)) {
96-
$class = $this->getEntityClass();
151+
$class = $this->entityClass;
97152
$obj = new $class;
98153
foreach ($datas as $field => $value) {
99-
if ($accessor) {
100-
$accessor->setValue($obj, $field, $value);
154+
if ($this->propertyAccessor) {
155+
$this->propertyAccessor->setValue($obj, $field, $value);
101156
} else {
102-
// Force the use of a setter if accessor is not available
157+
// Force the use of a setter if accessor is not available.
103158
$obj->{'set'.ucfirst($field)}($value);
104159
}
105160
}
106161
}
107162

108-
// If the ID is set, we tell Doctrine to force the insertion of it
163+
// If the ID is set, we tell Doctrine to force the insertion of it.
109164
if ($id) {
110165
/** @var ClassMetadata $metadata */
111166
$metadata = $this->manager->getClassMetaData(get_class($obj));
@@ -114,47 +169,94 @@ protected function fixtureObject($datas)
114169

115170
// And finally we persist the item
116171
$this->manager->persist($obj);
172+
173+
// If we need to flush it, then we do it too.
174+
if (
175+
$this->flushEveryXIterations
176+
&& $this->numberOfIteratedObjects
177+
&& $this->numberOfIteratedObjects % $this->flushEveryXIterations === 0
178+
) {
179+
$this->manager->flush();
180+
$this->manager->clear();
181+
}
117182
$addRef = true;
118183
}
119184

120-
// If we have to add a reference, we set it
121-
if ($addRef === true && $obj && $this->getReferencePrefix()) {
122-
$this->addReference($this->getReferencePrefix().($id ?: (string) $obj), $obj);
185+
// If we have to add a reference, we do it
186+
if ($addRef === true && $obj && $this->referencePrefix) {
187+
$this->addReference($this->referencePrefix.($id ?: (string) $obj), $obj);
123188
}
189+
190+
$obj = null;
191+
}
192+
193+
/**
194+
* Get the order of this fixture.
195+
* Default null means 0, so the fixture will be run at the beginning in order of appearance.
196+
* Is to be overriden if used.
197+
*
198+
* @return int
199+
*/
200+
public function getOrder() {
201+
return $this->order;
202+
}
203+
204+
/**
205+
* If true, the SQL logger will be disabled, and therefore will avoid memory leaks and save memory during execution.
206+
* Very useful for big batches of entities.
207+
*
208+
* @return bool
209+
*/
210+
protected function disableLogger()
211+
{
212+
return false;
124213
}
125214

126215
/**
127216
* Returns the prefix used to create fixtures reference.
128217
* If returns `null`, no reference will be created for the object.
129218
* NOTE: To create references of an object, it must have an ID, and if not, implement __toString(), because
130219
* each object is referenced BEFORE flushing the database.
131-
* Is to be overriden if used.
220+
* NOTE2: If you specified a "flushEveryXIterations" value, then the object will be provided with an ID every time.
132221
*
133222
* @return string|null
134223
*/
135224
protected function getReferencePrefix() {
136-
return null;
225+
return $this->referencePrefix;
137226
}
138227

139228
/**
140-
* Get the order of this fixture.
141-
* Is to be overriden if used.
229+
* If specified, the entity manager will be flushed every X times, depending on your specified values.
230+
* Default is null, so the database is flushed only at the end of all persists.
142231
*
143-
* @return int
232+
* @return bool
144233
*/
145-
public function getOrder() {
146-
return 0;
234+
protected function flushEveryXIterations()
235+
{
236+
return $this->flushEveryXIterations;
237+
}
238+
239+
/**
240+
* If true and an ID is specified, will execute a $manager->find($id) in the database.
241+
* By default this var is true.
242+
* Be careful, if you set it to false you may have "duplicate entry" errors if your database is already populated.
243+
*
244+
* @return bool
245+
*/
246+
protected function searchForMatchingIds()
247+
{
248+
return $this->searchForMatchingIds;
147249
}
148250

149251
/**
150-
* Returns the class of the entity you're managing
252+
* Returns the class of the entity you're managing.
151253
*
152254
* @return string
153255
*/
154256
protected abstract function getEntityClass();
155257

156258
/**
157-
* Returns a list of objects to
259+
* Returns a list of objects to insert in the database.
158260
*
159261
* @return ArrayCollection|object[]
160262
*/

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ Orbitale Doctrine Tools
44
This library is composed of multiple tools to be used with the Doctrine ORM.
55

66

7+
### Documentation
8+
9+
* [Installation](#installation)
10+
* [Usage](#usage)
11+
* [Entity Repository](#entity-repository)
12+
* [Doctrine Fixtures](#doctrine-fixtures)
13+
14+
715
# Installation
816

917
Simply install the library with [Composer](https://getcomposer.org):
@@ -84,16 +92,25 @@ class PostFixtures extends AbstractFixture
8492
}
8593

8694
public function getObjects() {
87-
return array(
88-
array('id' => 1, 'title' => 'First post', 'description' => 'Lorem ipsum'),
89-
array('id' => 2, 'title' => 'Second post', 'description' => 'muspi meroL'),
90-
);
95+
return [
96+
['id' => 1, 'title' => 'First post', 'description' => 'Lorem ipsum'],
97+
['id' => 2, 'title' => 'Second post', 'description' => 'muspi meroL'],
98+
];
9199
}
92100

93101
}
94102

95103
```
96104

105+
### Methods of the `AbstractFixture` class that can be overriden:
106+
107+
* `getOrder()` to change the order in which the fixtures will be loaded.
108+
* `getReferencePrefix()` to add a reference in the Fixtures' batch so you can use them later.
109+
References are stored as `{referencePrefix}-{id|__toString()}`.
110+
* `flushEveryXIterations()` to flush in batches instead of flushing only once at the end of all fixtures persist.
111+
* `searchForMatchingIds()` to check that an ID exists in database and therefore not insert it back if it does.
112+
* `disableLogger()` to disable SQL queries logging, useful to save memory at runtime.
113+
97114
This way, 2 objects are automatically persisted in the database, and they're all identified with their ID.
98115
Also, if you run the symfony `app/console doctrine:fixtures:load` using the `--append` option, the IDs will be detected
99116
in the database and will not be inserted twice, with no error so you can really use fixtures as reference datas!

0 commit comments

Comments
 (0)