Files
myep/src/Model/ContentFeed/ManifestItem.php
T

91 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Model\ContentFeed;
use App\Entity\ContentDocument;
/**
* One entry of the TYPO3 content manifest. Identity is `(type, uid, language)`.
*
* `contentUrl`, `breadcrumb` and `detailOf` are set on pages only. `contentUrl` is fetched as
* given and never constructed: the trailing-slash variant silently serves the whole page.
*/
final readonly class ManifestItem
{
/**
* @param list<string> $breadcrumb
* @param list<array{type: string, uid: int}> $detailOf
*/
public function __construct(
public string $type,
public int $uid,
public string $language,
public ?string $site,
public ?string $url,
public \DateTimeImmutable $modified,
public ?string $contentUrl = null,
public ?string $title = null,
public ?string $description = null,
public array $breadcrumb = [],
public array $detailOf = [],
) {
}
/**
* @param array<string, mixed> $item
*/
public static function fromArray(array $item): self
{
if (!is_string($item['type'] ?? null) || !is_int($item['uid'] ?? null) || !is_string($item['language'] ?? null) || !is_string($item['modified'] ?? null)) {
throw new \InvalidArgumentException('Manifest item without type, uid, language or modified');
}
$modified = self::localTime($item['modified']);
if (null === $modified) {
throw new \InvalidArgumentException(sprintf('Manifest item %s:%d has an unparseable modified', $item['type'], $item['uid']));
}
$detailOf = [];
foreach (is_array($item['detailOf'] ?? null) ? $item['detailOf'] : [] as $ref) {
if (is_array($ref) && is_string($ref['type'] ?? null) && is_int($ref['uid'] ?? null)) {
$detailOf[] = ['type' => $ref['type'], 'uid' => $ref['uid']];
}
}
$string = static fn (mixed $value): ?string => is_string($value) && '' !== $value ? $value : null;
return new self(
type: $item['type'],
uid: $item['uid'],
language: $item['language'],
site: $string($item['site'] ?? null),
url: $string($item['url'] ?? null),
modified: $modified,
contentUrl: $string($item['contentUrl'] ?? null),
title: $string($item['title'] ?? null),
description: $string($item['description'] ?? null),
breadcrumb: array_values(array_filter(is_array($item['breadcrumb'] ?? null) ? $item['breadcrumb'] : [], 'is_string')),
detailOf: $detailOf,
);
}
/**
* A feed timestamp in the application's timezone. Doctrine stores datetimes without
* their offset, so a value kept in TYPO3's +02:00 would never compare equal to itself
* once reloaded, and every item would look changed on every run.
*/
public static function localTime(string $value): ?\DateTimeImmutable
{
$date = date_create_immutable($value);
return false === $date ? null : $date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
}
public function key(): string
{
return ContentDocument::key($this->type, $this->uid, $this->language);
}
}