Passed
Push — master ( 6af672...0107a4 )
by Peter
07:37
created

PageSqlDataMapper::getWithLayoutQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 38
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 33
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 38
rs 9.392
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Website\Orm\DataMappers;
6
7
use AbterPhp\Framework\Domain\Entities\IStringerEntity;
8
use AbterPhp\Website\Domain\Entities\Page as Entity;
9
use AbterPhp\Website\Domain\Entities\Page\Assets as PageAssets;
10
use AbterPhp\Website\Domain\Entities\PageCategory;
11
use AbterPhp\Website\Domain\Entities\PageLayout\Assets as LayoutAssets;
12
use Opulence\Orm\DataMappers\SqlDataMapper;
13
use Opulence\QueryBuilders\Conditions\ConditionFactory;
14
use Opulence\QueryBuilders\Expression;
15
use Opulence\QueryBuilders\MySql\QueryBuilder;
16
use Opulence\QueryBuilders\MySql\SelectQuery;
17
18
/** @phan-file-suppress PhanTypeMismatchArgument */
19
class PageSqlDataMapper extends SqlDataMapper implements IPageDataMapper
20
{
21
    /**
22
     * @param IStringerEntity $entity
23
     */
24
    public function add($entity)
25
    {
26
        assert($entity instanceof Entity, new \InvalidArgumentException());
27
28
        $data  = $this->getColumnNamesToValues($entity, true);
29
        $query = (new QueryBuilder())->insert('pages', $data);
30
31
        $sql    = $query->getSql();
32
        $params = $query->getParameters();
33
34
        $statement = $this->writeConnection->prepare($sql);
35
        $statement->bindValues($params);
36
        $statement->execute();
37
    }
38
39
    /**
40
     * @param IStringerEntity $entity
41
     *
42
     * @throws \Opulence\QueryBuilders\InvalidQueryException
43
     */
44
    public function delete($entity)
45
    {
46
        assert($entity instanceof Entity, new \InvalidArgumentException());
47
48
        $query = (new QueryBuilder())
49
            ->update('pages', 'pages', ['deleted_at' => new Expression('NOW()')])
50
            ->where('id = ?')
51
            ->addUnnamedPlaceholderValue($entity->getId(), \PDO::PARAM_STR);
52
53
        $sql    = $query->getSql();
54
        $params = $query->getParameters();
55
56
        $statement = $this->writeConnection->prepare($sql);
57
        $statement->bindValues($params);
58
        $statement->execute();
59
    }
60
61
    /**
62
     * @return Entity[]
63
     * @throws \Opulence\Orm\OrmException
64
     */
65
    public function getAll(): array
66
    {
67
        $query = $this->getBaseQuery();
68
69
        $sql = $query->getSql();
70
71
        return $this->read($sql, [], self::VALUE_TYPE_ARRAY);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->read($sql,...self::VALUE_TYPE_ARRAY) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
72
    }
73
74
    /**
75
     * @param int      $limitFrom
76
     * @param int      $pageSize
77
     * @param string[] $orders
78
     * @param array    $conditions
79
     * @param array    $params
80
     *
81
     * @return Entity[]
82
     * @throws \Opulence\Orm\OrmException
83
     */
84
    public function getPage(int $limitFrom, int $pageSize, array $orders, array $conditions, array $params): array
85
    {
86
        $query = $this->getGridQuery()
87
            ->limit($pageSize)
88
            ->offset($limitFrom);
89
90
        if (!$orders) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orders of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
91
            $query->orderBy('pages.title ASC');
92
        }
93
        foreach ($orders as $order) {
94
            $query->addOrderBy($order);
95
        }
96
97
        foreach ($conditions as $condition) {
98
            $query->andWhere($condition);
99
        }
100
101
        $replaceCount = 1;
102
103
        $sql = $query->getSql();
104
        $sql = str_replace('SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $sql, $replaceCount);
105
106
        return $this->read($sql, $params, self::VALUE_TYPE_ARRAY);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->read($sql,...self::VALUE_TYPE_ARRAY) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
107
    }
108
109
    /**
110
     * @param int|string $id
111
     *
112
     * @return Entity|null
113
     * @throws \Opulence\Orm\OrmException
114
     */
115
    public function getById($id)
116
    {
117
        $query = $this->getExtendedQuery()->andWhere('pages.id = :page_id');
118
119
        $sql    = $query->getSql();
120
        $params = [
121
            'page_id' => [$id, \PDO::PARAM_STR],
122
        ];
123
124
        return $this->read($sql, $params, self::VALUE_TYPE_ENTITY, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->read($sql,...ALUE_TYPE_ENTITY, true) also could return the type array which is incompatible with the documented return type AbterPhp\Website\Domain\Entities\Page|null.
Loading history...
125
    }
126
127
    /**
128
     * @param string $identifier
129
     *
130
     * @return Entity|null
131
     * @throws \Opulence\Orm\OrmException
132
     */
133
    public function getByIdentifier(string $identifier): ?Entity
134
    {
135
        $query = $this->getExtendedQuery()->andWhere('pages.identifier = :identifier');
136
137
        $sql    = $query->getSql();
138
        $params = [
139
            'identifier' => [$identifier, \PDO::PARAM_STR],
140
        ];
141
142
        return $this->read($sql, $params, self::VALUE_TYPE_ENTITY, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->read($sql,...ALUE_TYPE_ENTITY, true) could return the type array which is incompatible with the type-hinted return AbterPhp\Website\Domain\Entities\Page|null. Consider adding an additional type-check to rule them out.
Loading history...
143
    }
144
145
    /**
146
     * @param string[] $identifiers
147
     *
148
     * @return array
149
     * @throws \Opulence\Orm\OrmException
150
     */
151
    public function getByCategoryIdentifiers(array $identifiers): array
152
    {
153
        if (count($identifiers) === 0) {
154
            return [];
155
        }
156
157
        $conditions = new ConditionFactory();
158
        $query      = $this->getSimplifiedQuery()
159
            ->andWhere($conditions->in('page_categories.identifier', $identifiers))
160
            ->andWhere('pages.is_draft = 0');
161
162
        $sql    = $query->getSql();
163
        $params = $query->getParameters();
164
165
        return $this->read($sql, $params, self::VALUE_TYPE_ARRAY);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->read($sql,...self::VALUE_TYPE_ARRAY) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
166
    }
167
168
    /**
169
     * @param string $identifier
170
     *
171
     * @return Entity|null
172
     * @throws \Opulence\Orm\OrmException
173
     */
174
    public function getWithLayout(string $identifier): ?Entity
175
    {
176
        $query = $this->getWithLayoutQuery()
177
            ->andWhere('(pages.identifier = :identifier OR pages.id = :identifier)');
178
179
        $sql    = $query->getSql();
180
        $params = [
181
            'identifier' => [$identifier, \PDO::PARAM_STR],
182
        ];
183
184
        return $this->read($sql, $params, self::VALUE_TYPE_ENTITY, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->read($sql,...ALUE_TYPE_ENTITY, true) could return the type array which is incompatible with the type-hinted return AbterPhp\Website\Domain\Entities\Page|null. Consider adding an additional type-check to rule them out.
Loading history...
185
    }
186
187
    /**
188
     * @param IStringerEntity $entity
189
     *
190
     * @throws \Opulence\QueryBuilders\InvalidQueryException
191
     */
192
    public function update($entity)
193
    {
194
        assert($entity instanceof Entity, new \InvalidArgumentException());
195
196
        $columnNamesToValues = $this->getColumnNamesToValues($entity, false);
197
198
        $query = (new QueryBuilder())
199
            ->update('pages', 'pages', $columnNamesToValues)
200
            ->where('id = ?')
201
            ->andWhere('deleted_at IS NULL')
202
            ->addUnnamedPlaceholderValue($entity->getId(), \PDO::PARAM_STR);
203
204
        $sql    = $query->getSql();
205
        $params = $query->getParameters();
206
207
        $statement = $this->writeConnection->prepare($sql);
208
        $statement->bindValues($params);
209
        $statement->execute();
210
    }
211
212
    /**
213
     * @param Entity $entity
214
     * @param bool   $create
215
     *
216
     * @return array
217
     */
218
    protected function getColumnNamesToValues(Entity $entity, bool $create): array
219
    {
220
        $layoutIdType = $entity->getLayoutId() ? \PDO::PARAM_STR : \PDO::PARAM_NULL;
221
222
        $categoryId     = $entity->getCategory() ? $entity->getCategory()->getId() : null;
223
        $categoryIdType = $categoryId ? \PDO::PARAM_STR : \PDO::PARAM_NULL;
224
225
        $columnNamesToValues = [
226
            'identifier'  => [$entity->getIdentifier(), \PDO::PARAM_STR],
227
            'title'       => [$entity->getTitle(), \PDO::PARAM_STR],
228
            'classes'     => [$entity->getClasses(), \PDO::PARAM_STR],
229
            'lede'        => [$entity->getLede(), \PDO::PARAM_STR],
230
            'body'        => [$entity->getBody(), \PDO::PARAM_STR],
231
            'is_draft'    => [$entity->isDraft(), \PDO::PARAM_BOOL],
232
            'category_id' => [$categoryId, $categoryIdType],
233
            'layout'      => [$entity->getLayout(), \PDO::PARAM_STR],
234
            'layout_id'   => [$entity->getLayoutId(), $layoutIdType],
235
        ];
236
237
        if ($create) {
238
            $columnNamesToValues = array_merge(['id' => [$entity->getId(), \PDO::PARAM_STR]], $columnNamesToValues);
239
        }
240
241
        $columnNamesToValues = $this->populateWithMeta($entity, $columnNamesToValues);
242
        $columnNamesToValues = $this->populateWithAssets($entity, $columnNamesToValues);
243
244
        return $columnNamesToValues;
245
    }
246
247
    /**
248
     * @param Entity $entity
249
     * @param array  $columnNamesToValues
250
     *
251
     * @return array
252
     */
253
    protected function populateWithMeta(Entity $entity, array $columnNamesToValues): array
254
    {
255
        $meta = $entity->getMeta();
256
257
        $metaValues = [
258
            'meta_description'    => $meta->getDescription(),
259
            'meta_robots'         => $meta->getRobots(),
260
            'meta_author'         => $meta->getAuthor(),
261
            'meta_copyright'      => $meta->getCopyright(),
262
            'meta_keywords'       => $meta->getKeywords(),
263
            'meta_og_title'       => $meta->getOGTitle(),
264
            'meta_og_image'       => $meta->getOGImage(),
265
            'meta_og_description' => $meta->getOGDescription(),
266
        ];
267
268
        return array_merge($columnNamesToValues, $metaValues);
269
    }
270
271
    /**
272
     * @param Entity $entity
273
     * @param array  $columnNamesToValues
274
     *
275
     * @return array
276
     */
277
    protected function populateWithAssets(Entity $entity, array $columnNamesToValues): array
278
    {
279
        if (!$entity->getAssets()) {
280
            return $columnNamesToValues;
281
        }
282
283
        $assets = $entity->getAssets();
284
285
        $assetValues = [
286
            'header'    => $assets->getHeader(),
287
            'footer'    => $assets->getFooter(),
288
            'css_files' => implode("\n\r", $assets->getCssFiles()),
289
            'js_files'  => implode("\n\r", $assets->getJsFiles()),
290
        ];
291
292
        return array_merge($columnNamesToValues, $assetValues);
293
    }
294
295
    /**
296
     * @param array $hash
297
     *
298
     * @return Entity
299
     */
300
    protected function loadEntity(array $hash)
301
    {
302
        $meta     = $this->loadMeta($hash);
303
        $assets   = $this->loadAssets($hash);
304
        $lede     = $hash['lede'] ?? '';
305
        $body     = $hash['body'] ?? '';
306
        $classes  = $hash['classes'] ?? '';
307
        $category = $this->loadCategory($hash);
308
        $layout   = $hash['layout'] ?? '';
309
        $layoutId = $hash['layout_id'] ?? '';
310
311
        return new Entity(
312
            $hash['id'],
313
            $hash['identifier'],
314
            $hash['title'],
315
            $classes,
316
            $lede,
317
            $body,
318
            (bool)$hash['is_draft'],
319
            $category,
320
            $layout,
321
            $layoutId,
322
            $meta,
323
            $assets
324
        );
325
    }
326
327
    /**
328
     * @param array $hash
329
     *
330
     * @return Entity\Meta|null
331
     */
332
    protected function loadMeta(array $hash): ?Entity\Meta
333
    {
334
        if (!array_key_exists('meta_description', $hash)) {
335
            return null;
336
        }
337
338
        return new Entity\Meta(
339
            $hash['meta_description'],
340
            $hash['meta_robots'],
341
            $hash['meta_author'],
342
            $hash['meta_copyright'],
343
            $hash['meta_keywords'],
344
            $hash['meta_og_title'],
345
            $hash['meta_og_image'],
346
            $hash['meta_og_description']
347
        );
348
    }
349
350
    /**
351
     * @param array $hash
352
     *
353
     * @return PageAssets|null
354
     */
355
    protected function loadAssets(array $hash): ?PageAssets
356
    {
357
        if (!array_key_exists('css_files', $hash)) {
358
            return null;
359
        }
360
361
        $layoutAssets = $this->loadLayoutAssets($hash);
362
363
        return new PageAssets(
364
            $hash['identifier'],
365
            $hash['header'],
366
            $hash['footer'],
367
            $this->extractFiles($hash['css_files']),
368
            $this->extractFiles($hash['js_files']),
369
            $layoutAssets
370
        );
371
    }
372
373
    /**
374
     * @param array $hash
375
     *
376
     * @return PageCategory|null
377
     */
378
    protected function loadCategory(array $hash): ?PageCategory
379
    {
380
        $id         = isset($hash['category_id']) ? $hash['category_id'] : '';
381
        $name       = isset($hash['category_name']) ? $hash['category_name'] : '';
382
        $identifier = isset($hash['category_identifier']) ? $hash['category_identifier'] : '';
383
384
        if (!$id && !$name && !$identifier) {
385
            return null;
386
        }
387
388
        return new PageCategory($id, $name, $identifier);
389
    }
390
391
    /**
392
     * @param array $hash
393
     *
394
     * @return LayoutAssets|null
395
     */
396
    protected function loadLayoutAssets(array $hash): ?LayoutAssets
397
    {
398
        if (!array_key_exists('layout_css_files', $hash) || null === $hash['layout_css_files']) {
399
            return null;
400
        }
401
402
        return new LayoutAssets(
403
            $hash['layout_identifier'],
404
            $hash['layout_header'],
405
            $hash['layout_footer'],
406
            $this->extractFiles($hash['layout_css_files']),
407
            $this->extractFiles($hash['layout_js_files'])
408
        );
409
    }
410
411
    /**
412
     * @param string $rawData
413
     *
414
     * @return string[]
415
     */
416
    private function extractFiles(string $rawData): array
417
    {
418
        if (empty($rawData)) {
419
            return [];
420
        }
421
422
        return explode("\n", str_replace("\r", "", trim($rawData)));
423
    }
424
425
    /**
426
     * @return SelectQuery
427
     */
428
    private function getBaseQuery(): SelectQuery
429
    {
430
        /** @var SelectQuery $query */
431
        $query = (new QueryBuilder())
432
            ->select(
433
                'pages.id',
434
                'pages.identifier',
435
                'pages.title',
436
                'pages.is_draft',
437
                'pages.category_id',
438
                'pages.layout_id'
439
            )
440
            ->from('pages')
441
            ->where('pages.deleted_at IS NULL');
442
443
        return $query;
444
    }
445
446
    /**
447
     * @return SelectQuery
448
     */
449
    private function getSimplifiedQuery(): SelectQuery
450
    {
451
        /** @var SelectQuery $query */
452
        $query = (new QueryBuilder())
453
            ->select(
454
                'pages.id',
455
                'pages.identifier',
456
                'pages.title',
457
                'pages.lede',
458
                'pages.is_draft',
459
                'page_categories.id AS category_id',
460
                'page_categories.identifier AS category_identifier',
461
                'page_categories.name AS category_name'
462
            )
463
            ->from('pages')
464
            ->innerJoin('page_categories', 'page_categories', 'page_categories.id = pages.category_id')
465
            ->where('pages.deleted_at IS NULL');
466
467
        return $query;
468
    }
469
470
    /**
471
     * @return SelectQuery
472
     */
473
    private function getGridQuery(): SelectQuery
474
    {
475
        /** @var SelectQuery $query */
476
        $query = (new QueryBuilder())
477
            ->select(
478
                'pages.id',
479
                'pages.identifier',
480
                'pages.title',
481
                'pages.is_draft',
482
                'categories.name AS category_name',
483
                'pages.layout_id',
484
                "IF(layouts.name <> '', layouts.name, pages.layout) AS layout"
485
            )
486
            ->from('pages')
487
            ->leftJoin('page_categories', 'categories', 'categories.id = pages.category_id')
488
            ->leftJoin('page_layouts', 'layouts', 'layouts.id = pages.layout_id')
489
            ->where('pages.deleted_at IS NULL');
490
491
        return $query;
492
    }
493
494
    /**
495
     * @return SelectQuery
496
     */
497
    private function getExtendedQuery(): SelectQuery
498
    {
499
        /** @var SelectQuery $query */
500
        $query = (new QueryBuilder())
501
            ->select(
502
                'pages.id',
503
                'pages.identifier',
504
                'pages.title',
505
                'pages.classes',
506
                'pages.lede',
507
                'pages.body',
508
                'pages.is_draft',
509
                'pages.category_id',
510
                'pages.layout_id',
511
                'pages.layout',
512
                'pages.meta_description',
513
                'pages.meta_robots',
514
                'pages.meta_author',
515
                'pages.meta_copyright',
516
                'pages.meta_keywords',
517
                'pages.meta_og_title',
518
                'pages.meta_og_image',
519
                'pages.meta_og_description',
520
                'pages.header',
521
                'pages.footer',
522
                'pages.css_files',
523
                'pages.js_files'
524
            )
525
            ->from('pages')
526
            ->where('pages.deleted_at IS NULL');
527
528
        return $query;
529
    }
530
531
    /**
532
     * @return SelectQuery
533
     */
534
    private function getWithLayoutQuery(): SelectQuery
535
    {
536
        /** @var SelectQuery $query */
537
        $query = (new QueryBuilder())
538
            ->select(
539
                'pages.id',
540
                'pages.identifier',
541
                'pages.title',
542
                "CONCAT(layouts.classes, ' ', pages.classes) AS classes",
543
                'pages.lede',
544
                'pages.body',
545
                'pages.is_draft',
546
                'pages.category_id',
547
                'pages.layout_id',
548
                'COALESCE(layouts.body, pages.layout) AS layout',
549
                'pages.meta_description',
550
                'pages.meta_robots',
551
                'pages.meta_author',
552
                'pages.meta_copyright',
553
                'pages.meta_keywords',
554
                'pages.meta_og_title',
555
                'pages.meta_og_image',
556
                'pages.meta_og_description',
557
                'pages.header AS header',
558
                'pages.footer AS footer',
559
                'pages.css_files AS css_files',
560
                'pages.js_files AS js_files',
561
                'layouts.identifier AS layout_identifier',
562
                'layouts.header AS layout_header',
563
                'layouts.footer AS layout_footer',
564
                'layouts.css_files AS layout_css_files',
565
                'layouts.js_files AS layout_js_files'
566
            )
567
            ->from('pages')
568
            ->leftJoin('page_layouts', 'layouts', 'layouts.id = pages.layout_id AND layouts.deleted_at IS NULL')
569
            ->where('pages.deleted_at IS NULL');
570
571
        return $query;
572
    }
573
}
574