Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.00% covered (success)
94.00%
47 / 50
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
DriverBase
94.00% covered (success)
94.00%
47 / 50
62.50% covered (warning)
62.50%
5 / 8
19.08
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 dispatch
n/a
0 / 0
n/a
0 / 0
0
 addConnection
n/a
0 / 0
n/a
0 / 0
0
 changeConnection
n/a
0 / 0
n/a
0 / 0
0
 setOptions
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 setLogger
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setQueryOptions
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 processRecords
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 formatRecords
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
8.01
 processOptions
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 parseOptions
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
1<?php
2
3declare(strict_types=1);
4
5namespace Projom\Storage\Engine\Driver;
6
7use Projom\Storage\Query\Action;
8use Projom\Storage\Engine\Driver\Connection\ConnectionInterface;
9use Projom\Storage\Query\Format;
10use Projom\Storage\Query\RecordInterface;
11use Psr\Log\LoggerAwareInterface;
12use Psr\Log\LoggerInterface;
13use Psr\Log\NullLogger;
14
15abstract class DriverBase implements LoggerAwareInterface
16{
17    protected const DEFAULT_OPTIONS = [
18        'return_single_record' => false,
19    ];
20
21    protected LoggerInterface $logger;
22    private array $options = [];
23    private null|array $queryOptions = null;
24
25    public function __construct(LoggerInterface $logger = new NullLogger(), array $options = [])
26    {
27        $this->logger = $logger;
28        $this->setOptions($options);
29    }
30
31    abstract public function dispatch(Action $action, mixed $args): mixed;
32    abstract public function addConnection(ConnectionInterface $connection): void;
33    abstract public function changeConnection(int|string $name): void;
34
35    public function setOptions(array $options): void
36    {
37        $this->logger->debug(
38            'Method: {method} with {options}.',
39            ['options' => $options, 'method' => __METHOD__]
40        );
41
42        $this->options = $options;
43    }
44
45    public function setLogger(LoggerInterface $logger): void
46    {
47        $this->logger = $logger;
48    }
49
50    protected function setQueryOptions(null|array $queryOptions): void
51    {
52        $this->logger->debug(
53            'Method: {method} with {options}.',
54            ['options' => $queryOptions, 'method' => __METHOD__]
55        );
56
57        $this->queryOptions = $queryOptions;
58    }
59
60    protected function processRecords(array $records, array $formatting): mixed
61    {
62        $this->logger->debug(
63            'Method: {method} with {records}.',
64            ['records' => $records, 'method' => __METHOD__]
65        );
66
67        $records = $this->formatRecords($records, ...$formatting);
68        $records = $this->processOptions($records);
69
70        return $records;
71    }
72
73    private function formatRecords(array $records, Format $format, mixed $args = null): mixed
74    {
75        $this->logger->debug(
76            'Method: {method} with {format} and {args}.',
77            ['format' => $format->name, 'args' => $args, 'method' => __METHOD__]
78        );
79
80        switch ($format) {
81            case Format::ARRAY:
82                return $records;
83
84            case Format::STD_CLASS:
85                return array_map(fn($record) => (object) $record, $records);
86
87            case Format::CUSTOM_OBJECT:
88                $className = $args;
89
90                if ($className === null)
91                    throw new \Exception('Class name not provided.', 400);
92                if (!class_exists($className))
93                    throw new \Exception("Class: $className does not exist.", 400);
94                if (!is_subclass_of($className, RecordInterface::class))
95                    throw new \Exception("Class: $className must implement RecordInterface.", 400);
96
97                return array_map(fn($record) =>  $className::createFromRecord($record), $records);
98
99            default:
100                throw new \Exception('Format is not recognized.', 400);
101        }
102    }
103
104    private function processOptions(array $records): array|object
105    {
106        $options = $this->parseOptions();
107
108        if ($options['return_single_record'])
109            if (count($records) === 1)
110                $records = $records[0];
111
112        return $records;
113    }
114
115    private function parseOptions(): array
116    {
117        $parseOptions = $this->options;
118        if ($this->queryOptions !== null)
119            $parseOptions = $this->queryOptions;
120
121        $options = static::DEFAULT_OPTIONS;
122
123        if (array_key_exists('return_single_record', $parseOptions))
124            $options['return_single_record'] = (bool) $parseOptions['return_single_record'];
125
126        return $options;
127    }
128}