Skip to content

Commit 2209f1e

Browse files
committed
Add support for fake assets
1 parent 254820f commit 2209f1e

File tree

6 files changed

+182
-11
lines changed

6 files changed

+182
-11
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"fakerphp/faker": "^1.23.0",
2121
"laravel/framework": "^11.21 || ^12.0",
2222
"laravel/prompts": "^0.1.24 || ^0.2.0 || ^0.3.0",
23+
"smknstd/fakerphp-picsum-images": "^1.0",
2324
"statamic/cms": "^5.0"
2425
},
2526
"require-dev": {

src/Factories/Concerns/CreatesEntry.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,26 @@
33
namespace Aerni\Factory\Factories\Concerns;
44

55
use Illuminate\Support\Arr;
6+
use Statamic\Fields\Blueprint;
67
use Statamic\Contracts\Entries\Entry;
78
use Statamic\Facades\Entry as EntryFacade;
9+
use Aerni\Factory\Factories\Concerns\WithAssets;
10+
use Statamic\Facades\Blueprint as BlueprintFacade;
811

912
trait CreatesEntry
1013
{
1114
use DefinitionHelpers;
1215
use Publishable;
1316
use WithSites;
17+
use WithAssets;
1418

1519
protected $model = Entry::class;
1620

1721
public function newModel(array $attributes = []): Entry
1822
{
1923
$entry = EntryFacade::make()
20-
->collection($this->collection())
21-
->blueprint($this->blueprint());
24+
->collection($this->collectionHandle())
25+
->blueprint($this->blueprintHandle());
2226

2327
if ($slug = Arr::pull($attributes, 'slug')) {
2428
$entry->slug($slug);
@@ -41,7 +45,7 @@ public function newModel(array $attributes = []): Entry
4145
return $entry->data($attributes);
4246
}
4347

44-
protected function collection(): string
48+
protected function collectionHandle(): string
4549
{
4650
return $this->collection
4751
?? str(get_class($this))
@@ -50,7 +54,7 @@ protected function collection(): string
5054
->lower();
5155
}
5256

53-
protected function blueprint(): string
57+
protected function blueprintHandle(): string
5458
{
5559
return $this->blueprint
5660
?? str(get_class($this))
@@ -59,8 +63,13 @@ protected function blueprint(): string
5963
->lower();
6064
}
6165

66+
protected function blueprint(): Blueprint
67+
{
68+
return BlueprintFacade::find("collections/{$this->collectionHandle()}/{$this->blueprintHandle()}");
69+
}
70+
6271
public function modelName(): string
6372
{
64-
return parent::modelName().'\\'.ucfirst($this->collection()).'\\'.ucfirst($this->blueprint());
73+
return parent::modelName().'\\'.ucfirst($this->collectionHandle()).'\\'.ucfirst($this->blueprintHandle());
6574
}
6675
}

src/Factories/Concerns/CreatesTerm.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
namespace Aerni\Factory\Factories\Concerns;
44

55
use Illuminate\Support\Arr;
6+
use Statamic\Fields\Blueprint;
67
use Statamic\Contracts\Taxonomies\Term;
78
use Statamic\Facades\Term as TermFacade;
9+
use Statamic\Facades\Blueprint as BlueprintFacade;
810

911
trait CreatesTerm
1012
{
@@ -16,8 +18,8 @@ trait CreatesTerm
1618
public function newModel(array $attributes = []): Term
1719
{
1820
$term = TermFacade::make()
19-
->taxonomy($this->taxonomy())
20-
->blueprint($this->blueprint());
21+
->taxonomy($this->taxonomyHandle())
22+
->blueprint($this->blueprintHandle());
2123

2224
$published = Arr::pull($attributes, 'published', true);
2325
$slug = Arr::pull($attributes, 'slug');
@@ -47,7 +49,7 @@ public function newModel(array $attributes = []): Term
4749
return $term;
4850
}
4951

50-
protected function taxonomy(): string
52+
protected function taxonomyHandle(): string
5153
{
5254
return $this->taxonomy
5355
?? str(get_class($this))
@@ -56,7 +58,7 @@ protected function taxonomy(): string
5658
->lower();
5759
}
5860

59-
protected function blueprint(): string
61+
protected function blueprintHandle(): string
6062
{
6163
return $this->blueprint
6264
?? str(get_class($this))
@@ -65,8 +67,13 @@ protected function blueprint(): string
6567
->lower();
6668
}
6769

70+
protected function blueprint(): Blueprint
71+
{
72+
return BlueprintFacade::find("taxonomies/{$this->taxonomyHandle()}/{$this->blueprintHandle()}");
73+
}
74+
6875
public function modelName(): string
6976
{
70-
return parent::modelName().'\\'.ucfirst($this->collection()).'\\'.ucfirst($this->blueprint());
77+
return parent::modelName().'\\'.ucfirst($this->taxonomyHandle()).'\\'.ucfirst($this->blueprintHandle());
7178
}
7279
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
namespace Aerni\Factory\Factories\Concerns;
4+
5+
use Statamic\Fields\Field;
6+
use Statamic\Fields\Value;
7+
use Statamic\Fields\Fields;
8+
use Statamic\Fields\Values;
9+
use Statamic\Fieldtypes\Bard;
10+
use Statamic\Fieldtypes\Grid;
11+
use Illuminate\Http\UploadedFile;
12+
use Illuminate\Support\Collection;
13+
use Statamic\Fieldtypes\Replicator;
14+
use Illuminate\Filesystem\Filesystem;
15+
use Statamic\Contracts\Entries\Entry;
16+
use Statamic\Fieldtypes\Assets\Assets;
17+
use Illuminate\Support\Facades\Storage;
18+
use Statamic\Forms\Uploaders\AssetsUploader;
19+
20+
trait WithAssets
21+
{
22+
// TODO: This can be a lot of work since we are augmenting the full entry upfront.
23+
// Can we do it differently by only augmenting the fields we need?
24+
public function moveAssets(Entry $entry): void
25+
{
26+
$field = $entry->toAugmentedCollection()
27+
->map(fn ($field) => match (true) {
28+
$field->fieldtype() instanceof Replicator => $field->value(),
29+
$field->fieldtype() instanceof Bard => $field->value(),
30+
$field->fieldtype() instanceof Grid => $field->value(),
31+
default => $field,
32+
})
33+
->dot()
34+
->map(fn ($value) => $value instanceof Values ? $value->all() : $value)
35+
->flatten()
36+
->filter(fn ($value) => $value instanceof Value && $value->fieldtype() instanceof Assets)
37+
->each(fn ($value) => $value->value()?->move($this->assetsFolder($value->fieldtype())));
38+
}
39+
40+
protected function assetsFolder(Assets $fieldtype): ?string
41+
{
42+
if (! in_array($field = $fieldtype->config('dynamic'), ['id', 'slug', 'author'])) {
43+
return null;
44+
}
45+
46+
if (! ($parent = $fieldtype->field()->parent()) instanceof Entry) {
47+
return null;
48+
}
49+
50+
$value = $parent->$field;
51+
52+
return match (true) {
53+
$value instanceof Collection => $value->first(), /* If the author field doesn't have a max_items of 1, it'll be a collection, so grab the first one. */
54+
is_object($value) => $value->id(), /* If the author field had max_items 1 it would be a user, or since we got it above, use its id. */
55+
default => $value
56+
};
57+
}
58+
59+
public function asset(string $field, int $width, int $height): string
60+
{
61+
$fields = $this->processFields($this->blueprint()->fields()->all());
62+
63+
$field = collect($fields)->dot()->get($field);
64+
65+
$image = $this->image($width, $height);
66+
67+
$uploadedFile = $this->uploadedFile($image);
68+
69+
$id = AssetsUploader::field($field->config())->upload($uploadedFile);
70+
71+
return $field->setValue($id)->process()->value();
72+
}
73+
74+
// TODO: This is basically a copy from the DefinitionGenerator. Can we abstract and reuse it?
75+
protected function processFields(Collection $fields): array
76+
{
77+
return $fields->map(fn (Field $field) => match ($field->type()) {
78+
'bard' => $this->processBardAndReplicator($field),
79+
'replicator' => $this->processBardAndReplicator($field),
80+
'grid' => $this->processGrid($field),
81+
default => $field,
82+
})->all();
83+
}
84+
85+
// TODO: This is basically a copy from the DefinitionGenerator. Can we abstract and reuse it?
86+
protected function processBardAndReplicator(Field $field): array
87+
{
88+
return collect($field->toArray()['sets'])
89+
->flatMap(function ($setGroup) {
90+
return collect($setGroup['sets'])->mapWithKeys(function ($set, $type) {
91+
return [$type => $this->processFields((new Fields($set['fields']))->all())];
92+
});
93+
})->toArray();
94+
}
95+
96+
// TODO: This is basically a copy from the DefinitionGenerator. Can we abstract and reuse it?
97+
protected function processGrid(Field $field): array
98+
{
99+
$fields = (new Fields($field->toArray()['fields']))
100+
->all()
101+
->pipe($this->processFields(...));
102+
103+
return $fields;
104+
}
105+
106+
protected function image(int $width, int $height): string
107+
{
108+
$storage = Storage::disk('local');
109+
110+
$dir = 'factory-tmp';
111+
112+
if (! $storage->exists($dir)) {
113+
$storage->makeDirectory($dir);
114+
}
115+
116+
return $this->faker->image($storage->path($dir), $width, $height);
117+
}
118+
119+
protected function uploadedFile(string $path): UploadedFile
120+
{
121+
$filesystem = new Filesystem;
122+
123+
$name = $filesystem->name($path);
124+
$extension = $filesystem->extension($path);
125+
$originalName = "{$name}.{$extension}";
126+
$mimeType = $filesystem->mimeType($path);
127+
$error = null;
128+
$test = true;
129+
130+
return new UploadedFile($path, $originalName, $mimeType, $error, $test);
131+
}
132+
}

src/Factories/Factory.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function __construct(
3434
) {
3535
$this->states ??= new Collection;
3636
$this->afterMaking ??= new Collection;
37-
$this->afterCreating ??= new Collection;
37+
$this->afterCreating ??= $this->defaultAfterCreating();
3838
$this->recycle ??= new Collection;
3939
$this->faker = $this->withFaker();
4040
}
@@ -271,6 +271,14 @@ public function afterCreating(Closure $callback): self
271271
]);
272272
}
273273

274+
protected function defaultAfterCreating(): Collection
275+
{
276+
return collect()
277+
->when(method_exists($this, 'moveAssets'), function ($collection) {
278+
$collection->push(fn ($entry) => $this->moveAssets($entry));
279+
});
280+
}
281+
274282
protected function callAfterMaking(Collection $instances): void
275283
{
276284
$instances->each(function ($model) {

src/ServiceProvider.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace Aerni\Factory;
44

5+
use Faker\Factory;
6+
use Faker\Generator;
57
use Statamic\Providers\AddonServiceProvider;
8+
use Smknstd\FakerPicsumImages\FakerPicsumImagesProvider;
69

710
class ServiceProvider extends AddonServiceProvider
811
{
@@ -11,4 +14,15 @@ class ServiceProvider extends AddonServiceProvider
1114
Console\Commands\MakeSeeder::class,
1215
Console\Commands\Seed::class,
1316
];
17+
18+
public function register(): void
19+
{
20+
$this->app->singleton(Generator::class, function () {
21+
$faker = Factory::create();
22+
23+
$faker->addProvider(new FakerPicsumImagesProvider($faker));
24+
25+
return $faker;
26+
});
27+
}
1428
}

0 commit comments

Comments
 (0)