Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
54.76% covered (warning)
54.76%
23 / 42
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
LogisticsEvent
54.76% covered (warning)
54.76%
23 / 42
66.67% covered (warning)
66.67%
6 / 9
94.49
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromStored
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 fromJsonLd
73.33% covered (warning)
73.33%
11 / 15
0.00% covered (danger)
0.00%
0 / 1
10.54
 eventDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 creationDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 eventCode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
 matchesCode
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
4
 types
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 toJsonLd
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace LambdaTwelve\OneRecord\Model;
6
7use DateTimeImmutable;
8use LambdaTwelve\OneRecord\JsonLd\Context;
9use LambdaTwelve\OneRecord\JsonLd\JsonLd;
10use LambdaTwelve\OneRecord\JsonLd\JsonLdException;
11use LambdaTwelve\OneRecord\JsonLd\Nodes;
12use LambdaTwelve\OneRecord\JsonLd\Writer;
13use LambdaTwelve\OneRecord\Rdf\BlankNode;
14use LambdaTwelve\OneRecord\Rdf\Graph;
15use LambdaTwelve\OneRecord\Rdf\Iri;
16use LambdaTwelve\OneRecord\Rdf\Triple;
17use LambdaTwelve\OneRecord\Vocabulary\Generated\Cargo;
18
19/**
20 * cargo:LogisticsEvent: an immutable status update attached to one logistics
21 * object, with its own URI below the object's. Neither a logistics object nor
22 * an embedded object, as the spec says.
23 */
24final readonly class LogisticsEvent
25{
26    public function __construct(
27        public Iri $iri,
28        public Iri $logisticsObject,
29        public Graph $graph,
30        public DateTimeImmutable $created,
31    ) {}
32
33    /**
34     * Rebuilds an event a store wrote with toJsonLd(): no validation, no
35     * re-rooting beyond giving a blank root its URI. Stores use this; the
36     * server uses fromJsonLd() for what partners post.
37     *
38     * @param string|array<string, mixed> $json
39     */
40    public static function fromStored(Iri $iri, Iri $logisticsObject, string|array $json, DateTimeImmutable $created): self
41    {
42        $document = JsonLd::expand($json, $iri);
43        $graph = $document->graph;
44        if (!$document->root->equals($iri)) {
45            $renamed = new Graph();
46            foreach ($graph as $triple) {
47                $renamed->add(new Triple(
48                    $triple->subject->equals($document->root) ? $iri : $triple->subject,
49                    $triple->predicate,
50                    $triple->object->equals($document->root) ? $iri : $triple->object,
51                ));
52            }
53            $graph = $renamed;
54        }
55
56        return new self($iri, $logisticsObject, $graph, $created);
57    }
58
59    /**
60     * Reads a posted event body and gives it its URI under the object.
61     *
62     * @param string|array<string, mixed> $json
63     */
64    public static function fromJsonLd(string|array $json, Iri $iri, Iri $logisticsObject, DateTimeImmutable $created): self
65    {
66        try {
67            $document = JsonLd::expand($json);
68        } catch (JsonLdException $e) {
69            throw new ModelException($e->getMessage(), previous: $e);
70        }
71        $types = $document->rootTypes();
72        if ($types === [] || !\in_array(Cargo::LogisticsEvent, $types, true) && !\in_array(Cargo::StatusUpdateEvent, $types, true)) {
73            throw new ModelException('The body is not a cargo:LogisticsEvent.');
74        }
75        $graph = new Graph();
76        foreach ($document->graph as $triple) {
77            $subject = $triple->subject->equals($document->root) ? $iri : $triple->subject;
78            $object = $triple->object->equals($document->root) ? $iri : $triple->object;
79            $graph->add(new Triple($subject, $triple->predicate, $object));
80        }
81        $event = new self($iri, $logisticsObject, $graph, $created);
82        if ($event->eventDate() === null) {
83            throw new ModelException('Every logistics event must have a cargo:eventDate.');
84        }
85
86        return $event;
87    }
88
89    public function eventDate(): ?DateTimeImmutable
90    {
91        return Nodes::dateTime($this->graph, $this->iri, Cargo::eventDate);
92    }
93
94    public function creationDate(): ?DateTimeImmutable
95    {
96        return Nodes::dateTime($this->graph, $this->iri, Cargo::creationDate);
97    }
98
99    /**
100     * The event code as written: a code-list IRI (…/StatusCode#DEP), a
101     * CodeListElement's cargo:code, or a plain string. Filters match on it.
102     */
103    public function eventCode(): ?string
104    {
105        $term = $this->graph->firstObject($this->iri, Cargo::eventCode);
106        if ($term instanceof Iri) {
107            return $term->value;
108        }
109        if ($term instanceof BlankNode) {
110            return Nodes::string($this->graph, $term, Cargo::code);
111        }
112
113        return $term === null ? null : Nodes::string($this->graph, $this->iri, Cargo::eventCode);
114    }
115
116    public function matchesCode(string $filter): bool
117    {
118        $code = $this->eventCode();
119
120        return $code !== null && ($code === $filter || str_ends_with($code, '#' . $filter) || str_ends_with($code, '/' . $filter));
121    }
122
123    /**
124     * @return list<string>
125     */
126    public function types(): array
127    {
128        $types = array_map(static fn(Iri $t): string => $t->value, $this->graph->typesOf($this->iri));
129        sort($types, SORT_STRING);
130
131        return $types;
132    }
133
134    /**
135     * @return array<string, mixed>
136     */
137    public function toJsonLd(?Context $context = null, bool $includeContext = true): array
138    {
139        return (new Writer())->write($this->graph, $this->iri, $context ?? Context::oneRecord(), $includeContext);
140    }
141}