Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
PDOAccess
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 14
1332
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 __call
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 postConnect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loadAccessor
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 connect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0
 requirePdo
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getConnection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 query
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 exec
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 prepare
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 beginTransaction
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 commit
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 rollBack
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 inTransaction
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BO\Zmsdldb;
4
5abstract class PDOAccess extends AbstractAccess
6{
7    /**
8     * @SuppressWarnings(PHPMD.LongVariable)
9     */
10    protected mixed $accessorClassName = [];
11
12    protected array $accessorNamesPlural = [
13        'Authority' => 'Authorities',
14        'Borough' => 'Boroughs',
15        'Link' => 'Links',
16        'Location' => 'Locations',
17        'Office' => 'Offices',
18        'Service' => 'Services',
19        'Setting' => 'Settings',
20        'Topic' => 'Topics'
21    ];
22
23    protected ?\PDO $pdo = null;
24
25    protected string $engine = 'SQLite';
26
27
28    public function __construct(array $options)
29    {
30        try {
31            $parts = explode("\\", static::class);
32            $this->engine = str_replace('Access', '', end($parts));
33
34            $accessorNameKeys = array_keys($this->accessorNamesPlural);
35            $accessorClassName = array_flip($this->accessorNamesPlural);
36
37            $this->accessorClassName = array_merge(
38                $accessorClassName,
39                array_combine($accessorNameKeys, $accessorNameKeys)
40            );
41            $this->accessorNamesPlural = array_merge(
42                $this->accessorNamesPlural,
43                array_flip($this->accessorNamesPlural)
44            );
45
46            if (isset($options['pdoConnection']) && $options['pdoConnection'] instanceof \PDO) {
47                $this->pdo = $options['pdoConnection'];
48            } else {
49                $this->connect($options);
50            }
51            $this->requirePdo()->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
52            $this->postConnect();
53        } catch (\Exception $e) {
54            throw $e;
55        }
56    }
57
58    #[\Override]
59    public function __call(mixed $method, array $args = [])
60    {
61        try {
62            return parent::__call($method, $args);
63        } catch (\Exception $e) {
64            if ('loadFromPath' != $method && preg_match('/load(?P<accessor>[A-Za-z_0-9]+)/', $method, $matches)) {
65                $locale = $args[0] ?? 'de';
66                $instance = $this->loadAccessor($matches['accessor'], $locale);
67
68                return $instance;
69            }
70        }
71    }
72
73    /**
74     * @return void
75     */
76    protected function postConnect()
77    {
78    }
79
80    public function loadAccessor(string $name, string $locale = 'de'): mixed
81    {
82        if (isset($this->accessorClassName[$name])) {
83            if (null === $this->accessInstance[$locale][$this->accessorClassName[$name]]) {
84                $accessorClass = __NAMESPACE__ . '\\' . $this->engine . '\\' . $this->accessorClassName[$name];
85
86                /** @var class-string $accessorClass */
87                $instance = new $accessorClass($this, $locale);
88                $this->accessInstance[$locale][$name] = $instance;
89                $this->accessInstance[$locale][$this->accessorNamesPlural[$name]] = $instance;
90            }
91            return $this->accessInstance[$locale][$name];
92        }
93        throw new \Exception('Invalid accessor');
94    }
95
96    abstract protected function connect(array $options): void;
97
98    protected function requirePdo(): \PDO
99    {
100        if (!$this->pdo instanceof \PDO) {
101            throw new \RuntimeException('PDO connection is not initialized');
102        }
103        return $this->pdo;
104    }
105
106    /** @psalm-api */
107    public function getConnection(): mixed
108    {
109        return $this->pdo;
110    }
111
112    /**
113     * parameters see https://www.php.net/manual/de/pdo.query.php
114     */
115
116    public function query(mixed ...$args): mixed
117    {
118        try {
119            return $this->requirePdo()->query(...$args);
120        } catch (\Exception $e) {
121            throw $e;
122        }
123    }
124
125    /**
126     * parameters see https://www.php.net/manual/de/pdo.exec.php
127     */
128    public function exec(string ...$args): mixed
129    {
130        try {
131            return $this->requirePdo()->exec(...$args);
132        } catch (\Exception $e) {
133            throw $e;
134        }
135    }
136
137    /**
138     * parameters see https://www.php.net/manual/de/pdo.prepare.php
139     */
140    public function prepare(mixed ...$args): mixed
141    {
142        try {
143            return $this->requirePdo()->prepare(...$args);
144        } catch (\Exception $e) {
145            throw $e;
146        }
147    }
148
149    protected int $transactionCount = 0;
150
151    public function beginTransaction(): mixed
152    {
153        try {
154            $this->transactionCount++;
155            if ($this->inTransaction()) {
156                return true;
157            }
158            return $this->requirePdo()->beginTransaction();
159        } catch (\Exception $e) {
160            throw $e;
161        }
162    }
163
164    public function commit(): mixed
165    {
166        try {
167            $this->transactionCount--;
168            if ($this->transactionCount == 0 && $this->inTransaction()) {
169                return $this->requirePdo()->commit();
170            } elseif (!$this->inTransaction()) {
171                trigger_error(__METHOD__ . ' no transaction started');
172            }
173            return true;
174        } catch (\Exception $e) {
175            throw $e;
176        }
177    }
178
179    public function rollBack(): mixed
180    {
181        try {
182            $this->transactionCount--;
183            if ($this->transactionCount == 0 && $this->inTransaction()) {
184                return $this->requirePdo()->rollBack();
185            } elseif (!$this->inTransaction()) {
186                trigger_error(__METHOD__ . ' no transaction started');
187            }
188            return true;
189        } catch (\Exception $e) {
190            throw $e;
191        }
192    }
193
194    public function inTransaction(): mixed
195    {
196        try {
197            return $this->requirePdo()->inTransaction();
198        } catch (\Exception $e) {
199            throw $e;
200        }
201    }
202}