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

ICanBoogie / ActiveRecord / 11645444145

02 Nov 2024 08:03PM UTC coverage: 86.318%. Remained the same
11645444145

push

github

olvlvl
Tidy Query

9 of 18 new or added lines in 2 files covered. (50.0%)

97 existing lines in 4 files now uncovered.

1369 of 1586 relevant lines covered (86.32%)

24.43 hits per line

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

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

3
namespace ICanBoogie\ActiveRecord;
4

5
use ICanBoogie\Accessor\AccessorTrait;
6
use ICanBoogie\ActiveRecord\Config\ConnectionDefinition;
7
use InvalidArgumentException;
8
use PDO;
9
use PDOException;
10
use RuntimeException;
11
use Throwable;
12

13
use function explode;
14
use function strtr;
15

16
/**
17
 * A connection to a database.
18
 *
19
 * @see self::get_last_insert_id()
20
 * @property-read int $last_insert_id
21
 *     Returns the ID of the last inserted row, or the last value from a sequence object,
22
 *     depending on the underlying driver.
23
 */
24
class Connection
25
{
26
    /**
27
     * @see self::get_last_insert_id()
28
     */
29
    use AccessorTrait;
30

31
    private const array DRIVERS_MAPPING = [
32

33
        'mysql' => Driver\MySQLDriver::class,
34
        'sqlite' => Driver\SQLiteDriver::class,
35

36
    ];
37

38
    public readonly string $id;
39

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

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

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

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

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

70
    public readonly PDO $pdo;
71
    public readonly ConnectionTelemetry $telemetry;
72

73
    /**
74
     * Establish a connection to a database.
75
     *
76
     * Custom options can be specified using the driver-specific connection options. See
77
     * {@see Options}.
78
     *
79
     * @link http://www.php.net/manual/en/pdo.construct.php
80
     * @link http://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html
81
     */
82
    public function __construct(ConnectionDefinition $definition)
83
    {
84
        $this->id = $definition->id;
89✔
85
        $dsn = $definition->dsn;
89✔
86

87
        $this->table_name_prefix = $definition->table_name_prefix
89✔
88
            ? $definition->table_name_prefix . '_'
30✔
89
            : '';
59✔
90

91
        [ $this->charset, $this->collate ] = extract_charset_and_collate(
89✔
92
            $definition->charset_and_collate ?? $definition::DEFAULT_CHARSET_AND_COLLATE
89✔
93
        );
89✔
94

95
        $this->timezone = $definition->time_zone;
89✔
96
        $this->driver_name = $this->resolve_driver_name($dsn);
89✔
97
        $this->driver = $this->resolve_driver($this->driver_name);
89✔
98
        $this->telemetry = new ConnectionTelemetry($this->id);
89✔
99

100
        $options = $this->make_options();
89✔
101

102
        $this->pdo = new PDO($dsn, $definition->username, $definition->password, $options);
89✔
103

104
        $this->after_connection();
88✔
105
    }
106

107
    /**
108
     * Resolve the driver name from the DSN string.
109
     */
110
    protected function resolve_driver_name(string $dsn): string
111
    {
112
        return explode(':', $dsn, 2)[0];
89✔
113
    }
114

115
    /**
116
     * Resolves driver class.
117
     *
118
     * @return class-string<Driver>
119
     * @throws DriverNotDefined
120
     *
121
     */
122
    private function resolve_driver_class(string $driver_name): string
123
    {
124
        return self::DRIVERS_MAPPING[$driver_name]
89✔
125
            ?? throw new DriverNotDefined($driver_name); // @phpstan-ignore-line
89✔
126
    }
127

128
    /**
129
     * Resolves a {@link Driver} implementation.
130
     */
131
    private function resolve_driver(string $driver_name): Driver
132
    {
133
        $driver_class = $this->resolve_driver_class($driver_name);
89✔
134

135
        return new $driver_class(
89✔
136
            function () {
89✔
137
                return $this;
66✔
138
            }
89✔
139
        );
89✔
140
    }
141

142
    /**
143
     * Called before the connection.
144
     *
145
     * May alter the options according to the driver.
146
     *
147
     * @return array<PDO::*, mixed>
148
     */
149
    private function make_options(): array
150
    {
151
        if ($this->driver_name != 'mysql') {
89✔
152
            return [];
88✔
153
        }
154

155
        $init_command = 'SET NAMES ' . $this->charset;
1✔
156
        $init_command .= ', time_zone = "' . $this->timezone . '"';
1✔
157

158
        return [
1✔
159

160
            PDO::MYSQL_ATTR_INIT_COMMAND => $init_command,
1✔
161

162
        ];
1✔
163
    }
164

165
    private function after_connection(): void
166
    {
167
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
88✔
168
    }
169

170
    /**
171
     * Overrides the method to resolve the statement before it is prepared, then set its fetch
172
     * mode and connection.
173
     *
174
     * @throws StatementNotValid if the statement cannot be prepared.
175
     */
176
    public function prepare(string $statement): Statement
177
    {
178
        $statement = $this->resolve_statement($statement);
46✔
179

180
        try {
181
            $statement = $this->pdo->prepare($statement);
46✔
182
        } catch (PDOException $e) {
3✔
183
            throw new StatementNotValid($statement, original: $e);
3✔
184
        }
185

186
        return new Statement($statement, $this->telemetry);
44✔
187
    }
188

189
    /**
190
     * Overrides the method to prepare (and resolve) the statement and execute it with
191
     * the specified arguments and options.
192
     *
193
     * @param mixed[] $args
194
     */
195
    public function query(string $statement, array $args = []): Statement
196
    {
197
        $statement = $this->prepare($statement);
40✔
198

199
        $this->telemetry->record_execute_duration(
38✔
200
            $statement,
38✔
201
            static fn() => $statement->execute($args)
38✔
202
        );
38✔
203

204
        return $statement;
37✔
205
    }
206

207
    /**
208
     * Executes a statement.
209
     *
210
     * The statement is resolved using the {@see resolve_statement()} method before it is
211
     * executed.
212
     *
213
     * The execution of the statement is wrapped in a try/catch block.
214
     * When a {@see PDOException} is caught, it is wrapped with {@see StatementNotValid}.
215
     *
216
     * Using this method increments the `queries_count` stat.
217
     *
218
     * @return false|int @FIXME https://github.com/sebastianbergmann/phpunit/issues/4735
219
     * @throws StatementNotValid if the statement cannot be executed.
220
     */
221
    public function exec(string $statement): bool|int
222
    {
223
        $statement = $this->resolve_statement($statement);
66✔
224

225
        try {
226
            // @phpstan-ignore-next-line
227
            return $this->telemetry->record_execute_duration(
66✔
228
                $statement,
66✔
229
                fn() => $this->pdo->exec($statement)
66✔
230
            );
66✔
UNCOV
231
        } catch (PDOException $e) {
×
UNCOV
232
            throw new StatementNotValid($statement, original: $e);
×
233
        }
234
    }
235

236
    public function get_last_insert_id(): int
237
    {
238
        $id = $this->pdo->lastInsertId();
34✔
239

240
        if ($id === false) {
34✔
UNCOV
241
            throw new RuntimeException("Unable to retrieve last inserted ID");
×
242
        }
243

244
        return (int)$id;
34✔
245
    }
246

247
    /**
248
     * Replaces placeholders with their value.
249
     *
250
     * The following placeholders are supported:
251
     *
252
     * - `{prefix}`: replaced by the {@link $table_name_prefix} property.
253
     * - `{charset}`: replaced by the {@link $charset} property.
254
     * - `{collate}`: replaced by the {@link $collate} property.
255
     */
256
    public function resolve_statement(string $statement): string
257
    {
258
        return strtr($statement, [
70✔
259
            '{prefix}' => $this->table_name_prefix,
70✔
260
            '{charset}' => $this->charset,
70✔
261
            '{collate}' => $this->collate,
70✔
262
        ]);
70✔
263
    }
264

265
    /**
266
     * Alias for the `beginTransaction()` method.
267
     *
268
     * @see PDO::beginTransaction
269
     */
270
    public function begin(): bool
271
    {
UNCOV
272
        return $this->pdo->beginTransaction();
×
273
    }
274

275
    /**
276
     * @see PDO::quote()
277
     */
278
    public function quote(string $string, int $type = PDO::PARAM_STR): string
279
    {
280
        $quoted = $this->pdo->quote($string, $type);
1✔
281

282
        // @phpstan-ignore-next-line
283
        if ($quoted === false) {
1✔
UNCOV
284
            throw new InvalidArgumentException("Unsupported quote type: $type");
×
285
        }
286

287
        return $quoted;
1✔
288
    }
289

290
    public function quote_identifier(string $identifier): string
291
    {
292
        return $this->driver->quote_identifier($identifier);
1✔
293
    }
294

295
    public function cast_value(mixed $value, string $type = null): mixed
296
    {
UNCOV
297
        return $this->driver->cast_value($value, $type);
×
298
    }
299

300
    /**
301
     * @throws Throwable
302
     */
303
    public function create_table(string $unprefixed_table_name, Schema $schema): void
304
    {
305
        $this->driver->create_table($this->table_name_prefix . $unprefixed_table_name, $schema);
66✔
306
    }
307

308
    /**
309
     * Determines if a table exists in the database.
310
     */
311
    public function table_exists(string $unprefixed_name): bool
312
    {
313
        return $this->driver->table_exists($this->table_name_prefix . $unprefixed_name);
36✔
314
    }
315

316
    public function optimize(): void
317
    {
UNCOV
318
        $this->driver->optimize();
×
319
    }
320
}
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