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

ICanBoogie / ActiveRecord / 11645431508

02 Nov 2024 08:01PM UTC coverage: 84.054%. Remained the same
11645431508

push

github

olvlvl
Add ModelAccessor

13 of 13 new or added lines in 2 files covered. (100.0%)

11 existing lines in 1 file now uncovered.

1381 of 1643 relevant lines covered (84.05%)

21.75 hits per line

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

81.16
/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;
81✔
85
        $dsn = $definition->dsn;
81✔
86

87
        $this->table_name_prefix = $definition->table_name_prefix
81✔
88
            ? $definition->table_name_prefix . '_'
28✔
89
            : '';
53✔
90

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

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

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

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

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

135
        return new $driver_class(
81✔
136
            function () {
81✔
137
                return $this;
51✔
138
            }
81✔
139
        );
81✔
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') {
81✔
152
            return [];
80✔
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);
80✔
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);
42✔
179

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

186
        return new Statement($statement, $this->telemetry);
40✔
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);
41✔
198

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

204
        return $statement;
38✔
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);
51✔
224

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

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

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

UNCOV
244
        return (int)$id;
×
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, [
55✔
259
            '{prefix}' => $this->table_name_prefix,
55✔
260
            '{charset}' => $this->charset,
55✔
261
            '{collate}' => $this->collate,
55✔
262
        ]);
55✔
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
    {
UNCOV
280
        $quoted = $this->pdo->quote($string, $type);
×
281

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

UNCOV
287
        return $quoted;
×
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);
51✔
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);
37✔
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

© 2025 Coveralls, Inc