PageSqlDataMapper::getExtendedQuery()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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