• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mixerapi / mixerapi-dev / 13002105632

28 Jan 2025 01:57AM UTC coverage: 86.907% (-0.5%) from 87.451%
13002105632

Pull #156

github

web-flow
Merge 9f35307d0 into 9d91f041b
Pull Request #156: Adds beforeSerialize and afterSerialize events

24 of 33 new or added lines in 3 files covered. (72.73%)

6 existing lines in 2 files now uncovered.

916 of 1054 relevant lines covered (86.91%)

3.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.0
/plugins/hal-view/src/JsonSerializer.php
1
<?php
2
declare(strict_types=1);
3

4
namespace MixerApi\HalView;
5

6
use Cake\Datasource\EntityInterface;
7
use Cake\Datasource\Paging\PaginatedResultSet;
8
use Cake\Datasource\ResultSetInterface;
9
use Cake\Event\Event;
10
use Cake\Event\EventManager;
11
use Cake\Http\ServerRequest;
12
use Cake\ORM\Entity;
13
use Cake\Utility\Inflector;
14
use Cake\View\Helper\PaginatorHelper;
15
use MixerApi\Core\View\SerializableAssociation;
16
use ReflectionClass;
17
use ReflectionException;
18
use RuntimeException;
19

20
/**
21
 * Creates a HAL+JSON resource
22
 *
23
 * @link https://tools.ietf.org/html/draft-kelly-json-hal-06
24
 */
25
class JsonSerializer
26
{
27
    public const BEFORE_SERIALIZE_EVENT = 'MixerApi.HalView.beforeSerialize';
28
    public const AFTER_SERIALIZE_EVENT = 'MixerApi.HalView.afterSerialize';
29
    private mixed $data;
30

31
    /**
32
     * If constructed without parameters collection meta data will not be added to HAL $data
33
     *
34
     * @param mixed $serialize the data to be converted into a HAL array
35
     * @param \Cake\Http\ServerRequest|null $request optional ServerRequest
36
     * @param \Cake\View\Helper\PaginatorHelper|null $paginator optional PaginatorHelper
37
     */
38
    public function __construct(
39
        mixed $serialize,
40
        private ?ServerRequest $request = null,
41
        private ?PaginatorHelper $paginator = null
42
    )
43
    {
44
        $hal = $this->recursion($serialize);
7✔
45

46
        if ($hal instanceof ResultSetInterface || $hal instanceof PaginatedResultSet) {
7✔
47
            $this->data = $this->collection($hal);
3✔
48
        } elseif (is_subclass_of($hal, Entity::class)) {
4✔
49
            $this->data = $this->item($hal);
3✔
50
        } else {
51
            $this->data = $serialize;
1✔
52
        }
53
    }
54

55
    /**
56
     * Serializes HAL data as hal+json
57
     *
58
     * @param int $jsonOptions JSON options see https://www.php.net/manual/en/function.json-encode.php
59
     * @return string
60
     * @throws \RuntimeException
61
     */
62
    public function asJson(int $jsonOptions = 0): string
63
    {
64
        EventManager::instance()->dispatch(new Event(self::BEFORE_SERIALIZE_EVENT, $this));
6✔
65

66
        $json = json_encode($this->data, $jsonOptions);
6✔
67

68
        if ($json === false) {
6✔
69
            throw new RuntimeException(json_last_error_msg(), json_last_error());
1✔
70
        }
71

72
        EventManager::instance()->dispatch(new Event(self::AFTER_SERIALIZE_EVENT, $this, [
5✔
73
            'data' => $json,
5✔
74
        ]));
5✔
75

76
        return $json;
5✔
77
    }
78

79
    /**
80
     * Get HAL data as an array
81
     *
82
     * @return mixed
83
     */
84
    public function getData(): mixed
85
    {
86
        return $this->data;
1✔
87
    }
88

89
    public function setData(mixed $data): void
90
    {
NEW
91
        $this->data = $data;
×
92
    }
93

94
    /**
95
     * @return \Cake\Http\ServerRequest|null
96
     */
97
    public function getRequest(): ?ServerRequest
98
    {
NEW
99
        return $this->request;
×
100
    }
101

102
    /**
103
     * @return \Cake\View\Helper\PaginatorHelper|null
104
     */
105
    public function getPaginatorHelper(): ?PaginatorHelper
106
    {
NEW
107
        return $this->paginator;
×
108
    }
109

110
    /**
111
     * Recursive method for converting mixed data into HAL. This method converts instances of Cake\ORM\Entity into
112
     * HAL resources, but does not serialize the data.
113
     *
114
     * @param mixed $mixed data to be serialized
115
     * @return array|\Cake\Datasource\EntityInterface|\Cake\Datasource\ResultSetInterface|\Cake\Datasource\Paging\PaginatedResultSet
116
     */
117
    private function recursion(mixed &$mixed): mixed
118
    {
119
        if ($mixed instanceof ResultSetInterface || $mixed instanceof PaginatedResultSet || is_array($mixed)) {
7✔
120
            foreach ($mixed as $item) {
6✔
121
                $this->recursion($item);
6✔
122
            }
123
        } elseif ($mixed instanceof EntityInterface) {
7✔
124
            $serializableAssociation = new SerializableAssociation($mixed);
6✔
125

126
            $mixed = $this->resource($mixed, $serializableAssociation);
6✔
127

128
            foreach ($serializableAssociation->getAssociations() as $value) {
6✔
129
                $this->recursion($value);
5✔
130
            }
131
        }
132

133
        return $mixed;
7✔
134
    }
135

136
    /**
137
     * HAL array for collection requests
138
     *
139
     * @param \Cake\Datasource\Paging\PaginatedResultSet|\Cake\Datasource\ResultSetInterface $collection the data to be converted into a HAL array
140
     * @return array
141
     */
142
    private function collection(mixed $collection): array
143
    {
144
        try {
145
            if ($collection instanceof PaginatedResultSet) {
3✔
146
                $entity = $collection->toArray()[0];
3✔
147
            } else {
UNCOV
148
                $entity = $collection->first();
×
149
            }
150
            $tableName = Inflector::tableize((new ReflectionClass($entity))->getShortName());
3✔
151
        } catch (ReflectionException $e) {
×
UNCOV
152
            $tableName = 'data';
×
153
        }
154

155
        $links = [];
3✔
156

157
        $return = [
3✔
158
            'count' => $collection->count(),
3✔
159
            'total' => null,
3✔
160
        ];
3✔
161

162
        if ($this->request instanceof ServerRequest) {
3✔
163
            $links = [
3✔
164
                'self' => ['href' => $this->request->getPath()],
3✔
165
            ];
3✔
166
        }
167

168
        if ($this->paginator instanceof PaginatorHelper) {
3✔
169
            $links = array_merge_recursive($links, [
3✔
170
                'next' => ['href' => $this->paginator->next()],
3✔
171
                'prev' => ['href' => $this->paginator->prev()],
3✔
172
                'first' => ['href' => $this->paginator->first()],
3✔
173
                'last' => ['href' => $this->paginator->last()],
3✔
174
            ]);
3✔
175
            $return['total'] = intval($this->paginator->counter());
3✔
176
        }
177

178
        $return['_links'] = $links;
3✔
179
        $return['_embedded'] = [$tableName => $collection];
3✔
180

181
        return $return;
3✔
182
    }
183

184
    /**
185
     * HAL array for item requests
186
     *
187
     * @param mixed $hal the data to be converted into a HAL array
188
     * @return array
189
     */
190
    private function item(mixed $hal): array
191
    {
192
        $links = [];
3✔
193

194
        if ($this->request instanceof ServerRequest) {
3✔
195
            $links = ['_links' => ['self' => $this->request->getPath()]];
3✔
196
        }
197

198
        if (!is_array($hal)) {
3✔
199
            $hal = $hal->toArray();
3✔
200
        }
201

202
        return array_merge($links, $hal);
3✔
203
    }
204

205
    /**
206
     * Creates a HAL+JSON Resource
207
     *
208
     * Requires an instance of Cake\ORM\Entity or EntityInterface and an EmbeddableHalResource instance
209
     *
210
     * @param \Cake\Datasource\EntityInterface $entity Cake\ORM\Entity or EntityInterface
211
     * @param \MixerApi\Core\View\SerializableAssociation $association SerializableAssociation
212
     * @return \Cake\Datasource\EntityInterface
213
     */
214
    private function resource(EntityInterface $entity, SerializableAssociation $association): EntityInterface
215
    {
216
        $embedded = [];
6✔
217

218
        foreach ($association->getAssociations() as $property => $value) {
6✔
219
            if (!is_array($value) && !$value instanceof EntityInterface) {
5✔
UNCOV
220
                continue;
×
221
            }
222
            $embedded[$property] = $value;
5✔
223
            $entity->unset($property);
5✔
224
        }
225

226
        if (!empty($embedded)) {
6✔
227
            $entity->set('_embedded', $embedded);
5✔
228
        }
229

230
        if ($entity instanceof HalResourceInterface) {
6✔
231
            $entity->set('_links', $entity->getHalLinks($entity));
6✔
232
        }
233

234
        return $entity;
6✔
235
    }
236
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc