1010use Exception ;
1111use SilverStripe \Core \Config \Config ;
1212use SilverStripe \Core \Injector \Injectable ;
13- use SilverStripe \Dev \Debug ;
1413use SilverStripe \ORM \DataList ;
1514use SilverStripe \ORM \DataObject ;
1615use SilverStripe \ORM \HasManyList ;
16+ use SilverStripe \ORM \RelationList ;
1717use Symfony \Component \Yaml \Yaml ;
1818use TractorCow \Fluent \Extension \FluentExtension ;
1919use 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