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

ICanBoogie / ActiveRecord / 4437457574

pending completion
4437457574

push

github

Olivier Laviale
Case of a nullable belong_to

7 of 7 new or added lines in 1 file covered. (100.0%)

1356 of 1686 relevant lines covered (80.43%)

34.68 hits per line

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

84.75
/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
    public readonly string $table_name_prefix;
50

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

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

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

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

71
    private Driver $driver;
72

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

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

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

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

104
        $this->id = $definition->id;
112✔
105
        $dsn = $definition->dsn;
112✔
106

107
        $this->table_name_prefix = $definition->table_name_prefix
112✔
108
            ? $definition->table_name_prefix . '_'
53✔
109
            : '';
60✔
110

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

115
        $this->timezone = $definition->time_zone;
112✔
116
        $this->driver_name = $this->resolve_driver_name($dsn);
112✔
117

118
        $options = $this->make_options();
112✔
119

120
        $this->pdo = new PDO($dsn, $definition->username, $definition->password, $options);
112✔
121

122
        $this->after_connection();
111✔
123
    }
124

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

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

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

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

161
        return new $driver_class(
75✔
162
            function () {
75✔
163
                return $this;
74✔
164
            }
75✔
165
        );
75✔
166
    }
167

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

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

184
        return [
1✔
185

186
            PDO::MYSQL_ATTR_INIT_COMMAND => $init_command,
1✔
187

188
        ];
1✔
189
    }
190

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

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

211
        try {
212
            $statement = $this->pdo->prepare($statement, $options);
74✔
213
        } catch (PDOException $e) {
×
214
            throw new StatementNotValid($statement, 500, $e);
×
215
        }
216

217
        if (isset($options['mode'])) {
74✔
218
            $mode = (array) $options['mode'];
×
219

220
            $statement->setFetchMode(...$mode);
×
221
        }
222

223
        return new Statement($statement, $this);
74✔
224
    }
225

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

238
        return $statement;
73✔
239
    }
240

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

260
        try {
261
            $this->queries_count++;
74✔
262

263
            return $this->pdo->exec($statement);
74✔
264
        } catch (PDOException $e) {
×
265
            throw new StatementNotValid($statement, 500, $e);
×
266
        }
267
    }
268

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

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

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

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

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

315
    /**
316
     * @throws Throwable
317
     */
318
    public function create_table(string $unprefixed_table_name, Schema $schema): void
319
    {
320
        $this->driver->create_table($this->table_name_prefix . $unprefixed_table_name, $schema);
74✔
321
    }
322

323
    /**
324
     * @throws Throwable
325
     */
326
    public function create_indexes(string $unprefixed_table_name, Schema $schema): void
327
    {
328
        $this->driver->create_indexes($this->table_name_prefix . $unprefixed_table_name, $schema);
74✔
329
    }
330

331
    /**
332
     * @codeCoverageIgnore
333
     */
334
    public function table_exists(string $unprefixed_name): bool
335
    {
336
        return $this->driver->table_exists($this->table_name_prefix . $unprefixed_name);
337
    }
338

339
    /**
340
     * @codeCoverageIgnore
341
     */
342
    public function optimize(): void
343
    {
344
        $this->driver->optimize();
345
    }
346
}
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