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

mixerapi / mixerapi-dev / 13002149106

28 Jan 2025 02:01AM UTC coverage: 86.907%. First build
13002149106

Pull #156

github

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

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

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
        $hal = $this->recursion($serialize);
7✔
44

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

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

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

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

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

75
        return $json;
5✔
76
    }
77

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

88
    /**
89
     * @param mixed $data The data to be serialized
90
     * @return void
91
     */
92
    public function setData(mixed $data): void
93
    {
NEW
94
        $this->data = $data;
×
95
    }
96

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

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

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

129
            $mixed = $this->resource($mixed, $serializableAssociation);
6✔
130

131
            foreach ($serializableAssociation->getAssociations() as $value) {
6✔
132
                $this->recursion($value);
5✔
133
            }
134
        }
135

136
        return $mixed;
7✔
137
    }
138

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

158
        $links = [];
3✔
159

160
        $return = [
3✔
161
            'count' => $collection->count(),
3✔
162
            'total' => null,
3✔
163
        ];
3✔
164

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

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

181
        $return['_links'] = $links;
3✔
182
        $return['_embedded'] = [$tableName => $collection];
3✔
183

184
        return $return;
3✔
185
    }
186

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

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

201
        if (!is_array($hal)) {
3✔
202
            $hal = $hal->toArray();
3✔
203
        }
204

205
        return array_merge($links, $hal);
3✔
206
    }
207

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

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

229
        if (!empty($embedded)) {
6✔
230
            $entity->set('_embedded', $embedded);
5✔
231
        }
232

233
        if ($entity instanceof HalResourceInterface) {
6✔
234
            $entity->set('_links', $entity->getHalLinks($entity));
6✔
235
        }
236

237
        return $entity;
6✔
238
    }
239
}
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