refactor(tenant): introduce TenantInformationService for loading tenant data and resolving website extras
This commit is contained in:
177
app/Domains/Tenant/Services/TenantInformationService.php
Normal file
177
app/Domains/Tenant/Services/TenantInformationService.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Services;
|
||||
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Models\WebsiteExtra;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class TenantInformationService
|
||||
{
|
||||
private const DEFAULT_RELATIONS = [
|
||||
'headerLogo',
|
||||
'footerLogo',
|
||||
'socialMedia',
|
||||
'websiteType',
|
||||
'websiteExtras.websiteTypeExtra',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $relations
|
||||
*/
|
||||
public function load(Tenant $tenant, array $relations = []): Tenant
|
||||
{
|
||||
$this->loadMany(new EloquentCollection([$tenant]), $relations);
|
||||
|
||||
return $tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EloquentCollection<int, Tenant> $tenants
|
||||
* @param array<int|string, mixed> $relations
|
||||
* @return EloquentCollection<int, Tenant>
|
||||
*/
|
||||
public function loadMany(
|
||||
EloquentCollection $tenants,
|
||||
array $relations = []
|
||||
): EloquentCollection {
|
||||
$tenants->loadMissing([
|
||||
...self::DEFAULT_RELATIONS,
|
||||
...$relations,
|
||||
]);
|
||||
|
||||
$websiteExtras = $tenants
|
||||
->flatMap(fn (Tenant $tenant): Collection => $tenant->websiteExtras);
|
||||
|
||||
$this->resolveWebsiteExtraAttachments($websiteExtras);
|
||||
|
||||
return $tenants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace attachment IDs with their models in each extra's resolved config.
|
||||
*
|
||||
* @param Collection<int, WebsiteExtra> $websiteExtras
|
||||
*/
|
||||
private function resolveWebsiteExtraAttachments(Collection $websiteExtras): void
|
||||
{
|
||||
$attachmentIds = $websiteExtras
|
||||
->flatMap(function (WebsiteExtra $extra): array {
|
||||
$ids = [];
|
||||
|
||||
foreach ($this->attachmentTransformPaths($extra) as $path) {
|
||||
$ids = [
|
||||
...$ids,
|
||||
...$this->valuesAtPath($extra->config, $this->pathSegments($path)),
|
||||
];
|
||||
}
|
||||
|
||||
return $ids;
|
||||
})
|
||||
->filter(fn (mixed $id): bool => is_int($id) || ctype_digit((string) $id))
|
||||
->map(fn (mixed $id): int => (int) $id)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
$attachments = Attachment::query()
|
||||
->whereIn('id', $attachmentIds)
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
foreach ($websiteExtras as $extra) {
|
||||
$config = $extra->config;
|
||||
|
||||
foreach ($this->attachmentTransformPaths($extra) as $path) {
|
||||
$config = $this->replaceAtPath(
|
||||
$config,
|
||||
$this->pathSegments($path),
|
||||
fn (mixed $attachmentId): ?Attachment => $attachments->get((int) $attachmentId)
|
||||
);
|
||||
}
|
||||
|
||||
$extra->setResolvedConfig($config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function attachmentTransformPaths(WebsiteExtra $extra): array
|
||||
{
|
||||
return collect($extra->websiteTypeExtra->config_schema['transforms'] ?? [])
|
||||
->filter(
|
||||
fn (mixed $transform): bool => is_array($transform)
|
||||
&& ($transform['handler'] ?? null) === 'attachment'
|
||||
)
|
||||
->keys()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function pathSegments(string $path): array
|
||||
{
|
||||
$path = ltrim($path, '$');
|
||||
$path = ltrim($path, '.');
|
||||
|
||||
return $path === '' ? [] : explode('.', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
private function valuesAtPath(mixed $value, array $segments): array
|
||||
{
|
||||
if ($segments === []) {
|
||||
return [$value];
|
||||
}
|
||||
|
||||
if (! is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$segment = array_shift($segments);
|
||||
|
||||
if ($segment === '*') {
|
||||
return collect($value)
|
||||
->flatMap(fn (mixed $item): array => $this->valuesAtPath($item, $segments))
|
||||
->all();
|
||||
}
|
||||
|
||||
if (! array_key_exists($segment, $value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->valuesAtPath($value[$segment], $segments);
|
||||
}
|
||||
|
||||
private function replaceAtPath(mixed $value, array $segments, callable $replace): mixed
|
||||
{
|
||||
if ($segments === []) {
|
||||
return $value === null ? null : $replace($value);
|
||||
}
|
||||
|
||||
if (! is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$segment = array_shift($segments);
|
||||
|
||||
if ($segment === '*') {
|
||||
foreach ($value as $key => $item) {
|
||||
$value[$key] = $this->replaceAtPath($item, $segments, $replace);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (array_key_exists($segment, $value)) {
|
||||
$value[$segment] = $this->replaceAtPath($value[$segment], $segments, $replace);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user