Lines 100.00% 26 / 26
Methods 100.00% 4 / 4
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 fromStored 100.00% 1 / 1 100.00% 1 / 1 2
 fromArray 100.00% 8 / 8 100.00% 1 / 1 1
 toArray 100.00% 16 / 16 100.00% 1 / 1 1
17final class Bannergress
18{
19    /** The newest banners kept of the to-do list. */
20    public const int MAX_BANNERS = 20;
21
22    /** The most published banners a sync takes. */
23    public const int MAX_PUBLISHED_BANNERS = 2000;
24
25    /** The most banners of the done list a sync takes. */
26    public const int MAX_DONE_BANNERS = 20000;
27
28    /** A banner picture as Bannergress has it, relative to PICTURE_BASE. */
29    public const string PICTURE_PATTERN = '/^\/bnrs\/pictures\/[A-Za-z0-9]{1,100}$/';
30
31    public const string PICTURE_BASE = 'https://api.bannergress.com';
32
33    public const string BANNER_BASE = 'https://bannergress.com/banner/';
34
35    /**
36     * @param list<array{id: string, title: string, missions: int, picture: string|null, startLatitude?: float|int|null, startLongitude?: float|int|null}>                               $todoBanners      newest first
37     * @param list<array{id: string, title: string, missions: int, picture: string|null, startLatitude?: float|int|null, startLongitude?: float|int|null, eventStartDate?: string|null}> $doneBanners      newest first, written only
38     * @param list<array{id: string, title: string, missions: int, picture: string|null, startLatitude?: float|int|null, startLongitude?: float|int|null}>                               $publishedBanners newest first
39     */
40    public function __construct(
41        /** Banners with a mission the agent created. */
42        #[Groups([Me::READ, Me::WRITE, Agent::ITEM])]
43        #[Assert\PositiveOrZero]
44        public int $published = 0,
45        #[Groups([Me::READ, Me::WRITE, Agent::ITEM])]
46        #[Assert\PositiveOrZero]
47        public int $todo = 0,
48        #[Groups([Me::READ, Me::WRITE, Agent::ITEM])]
49        #[Assert\PositiveOrZero]
50        public int $done = 0,
51        #[Groups([Me::READ, Me::WRITE, Agent::ITEM])]
52        #[Assert\Count(max: self::MAX_BANNERS)]
53        #[Assert\All([new Assert\Collection(fields: [
54            'id' => [new Assert\Type('string'), new Assert\NotBlank(), new Assert\Length(max: 200)],
55            'title' => [new Assert\Type('string'), new Assert\Length(max: 200)],
56            'missions' => [new Assert\Type('int'), new Assert\PositiveOrZero()],
57            'picture' => new Assert\Optional([new Assert\Type('string'), new Assert\Regex(self::PICTURE_PATTERN)]),
58            'startLatitude' => new Assert\Optional([new Assert\Type(['int', 'float']), new Assert\Range(min: -90, max: 90)]),
59            'startLongitude' => new Assert\Optional([new Assert\Type(['int', 'float']), new Assert\Range(min: -180, max: 180)]),
60        ])])]
61        public array $todoBanners = [],
62        /** Every banner of the done list, newest first: the app sends them, they are stored as DoneBanner. */
63        #[Groups([Me::WRITE])]
64        #[Assert\Count(max: self::MAX_DONE_BANNERS)]
65        #[Assert\All([new Assert\Collection(fields: [
66            'id' => [new Assert\Type('string'), new Assert\NotBlank(), new Assert\Length(max: 200)],
67            'title' => [new Assert\Type('string'), new Assert\Length(max: 200)],
68            'missions' => [new Assert\Type('int'), new Assert\PositiveOrZero()],
69            'picture' => new Assert\Optional([new Assert\Type('string'), new Assert\Regex(self::PICTURE_PATTERN)]),
70            'startLatitude' => new Assert\Optional([new Assert\Type(['int', 'float']), new Assert\Range(min: -90, max: 90)]),
71            'startLongitude' => new Assert\Optional([new Assert\Type(['int', 'float']), new Assert\Range(min: -180, max: 180)]),
72            // The first day of an event banner: the day it was done unless the user set one
73            'eventStartDate' => new Assert\Optional([new Assert\Type('string'), new Assert\Date()]),
74        ])])]
75        public array $doneBanners = [],
76        /** Every banner with a mission the agent created, newest first. */
77        #[Groups([Me::READ, Me::WRITE, Agent::ITEM])]
78        #[Assert\Count(max: self::MAX_PUBLISHED_BANNERS)]
79        #[Assert\All([new Assert\Collection(fields: [
80            'id' => [new Assert\Type('string'), new Assert\NotBlank(), new Assert\Length(max: 200)],
81            'title' => [new Assert\Type('string'), new Assert\Length(max: 200)],
82            'missions' => [new Assert\Type('int'), new Assert\PositiveOrZero()],
83            'picture' => new Assert\Optional([new Assert\Type('string'), new Assert\Regex(self::PICTURE_PATTERN)]),
84            'startLatitude' => new Assert\Optional([new Assert\Type(['int', 'float']), new Assert\Range(min: -90, max: 90)]),
85            'startLongitude' => new Assert\Optional([new Assert\Type(['int', 'float']), new Assert\Range(min: -180, max: 180)]),
86        ])])]
87        public array $publishedBanners = [],
88        /** When the app synced it (ISO 8601); the server sets it. */
89        #[Groups([Me::READ, Agent::ITEM])]
90        public ?string $syncedAt = null,
91    ) {
92    }
93
94    /**
95     * @param array<string, mixed>|null $stored as stored on the User, null when not synced yet
96     */
97    public static function fromStored(?array $stored): ?self
98    {
99        return null === $stored ? null : self::fromArray($stored);
100    }
101
102    /**
103     * @param array<string, mixed> $stored as stored on the User
104     */
105    public static function fromArray(array $stored): self
106    {
107        /* @var array{published?: int, todo?: int, done?: int, todoBanners?: list<array{id: string, title: string, missions: int, picture: string|null, startLatitude?: float|int|null, startLongitude?: float|int|null}>, doneBanners?: list<array{id: string, title: string, missions: int, picture: string|null, startLatitude?: float|int|null, startLongitude?: float|int|null}>, syncedAt?: string} $stored */
108        return new self(
109            $stored['published'] ?? 0,
110            $stored['todo'] ?? 0,
111            $stored['done'] ?? 0,
112            $stored['todoBanners'] ?? [],
113            publishedBanners: $stored['publishedBanners'] ?? [],
114            syncedAt: $stored['syncedAt'] ?? null,
115        );
116    }
117
118    /**
119     * @return array<string, mixed> for the User, without the done banners (DoneBanner)
120     */
121    public function toArray(): array
122    {
123        $banner = static fn (array $banner): array => [
124            'id' => $banner['id'],
125            'title' => $banner['title'],
126            'missions' => $banner['missions'],
127            'picture' => $banner['picture'] ?? null,
128            'startLatitude' => $banner['startLatitude'] ?? null,
129            'startLongitude' => $banner['startLongitude'] ?? null,
130        ];
131
132        return [
133            'published' => $this->published,
134            'todo' => $this->todo,
135            'done' => $this->done,
136            'todoBanners' => \array_map($banner, $this->todoBanners),
137            'publishedBanners' => \array_map($banner, $this->publishedBanners),
138            'syncedAt' => $this->syncedAt,
139        ];
140    }
141}