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

ICanBoogie / ActiveRecord / 6362433236

30 Sep 2023 11:14AM UTC coverage: 85.731% (+5.6%) from 80.178%
6362433236

push

github

olvlvl
Rename StaticModelProvider methods

1436 of 1675 relevant lines covered (85.73%)

29.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.93
/lib/ActiveRecord/Connection.php
1
<?php
2

3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
namespace ICanBoogie\ActiveRecord;
13

14
use ICanBoogie\Accessor\AccessorTrait;
15
use ICanBoogie\ActiveRecord\Config\ConnectionDefinition;
16
use PDO;
17
use PDOException;
18
use Throwable;
19

20
use function explode;
21
use function strtr;
22

23
/**
24
 * A connection to a database.
25
 */
26
class Connection
27
{
28
    /**
29
     * @uses lazy_get_driver
30
     */
31
    use AccessorTrait;
32

33
    private const DRIVERS_MAPPING = [
34

35
        'mysql' => Driver\MySQLDriver::class,
36
        'sqlite' => Driver\SQLiteDriver::class,
37

38
    ];
39

40
    public readonly string $id;
41

42
    /**
43
     * Prefix to prepend to every table name.
44
     *
45
     * If set to "dev", all table names will be named like "dev_nodes", "dev_contents", etc.
46
     * This is a convenient way of creating a namespace for tables in a shared database.
47
     * By default, the prefix is the empty string, that is there is not prefix.
48
     *
49
     * @var non-empty-string
50
     */
51
    public readonly string $table_name_prefix;
52

53
    /**
54
     * Charset for the connection. Also used to specify the charset while creating tables.
55
     */
56
    public readonly string $charset;
57

58
    /**
59
     * Used to specify the collate while creating tables.
60
     */
61
    public readonly string $collate;
62

63
    /**
64
     * Timezone of the connection.
65
     */
66
    public readonly string $timezone;
67

68
    /**
69
     * Driver name for the connection.
70
     */
71
    public readonly string $driver_name;
72

73
    private Driver $driver;
74

75
    private function lazy_get_driver(): Driver
76
    {
77
        return $this->resolve_driver($this->driver_name);
76✔
78
    }
79

80
    /**
81
     * The number of database queries and executions, used for statistics purpose.
82
     */
83
    public int $queries_count = 0;
84
    public readonly PDO $pdo;
85

86
    /**
87
     * The number of micro seconds spent per request.
88
     *
89
     * @var array[]
90
     */
91
    public array $profiling = [];
92

93
    /**
94
     * Establish a connection to a database.
95
     *
96
     * Custom options can be specified using the driver-specific connection options. See
97
     * {@link Options}.
98
     *
99
     * @link http://www.php.net/manual/en/pdo.construct.php
100
     * @link http://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html
101
     */
102
    public function __construct(ConnectionDefinition $definition)
103
    {
104
        unset($this->driver); // to trigger lazy loading
106✔
105

106
        $this->id = $definition->id;
106✔
107
        $dsn = $definition->dsn;
106✔
108

109
        $this->table_name_prefix = $definition->table_name_prefix
106✔
110
            ? $definition->table_name_prefix . '_'
52✔
111
            : '';
54✔
112

113
        [ $this->charset, $this->collate ] = extract_charset_and_collate(
106✔
114
            $definition->charset_and_collate ?? $definition::DEFAULT_CHARSET_AND_COLLATE
106✔
115
        );
106✔
116

117
        $this->timezone = $definition->time_zone;
106✔
118
        $this->driver_name = $this->resolve_driver_name($dsn);
106✔
119

120
        $options = $this->make_options();
106✔
121

122
        $this->pdo = new PDO($dsn, $definition->username, $definition->password, $options);
106✔
123

124
        $this->after_connection();
105✔
125
    }
126

127
    /**
128
     * Alias to {@link query}.
129
     */
130
    public function __invoke(mixed ...$args): Statement
131
    {
132
        return $this->query(...$args);
×
133
    }
134

135
    /**
136
     * Resolve the driver name from the DSN string.
137
     */
138
    protected function resolve_driver_name(string $dsn): string
139
    {
140
        return explode(':', $dsn, 2)[0];
106✔
141
    }
142

143
    /**
144
     * Resolves driver class.
145
     *
146
     * @throws DriverNotDefined
147
     *
148
     * @return class-string<Driver>
149
     */
150
    private function resolve_driver_class(string $driver_name): string
151
    {
152
        return self::DRIVERS_MAPPING[$driver_name]
76✔
153
            ?? throw new DriverNotDefined($driver_name);
76✔
154
    }
155

156
    /**
157
     * Resolves a {@link Driver} implementation.
158
     */
159
    private function resolve_driver(string $driver_name): Driver
160
    {
161
        $driver_class = $this->resolve_driver_class($driver_name);
76✔
162

163
        return new $driver_class(
76✔
164
            function () {
76✔
165
                return $this;
75✔
166
            }
76✔
167
        );
76✔
168
    }
169

170
    /**
171
     * Called before the connection.
172
     *
173
     * May alter the options according to the driver.
174
     *
175
     * @return array<PDO::*, mixed>
176
     */
177
    private function make_options(): array
178
    {
179
        if ($this->driver_name != 'mysql') {
106✔
180
            return [];
105✔
181
        }
182

183
        $init_command = 'SET NAMES ' . $this->charset;
1✔
184
        $init_command .= ', time_zone = "' . $this->timezone . '"';
1✔
185

186
        return [
1✔
187

188
            PDO::MYSQL_ATTR_INIT_COMMAND => $init_command,
1✔
189

190
        ];
1✔
191
    }
192

193
    private function after_connection(): void
194
    {
195
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
105✔
196
    }
197

198
    /**
199
     * Overrides the method to resolve the statement before it is prepared, then set its fetch
200
     * mode and connection.
201
     *
202
     * @param string $statement Query statement.
203
     * @param array<string, mixed> $options
204
     *
205
     * @return Statement The prepared statement.
206
     *
207
     * @throws StatementNotValid if the statement cannot be prepared.
208
     */
209
    public function prepare(string $statement, array $options = []): Statement
210
    {
211
        $statement = $this->resolve_statement($statement);
62✔
212

213
        try {
214
            $statement = $this->pdo->prepare($statement, $options);
62✔
215
        } catch (PDOException $e) {
1✔
216
            throw new StatementNotValid($statement, $e);
1✔
217
        }
218

219
        if (isset($options['mode'])) {
62✔
220
            $mode = (array) $options['mode'];
×
221

222
            $statement->setFetchMode(...$mode);
×
223
        }
224

225
        return new Statement($statement, $this);
62✔
226
    }
227

228
    /**
229
     * Overrides the method in order to prepare (and resolve) the statement and execute it with
230
     * the specified arguments and options.
231
     *
232
     * @param array<string|int, mixed> $args
233
     * @param array<string, mixed> $options
234
     */
235
    public function query(string $statement, array $args = [], array $options = []): Statement
236
    {
237
        $statement = $this->prepare($statement, $options);
61✔
238
        $statement->execute($args);
61✔
239

240
        return $statement;
61✔
241
    }
242

243
    /**
244
     * Executes a statement.
245
     *
246
     * The statement is resolved using the {@link resolve_statement()} method before it is
247
     * executed.
248
     *
249
     * The execution of the statement is wrapped in a try/catch block. {@link PDOException} are
250
     * caught and {@link StatementNotValid} exception are thrown with additional information
251
     * instead.
252
     *
253
     * Using this method increments the `queries_by_connection` stat.
254
     *
255
     * @return false|int @FIXME https://github.com/sebastianbergmann/phpunit/issues/4735
256
     * @throws StatementNotValid if the statement cannot be executed.
257
     */
258
    public function exec(string $statement): bool|int
259
    {
260
        $statement = $this->resolve_statement($statement);
75✔
261

262
        try {
263
            $this->queries_count++;
75✔
264

265
            return $this->pdo->exec($statement);
75✔
266
        } catch (PDOException $e) {
×
267
            throw new StatementNotValid($statement, $e);
×
268
        }
269
    }
270

271
    /**
272
     * Replaces placeholders with their value.
273
     *
274
     * The following placeholders are supported:
275
     *
276
     * - `{prefix}`: replaced by the {@link $table_name_prefix} property.
277
     * - `{charset}`: replaced by the {@link $charset} property.
278
     * - `{collate}`: replaced by the {@link $collate} property.
279
     */
280
    public function resolve_statement(string $statement): string
281
    {
282
        return strtr($statement, [
75✔
283
            '{prefix}' => $this->table_name_prefix,
75✔
284
            '{charset}' => $this->charset,
75✔
285
            '{collate}' => $this->collate,
75✔
286
        ]);
75✔
287
    }
288

289
    /**
290
     * Alias for the `beginTransaction()` method.
291
     *
292
     * @see PDO::beginTransaction
293
     */
294
    public function begin(): bool
295
    {
296
        return $this->pdo->beginTransaction();
×
297
    }
298

299
    /**
300
     * @codeCoverageIgnore
301
     */
302
    public function quote_string(string $string): string
303
    {
304
        return $this->pdo->quote($string);
305
    }
306

307
    public function quote_identifier(string $identifier): string
308
    {
309
        return $this->driver->quote_identifier($identifier);
1✔
310
    }
311

312
    public function cast_value(mixed $value, string $type = null): mixed
313
    {
314
        return $this->driver->cast_value($value, $type);
×
315
    }
316

317
    /**
318
     * @param non-empty-string $unprefixed_table_name
319
     *
320
     * @throws Throwable
321
     */
322
    public function create_table(string $unprefixed_table_name, Schema $schema): void
323
    {
324
        $this->driver->create_table($this->table_name_prefix . $unprefixed_table_name, $schema);
75✔
325
    }
326

327
    /**
328
     * @codeCoverageIgnore
329
     */
330
    public function table_exists(string $unprefixed_name): bool
331
    {
332
        return $this->driver->table_exists($this->table_name_prefix . $unprefixed_name);
333
    }
334

335
    /**
336
     * @codeCoverageIgnore
337
     */
338
    public function optimize(): void
339
    {
340
        $this->driver->optimize();
341
    }
342
}
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

© 2026 Coveralls, Inc