Skip to content

Commit ce339a4

Browse files
lekoalachrispenny
andauthored
add allowedDepth param (#6)
* add allowedDepth param * Update Fluent support with allowed deptch * Add depth option to task. Remove empty groups before outputting fixture Co-authored-by: Chris Penny <chris.penny@gmail.com>
1 parent 2b5ab9d commit ce339a4

File tree

4 files changed

+134
-37
lines changed

4 files changed

+134
-37
lines changed

_config/model.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ SilverStripe\Security\MemberPassword:
1212

1313
SilverStripe\Security\RememberLoginHash:
1414
exclude_from_fixture_relationships: 1
15+
16+
Symbiote\QueuedJobs\Controllers\QueuedTaskRunner:
17+
task_blacklist:
18+
- ChrisPenny\DataObjectToFixture\Task\GenerateFixtureFromDataObject

src/ORM/Group.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ public function toArray(): array
106106
$records = [];
107107

108108
foreach ($this->records as $record) {
109+
$fields = $record->getFields();
110+
111+
if (count($fields) === 0) {
112+
continue;
113+
}
114+
109115
$records[$record->getId()] = $record->getFields();
110116
}
111117

src/Service/FixtureService.php

Lines changed: 101 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
use Exception;
1111
use SilverStripe\Core\Config\Config;
1212
use SilverStripe\Core\Injector\Injectable;
13-
use SilverStripe\Dev\Debug;
1413
use SilverStripe\ORM\DataList;
1514
use SilverStripe\ORM\DataObject;
1615
use SilverStripe\ORM\HasManyList;
16+
use SilverStripe\ORM\RelationList;
1717
use Symfony\Component\Yaml\Yaml;
1818
use TractorCow\Fluent\Extension\FluentExtension;
1919
use TractorCow\Fluent\Model\Locale;
@@ -48,6 +48,11 @@ class FixtureService
4848
*/
4949
private $warnings = [];
5050

51+
/**
52+
* @var int
53+
*/
54+
private $allowedDepth = null;
55+
5156
public function __construct()
5257
{
5358
$this->fixtureManifest = new FixtureManifest();
@@ -56,15 +61,19 @@ public function __construct()
5661

5762
/**
5863
* @param DataObject $dataObject
64+
* @param int $currentDepth (for internal use)
5965
* @return FixtureService
6066
* @throws Exception
6167
*/
62-
public function addDataObject(DataObject $dataObject): FixtureService
68+
public function addDataObject(DataObject $dataObject, int $currentDepth = 0): FixtureService
6369
{
64-
if (!$dataObject->exists()) {
70+
// Check isInDB() rather than exists(), as exists() has additional checks for (eg) Files
71+
if (!$dataObject->isInDB()) {
6572
throw new Exception('Your DataObject must be in the DB');
6673
}
6774

75+
$currentDepth += 1;
76+
6877
// Any time we add a new DataObject, we need to set validated back to false.
6978
$this->validated = false;
7079
$this->organised = false;
@@ -89,20 +98,25 @@ public function addDataObject(DataObject $dataObject): FixtureService
8998
$this->relationshipManifest->addGroup($group);
9099
// Add the standard DB fields for this record
91100
$this->addDataObjectDBFields($dataObject);
101+
102+
// If the DataObject has Fluent applied, then we also need to add Localised fields.
103+
if ($dataObject->hasExtension(FluentExtension::class)) {
104+
$this->addDataObjectLocalisedFields($dataObject, $currentDepth);
105+
}
106+
107+
if ($this->getAllowedDepth() !== null && $currentDepth > $this->getAllowedDepth()) {
108+
return $this;
109+
}
110+
92111
// Add direct relationships.
93-
$this->addDataObjectHasOneFields($dataObject);
112+
$this->addDataObjectHasOneFields($dataObject, $currentDepth);
94113
// Add belongs to relationships.
95-
$this->addDataObjectBelongsToFields($dataObject);
114+
$this->addDataObjectBelongsToFields($dataObject, $currentDepth);
96115
// has_many fields will include any relationships that you're created using many_many "through".
97-
$this->addDataObjectHasManyFields($dataObject);
116+
$this->addDataObjectHasManyFields($dataObject, $currentDepth);
98117
// many_many relationships without a "through" object are not supported. Add warning for any relationships
99118
// we find like that.
100-
$this->addDataObjectManyManyFieldWarnings($dataObject);
101-
102-
// If the DataObject has Fluent applied, then we also need to add Localised fields.
103-
if ($dataObject->hasExtension(FluentExtension::class)) {
104-
$this->addDataObjectLocalisedFields($dataObject);
105-
}
119+
$this->addDataObjectManyManyFieldWarnings($dataObject, $currentDepth);
106120

107121
return $this;
108122
}
@@ -169,7 +183,19 @@ protected function toArray(): array
169183
$toArrayGroups = [];
170184

171185
foreach ($this->fixtureManifest->getGroupsPrioritised() as $group) {
172-
$toArrayGroups[$group->getClassName()] = $group->toArray();
186+
$records = $group->toArray();
187+
188+
if (count($records) === 0) {
189+
$this->addWarning(sprintf(
190+
'Fixture output: No records were found for Group/ClassName "%s". You might need to check that you'
191+
. ' do not have any relationships pointing to this Group/ClassName.',
192+
$group->getClassName(),
193+
));
194+
195+
continue;
196+
}
197+
198+
$toArrayGroups[$group->getClassName()] = $records;
173199
}
174200

175201
return $toArrayGroups;
@@ -204,9 +230,10 @@ protected function addDataObjectDBFields(DataObject $dataObject): void
204230

205231
/**
206232
* @param DataObject $dataObject
233+
* @param int $currentDepth (for internal use)
207234
* @throws Exception
208235
*/
209-
protected function addDataObjectHasOneFields(DataObject $dataObject): void
236+
protected function addDataObjectHasOneFields(DataObject $dataObject, int $currentDepth = 0): void
210237
{
211238
$group = $this->fixtureManifest->getGroupByClassName($dataObject->ClassName);
212239

@@ -257,7 +284,7 @@ protected function addDataObjectHasOneFields(DataObject $dataObject): void
257284
$relatedObjectID = (int) $dataObject->{$relationFieldName};
258285

259286
// We cannot query a DataObject
260-
if($relationClassName == DataObject::class) {
287+
if ($relationClassName == DataObject::class) {
261288
continue;
262289
}
263290
$relatedObject = DataObject::get($relationClassName)->byID($relatedObjectID);
@@ -285,7 +312,7 @@ protected function addDataObjectHasOneFields(DataObject $dataObject): void
285312
$record->addFieldValue($relationFieldName, $relationshipValue);
286313

287314
// Add the related DataObject.
288-
$this->addDataObject($relatedObject);
315+
$this->addDataObject($relatedObject, $currentDepth);
289316

290317
// Find the Group for the DataObject that we should have just added.
291318
$relatedGroup = $this->fixtureManifest->getGroupByClassName($relatedObject->ClassName);
@@ -301,9 +328,10 @@ protected function addDataObjectHasOneFields(DataObject $dataObject): void
301328

302329
/**
303330
* @param DataObject $dataObject
331+
* @param int $currentDepth (for internal use)
304332
* @throws Exception
305333
*/
306-
protected function addDataObjectBelongsToFields(DataObject $dataObject): void
334+
protected function addDataObjectBelongsToFields(DataObject $dataObject, int $currentDepth = 0): void
307335
{
308336
// belongs_to fixture definitions don't appear to be support currently. This is how we can eventually solve
309337
// looping relationships though...
@@ -328,9 +356,10 @@ protected function hasBelongsToRelationship(
328356

329357
/**
330358
* @param DataObject $dataObject
359+
* @param int $currentDepth (for internal use)
331360
* @throws Exception
332361
*/
333-
protected function addDataObjectHasManyFields(DataObject $dataObject): void
362+
protected function addDataObjectHasManyFields(DataObject $dataObject, int $currentDepth = 0): void
334363
{
335364
/** @var array $hasManyRelationships */
336365
$hasManyRelationships = $dataObject->config()->get('has_many');
@@ -344,26 +373,27 @@ protected function addDataObjectHasManyFields(DataObject $dataObject): void
344373
// Use Schema to make sure that this relationship has a reverse has_one created. This will throw an
345374
// Exception if there isn't (SilverStripe always expects you to have it).
346375
$schema->getRemoteJoinField($dataObject->ClassName, $relationFieldName, 'has_many');
347-
376+
348377
// This class has requested that it not be included in relationship maps.
349378
$exclude = Config::inst()->get($relationClassName, 'exclude_from_fixture_relationships');
350379
if ($exclude) {
351380
continue;
352381
}
353-
382+
354383
// If we have the correct relationship mapping (a "has_one" relationship on the object in the "has_many"),
355384
// then we can simply add each of these records and let the "has_one" be added by addRecordHasOneFields().
356385
foreach ($dataObject->relField($relationFieldName) as $relatedObject) {
357386
// Add the related DataObject. Recursion starts.
358-
$this->addDataObject($relatedObject);
387+
$this->addDataObject($relatedObject, $currentDepth);
359388
}
360389
}
361390
}
362391

363392
/**
364393
* @param DataObject $dataObject
394+
* @param int $currentDepth (for internal use)
365395
*/
366-
protected function addDataObjectManyManyFieldWarnings(DataObject $dataObject): void
396+
protected function addDataObjectManyManyFieldWarnings(DataObject $dataObject, int $currentDepth = 0): void
367397
{
368398
/** @var array $manyManyRelationships */
369399
$manyManyRelationships = $dataObject->config()->get('many_many');
@@ -392,7 +422,7 @@ protected function addDataObjectManyManyFieldWarnings(DataObject $dataObject): v
392422
// warning.
393423
$this->addWarning(sprintf(
394424
'many_many relationships without a "through" are not supported. No yml generated for '
395-
. 'relationship: %s::%s()',
425+
. 'relationship: %s::%s()',
396426
$dataObject->ClassName,
397427
$relationshipName
398428
));
@@ -401,9 +431,10 @@ protected function addDataObjectManyManyFieldWarnings(DataObject $dataObject): v
401431

402432
/**
403433
* @param DataObject|FluentExtension $dataObject
434+
* @param int $currentDepth (for internal use)
404435
* @throws Exception
405436
*/
406-
protected function addDataObjectLocalisedFields(DataObject $dataObject): void
437+
protected function addDataObjectLocalisedFields(DataObject $dataObject, int $currentDepth = 0): void
407438
{
408439
$localeCodes = FluentHelper::getLocaleCodesByObjectInstance($dataObject);
409440
$localisedTables = $dataObject->getLocalisedTables();
@@ -428,12 +459,13 @@ protected function addDataObjectLocalisedFields(DataObject $dataObject): void
428459

429460
foreach ($localeCodes as $locale) {
430461
FluentState::singleton()->withState(
431-
function (FluentState $state) use(
462+
function (FluentState $state) use (
432463
$localisedTables,
433464
$relatedDataObjects,
434465
$locale,
435466
$className,
436-
$id
467+
$id,
468+
$currentDepth
437469
): void {
438470
$state->setLocale($locale);
439471

@@ -463,7 +495,7 @@ function (FluentState $state) use(
463495
$record->addFieldValue('Locale', $locale);
464496

465497
foreach ($localisedFields as $localisedField) {
466-
$isIDField = (substr($localisedField, -2) === 'ID') ;
498+
$isIDField = (substr($localisedField, -2) === 'ID');
467499

468500
if ($isIDField) {
469501
$relationshipName = substr($localisedField, 0, -2);
@@ -473,6 +505,19 @@ function (FluentState $state) use(
473505
$fieldValue = $localisedDataObject->relField($localisedField);
474506
}
475507

508+
// Check if this is a "regular" field value, if it is then add it and continue
509+
if (!$fieldValue instanceof DataObject && !$fieldValue instanceof RelationList) {
510+
$record->addFieldValue($localisedField, $fieldValue);
511+
512+
continue;
513+
}
514+
515+
// Remaining field values are going to be relational values, so we need to check whether or
516+
// not we're already at our max allowed depth before adding those relationships
517+
if ($this->getAllowedDepth() !== null && $currentDepth > $this->getAllowedDepth()) {
518+
continue;
519+
}
520+
476521
if ($fieldValue instanceof DataObject) {
477522
$relatedDataObjects[] = $fieldValue;
478523

@@ -488,19 +533,17 @@ function (FluentState $state) use(
488533
foreach ($fieldValue as $relatedDataObject) {
489534
$relatedDataObjects[] = $relatedDataObject;
490535
}
491-
492-
continue;
493536
}
494537

495-
$record->addFieldValue($localisedField, $fieldValue);
538+
// No other field types are supported (EG: ManyManyList)
496539
}
497540
}
498541
}
499542
);
500543
}
501544

502545
foreach ($relatedDataObjects as $relatedDataObject) {
503-
$this->addDataObject($relatedDataObject);
546+
$this->addDataObject($relatedDataObject, $currentDepth);
504547
}
505548
}
506549

@@ -678,8 +721,8 @@ protected function removeLoopingRelationships(array $parentage, array $toClasses
678721

679722
$this->addWarning(sprintf(
680723
'A relationships was removed between "%s" and "%s". This occurs if we have detected a loop'
681-
. '. Until belongs_to relationships are supported in fixtures, you might not be able to rely on'
682-
. ' fixtures generated to have the appropriate priority order',
724+
. '. Until belongs_to relationships are supported in fixtures, you might not be able to rely on'
725+
. ' fixtures generated to have the appropriate priority order',
683726
$loopingRelationship,
684727
$toClass
685728
));
@@ -725,4 +768,29 @@ protected function addWarning(string $message): void
725768

726769
$this->warnings[] = $message;
727770
}
771+
772+
/**
773+
* @return int
774+
*/
775+
public function getAllowedDepth(): ?int
776+
{
777+
return $this->allowedDepth;
778+
}
779+
780+
/**
781+
* @param int $allowedDepth
782+
* @return FixtureService
783+
*/
784+
public function setAllowedDepth(int $allowedDepth = null): FixtureService
785+
{
786+
if ($allowedDepth === 0) {
787+
$this->addWarning('You set an allowed depth of 0. We have assumed you meant 1.');
788+
789+
$allowedDepth = 1;
790+
}
791+
792+
$this->allowedDepth = $allowedDepth;
793+
794+
return $this;
795+
}
728796
}

0 commit comments

Comments
 (0)