Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion src/Element/Queries/ElementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public function __construct(
public string $elementType = Element::class,
protected array $config = [],
) {
parent::__construct($config);
parent::__construct();

$this->query = DB::table($this->table)->select('**');

Expand Down Expand Up @@ -267,6 +267,8 @@ public function __construct(
}

$this->initTraits();

self::configure($this, $config);
}

protected function initTraits(): void
Expand Down Expand Up @@ -821,11 +823,56 @@ public function orderBy($column, $direction = 'asc'): self
return $this;
}

if (is_string($column)) {
if ($this->isDeprecatedOrderByString($column)) {
Deprecator::log(
static::class.'::orderBy(string)',
sprintf('Passing `%s` to %s::orderBy() has been deprecated. Pass the direction as the second argument, or call orderBy() once for each column.', $column, static::class),
);
}

foreach ($this->normalizeOrderByString($column, $direction) as [$column, $direction]) {
$this->forwardCallTo($this->query, 'orderBy', [$column, $direction]);
}

return $this;
}

$this->forwardCallTo($this->query, 'orderBy', [$column, $direction]);

return $this;
}

private function isDeprecatedOrderByString(string $columns): bool
{
return str_contains($columns, ',') || preg_match('/\s+(asc|desc)(?:\s*(?:,|$))/i', $columns) === 1;
}

private function normalizeOrderByString(string $columns, mixed $defaultDirection): array
{
$defaultDirection = match ($defaultDirection) {
SORT_DESC, 'desc' => 'desc',
default => 'asc',
};

return str($columns)
->explode(',')
->map(fn (string $column) => trim($column))
->filter()
->map(function (string $column) use ($defaultDirection) {
$direction = $defaultDirection;

if (preg_match('/^(.+?)\s+(asc|desc)$/i', $column, $matches)) {
$column = $matches[1];
$direction = strtolower($matches[2]);
}

return [trim($column), $direction];
})
->values()
->all();
}

/**
* Get the "offset" value from the query or null if it's not set.
*/
Expand Down
36 changes: 36 additions & 0 deletions tests/Feature/Element/Queries/Concerns/FormatsResultsTest.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<?php

use CraftCms\Cms\Element\Queries\AddressQuery;
use CraftCms\Cms\Element\Queries\EntryQuery;
use CraftCms\Cms\Entry\Models\Entry;
use CraftCms\Cms\Support\Facades\Deprecator;
use Illuminate\Support\Collection;

test('inReverse', function () {
Expand Down Expand Up @@ -109,3 +111,37 @@
'user last login date' => [userQuery(...), 'lastLoginDate', 'users.lastLoginDate'],
'address country code' => [fn () => new AddressQuery, 'countryCode', 'addresses.countryCode'],
]);

it('parses string order columns with directions', function () {
$query = entryQuery()->orderBy('dateUpdated DESC, title ASC');
$query->applyBeforeQueryCallbacks();

expect($query->getQuery()->orders)
->toContain(['column' => 'elements.dateUpdated', 'direction' => 'desc'])
->toContain(['column' => 'elements_sites.title', 'direction' => 'asc']);
});

it('parses string order columns with directions from find criteria', function () {
$entry = Entry::factory()->create();

expect(CraftCms\Cms\Entry\Elements\Entry::findOne([
'sectionId' => $entry->sectionId,
'orderBy' => 'dateUpdated DESC',
]))->not()->toBeNull();
});

it('parses string order columns with directions from query config', function () {
$query = entryQuery(['orderBy' => 'dateUpdated DESC']);
$query->applyBeforeQueryCallbacks();

expect($query->getQuery()->orders)
->toContain(['column' => 'elements.dateUpdated', 'direction' => 'desc']);
});

it('logs a deprecation for legacy string order columns', function () {
entryQuery()->orderBy('dateUpdated DESC');

expect(collect(array_keys(Deprecator::getRequestLogs()))
->contains(fn (string $key) => str_starts_with($key, EntryQuery::class.'::orderBy(string)-')))
->toBeTrue();
});
Loading