Skip to content

Commit

Permalink
Revisit the full slug saving flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Tofandel committed Nov 21, 2024
1 parent 7bd0023 commit f1c987c
Show file tree
Hide file tree
Showing 17 changed files with 110 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class Page extends Model
'description',
];
public $slugAttributes = [
protected $slugFields = [
'title',
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Article extends Model implements LocalizedUrlRoutable
'description',
];

public $slugAttributes = [
protected $slugFields = [
'title',
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Article extends Model implements Sortable

// #endregion fillable

public $slugAttributes = [
private $slugFields = [
'title',
];

Expand Down
2 changes: 1 addition & 1 deletion examples/basic-page-builder/app/Models/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Page extends Model
'description',
];

public $slugAttributes = [
protected $slugFields = [
'title',
];
}
2 changes: 1 addition & 1 deletion examples/portfolio/app/Models/Partner.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Partner extends Model
'description',
];

public $slugAttributes = [
protected $slugFields = [
'title',
];

Expand Down
2 changes: 1 addition & 1 deletion examples/portfolio/app/Models/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Project extends Model
'description',
];

public $slugAttributes = [
protected $slugFields = [
'title',
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Post extends Model implements Sortable

public $translatedAttributes = ['title', 'description'];

public $slugAttributes = ['title'];
public $slugFields = ['title'];

public $mediasParams = [
'cover' => [
Expand Down
2 changes: 1 addition & 1 deletion examples/tests-modules/app/Models/Author.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Author extends Model implements Sortable
public $translatedAttributes = ['name', 'description', 'bio'];

// uncomment and modify this as needed if you use the HasSlug trait
public $slugAttributes = ['name'];
protected $slugFields = ['name'];

// add checkbox fields names here (published toggle is itself a checkbox)
public $checkboxes = ['published'];
Expand Down
2 changes: 1 addition & 1 deletion examples/tests-modules/app/Models/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Category extends Model
public $translatedAttributes = ['title'];

// uncomment and modify this as needed if you use the HasSlug trait
public $slugAttributes = ['title'];
protected $slugFields = ['title'];

// add checkbox fields names here (published toggle is itself a checkbox)
public $checkboxes = ['published'];
Expand Down
2 changes: 1 addition & 1 deletion examples/tests-singleton/app/Models/ContactPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ContactPage extends Model
'description',
];

public $slugAttributes = [
protected $slugFields = [
'title',
];

Expand Down
2 changes: 1 addition & 1 deletion examples/tests-subdomain-routing/app/Models/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Page extends Model implements Sortable
'description',
];

public $slugAttributes = [
public $slugFields = [
'title',
];

Expand Down
3 changes: 2 additions & 1 deletion src/Commands/stubs/model.stub
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ class {{modelClassName}} extends Model {{modelImplements}}
'description',
];
{{/hasTranslation}}{{hasSlug}}
public $slugAttributes = [
protected $slugFields = [
'title',
];
protected $slugDeps = [];
{{/hasSlug}}
}
51 changes: 36 additions & 15 deletions src/Models/Behaviors/HasSlug.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private function handleSlugsSaving(): void
$slugParams = $this->twillSlugData;

foreach ($slugParams as $locale => $params) {
$slugParams[$locale] = array_merge($this->getSlugParams($locale) ?? [], $params);
$slugParams[$locale] = array_merge($this->getSlugParams($locale, empty($params['slug'])) ?? [], $params);
}
$slugParams = array_filter($slugParams, fn($p) => !empty($p['slug']));
}
Expand Down Expand Up @@ -176,7 +176,13 @@ public function updateOrNewSlug(array $slugParams): void
if ($this->relationLoaded('slugs')) {
// Report update to slugs so that getSlug() returns the correct value
$slug = $this->slugs->whereKey($oldMatchingSlug->getKey());
$slug->active = $isNowActive;
if ($slug) {
$slug->active = $isNowActive;
$slug->syncOriginalAttribute('active');
} else {
// The relation was loaded before the old slug even existed, unload it and let it lazy reload as needed
$this->unsetRelation('slugs');
}
}
if ($isNowActive) {
$this->disableLocaleSlugs($oldMatchingSlug->locale, $oldMatchingSlug->getKey());
Expand Down Expand Up @@ -220,9 +226,7 @@ protected function addOneSlug(array $slugParams, $alreadySuffixed = false): void
$slugParams['slug'] = $this->suffixSlugIfExisting($slugParams);
}

$slugParams[$this->getForeignKey()] = $this->getKey();

$slugModel = \Illuminate\Database\Eloquent\Model::unguarded(fn() => $this->getSlugModelClass()::create($slugParams));
$slugModel = \Illuminate\Database\Eloquent\Model::unguarded(fn() => $this->slugs()->create($slugParams));

if ($this->relationLoaded('slugs')) {
// Report update to slugs so that getSlug() returns the correct value
Expand All @@ -248,7 +252,10 @@ public function disableLocaleSlugs(string|array $locale = null, int|array $excep
$this->slugs->where('active', true)
->whereNotIn('id', Arr::wrap($except_slug_id))
->whereIn('locale', Arr::wrap($locale))
->each(fn($slug) => $slug->active = false);
->each(function (Model $slug) {
$slug->active = false;
$slug->syncOriginalAttribute('active');
});
}
}

Expand Down Expand Up @@ -282,11 +289,13 @@ private function suffixSlugIfExisting(array $slugParams): string
}

/**
* @deprecated
* @deprecated use suffixSlugIfExisting instead
* Checks if a slug needs a suffix due to a conflict with another model.
*/
private function slugNeedsSuffix(array $slugParams): bool
{
trigger_deprecation('area17/twill', '3.5', 'The slugNeedsSuffix method is deprecated and will be removed in 4.x use suffixSlugIfExisting instead');

unset($slugParams['active']);

$hasExisting = false;
Expand Down Expand Up @@ -349,16 +358,28 @@ public function getSlugAttribute(): string
return $this->getSlug();
}

public function getSlugDeps(): array
{
return $this->slugDeps ?? array_slice($this->slugAttributes ?? [], 1);
}
public function getSlugFields(): array
{
if (!isset($this->slugFields) && isset($this->slugAttributes)) {
trigger_deprecation('area17/twill', '3.5', 'The slugAttributes property has been deprecated instead define slug fields with the slugFields property and additional columns with the slugDeps property');
}
return $this->slugFields ?? array_slice($this->slugAttributes ?? [], 0, 1);
}

public function getSlugParams(?string $locale = null, bool $skipUnchanged = false): ?array
{
$translatedAttributes = $this->getTranslatedAttributes();
$slugParams = [];
$attributes = $this->slugAttributes ?? [];
$deps = $this->slugDependencies ?? [];
if (empty($attributes)) {
return null;
$deps = $this->getSlugDeps();
$fields = $this->getSlugFields();
if (empty($fields)) {
return $locale === null ? [] : null;
}
foreach (getLocales() as $appLocale) {
foreach ($locale ? [$locale] : getLocales() as $appLocale) {
if ($appLocale === $locale || $locale === null) {
$wasChanged = $this->wasRecentlyCreated;

Expand All @@ -384,9 +405,9 @@ public function getSlugParams(?string $locale = null, bool $skipUnchanged = fals
return $this->$attribute;
};

$slugAttributes = array_filter(array_map($getAttributeValue, $attributes));
$slugFields = array_filter(array_map($getAttributeValue, $fields));

if (empty($slugAttributes)) {
if (empty($slugFields)) {
// Skip empty slugs
continue;
}
Expand All @@ -397,7 +418,7 @@ public function getSlugParams(?string $locale = null, bool $skipUnchanged = fals
}
$slugParam = [
'active' => $translation?->active ?? true,
'slug' => Arr::join($slugAttributes, '-'),
'slug' => Arr::join($slugFields, '-'),
'locale' => $appLocale,
] + $slugDependencies;

Expand Down
4 changes: 2 additions & 2 deletions src/Repositories/Behaviors/HandleSlugs.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ trait HandleSlugs
{
public function beforeSaveHandleSlugs(TwillModelContract $object, array $fields): void
{
if (property_exists($this->model, 'slugAttributes')) {
if (method_exists($this->model, 'getSlugFields')) {
$object->twillSlugData = [];
$submittedLanguages = Collection::make($fields['languages'] ?? []);

Expand Down Expand Up @@ -63,7 +63,7 @@ public function getSlugParameters(TwillModelContract $object, array $fields, arr

$slugParams = $object->getSlugParams($slug['locale']);

foreach ($object->slugDependencies ?? [] as $param) {
foreach ($object->slugAttributes as $param) {
if (isset($slugParams[$param]) && isset($fields[$param])) {
$slug[$param] = $fields[$param];
} elseif (isset($slugParams[$param])) {
Expand Down
34 changes: 34 additions & 0 deletions tests/integration/Anonymous/AnonymousModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class AnonymousModule
/** @var string[] */
private array $slugAttributes = [];

/** @var string[] */
private array $slugFields = [];

/** @var string[] */
private array $slugDeps = [];

protected function __construct(public string $namePlural, public Application $app)
{
$this->classPrinter = new PsrPrinter();
Expand Down Expand Up @@ -203,6 +209,22 @@ public function withSlugAttributes(array $fields): self
return $this;
}


public function withSlugFields(array $fields): self
{
$this->slugFields = $fields;

return $this;
}

public function withSlugDeps(array $fields): self
{
$this->slugDeps = $fields;

return $this;
}


/**
* Boots the anonymous module and returns the model class.
*/
Expand Down Expand Up @@ -626,6 +648,18 @@ private function getModelClass(string $classNameWithNamespace): PhpNamespace
$this->slugAttributes
);
}
if ($this->slugDeps !== []) {
$class->addProperty(
'slugDeps',
$this->slugDeps
);
}
if ($this->slugFields !== []) {
$class->addProperty(
'slugFields',
$this->slugFields
);
}

foreach ($this->belongsToMany as $name => $target) {
$method = $class->addMethod($name);
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/Models/StandaloneSlugTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public function testSavingStandaloneSlug(): void
$this->assertEquals(10, count($log));
$this->assertEquals('new-test-author', $author->getSlug('en'));
$this->assertEquals('nouveau-test-auteur', $author->getSlug('fr'));
// All queries combined should take less than 15ms (they usually take a total of 5ms)
$this->assertLessThan(15, array_sum(Arr::pluck($log, 'time')));
// All queries combined should take less than 40ms (they usually take a total of 5ms, allow big range to avoid flaky test)
$this->assertLessThan(40, array_sum(Arr::pluck($log, 'time')));

$author2 = $createAuthor();
$this->assertEquals('test-author-2', $author2->slug);
Expand Down
28 changes: 23 additions & 5 deletions tests/integration/SlugTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

namespace A17\Twill\Tests\Integration;

use A17\Twill\Models\Behaviors\HasSlug;
use A17\Twill\Models\Model;
use A17\Twill\Tests\Integration\Anonymous\AnonymousModule;
use App\Models\Author;
use App\Repositories\AuthorRepository;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;

class SlugTest extends TestCase
{
Expand Down Expand Up @@ -34,7 +32,7 @@ public function testMultipleSlugAttributes()
'first_name' => [],
'last_name' => [],
])
->withSlugAttributes([
->withSlugFields([
'last_name', 'first_name'
])
->boot();
Expand Down Expand Up @@ -179,4 +177,24 @@ public function testCanReuseSoftDeletedSlugWithHistory(): void
$this->assertCount(2, $model->slugs()->get());
$this->assertEquals('slug-update-2', $model->getSlug());
}

public function testCustomSlugDoesntChangeOnUpdate(): void
{
/** @var Model|HasSlug $model */
$model = $this->module->getRepository()->create([
'title' => 'My title',
'slug' => ['en' => 'my-custom-slug'],
]);

$this->assertEquals('my-custom-slug', $model->getSlug());
$this->assertEquals(1, $model->slugs()->count());

$model = $this->module->getRepository()->update($model->id, ['position' => 1]);

$this->assertEquals(1, $model->slugs()->count());
$this->assertEquals('my-custom-slug', $model->getSlug());

$model = $this->module->getRepository()->update($model->id, ['title' => 'My new title']);
$this->assertEquals('my-new-title', $model->getSlug());
}
}

0 comments on commit f1c987c

Please sign in to comment.