$relations */ public function load(Tenant $tenant, array $relations = []): Tenant { $this->loadMany(new EloquentCollection([$tenant]), $relations); return $tenant; } /** * @param EloquentCollection $tenants * @param array $relations * @return EloquentCollection */ 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 $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 */ 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 */ private function pathSegments(string $path): array { $path = ltrim($path, '$'); $path = ltrim($path, '.'); return $path === '' ? [] : explode('.', $path); } /** * @return array */ 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; } }