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

ICanBoogie / ActiveRecord / 11881074700

17 Nov 2024 06:07PM UTC coverage: 86.108%. Remained the same
11881074700

push

github

olvlvl
Use PHPStan 2.0

5 of 10 new or added lines in 6 files covered. (50.0%)

108 existing lines in 5 files now uncovered.

1376 of 1598 relevant lines covered (86.11%)

24.51 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 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;
90✔
85
        $dsn = $definition->dsn;
90✔
86

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

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

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

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

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

104
        $this->after_connection();
89✔
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];
90✔
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]
90✔
125
            ?? throw new DriverNotDefined($driver_name); // @phpstan-ignore-line
90✔
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);
90✔
134

135
        return new $driver_class(
90✔
136
            function () {
90✔
137
                return $this;
66✔
138
            }
90✔
139
        );
90✔
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') {
90✔
152
            return [];
89✔
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);
89✔
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) // @phpstan-ignore argument.type
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
            return $this->telemetry->record_execute_duration(
66✔
227
                $statement,
66✔
228
                fn() => $this->pdo->exec($statement)
66✔
229
            );
66✔
230
        } catch (PDOException $e) {
×
UNCOV
231
            throw new StatementNotValid($statement, original: $e);
×
232
        }
233
    }
234

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

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

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

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

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

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

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

286
        return $quoted;
1✔
287
    }
288

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

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

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

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

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

© 2025 Coveralls, Inc