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

ICanBoogie / ActiveRecord / 11642781628

02 Nov 2024 12:41PM UTC coverage: 83.671%. Remained the same
11642781628

push

github

olvlvl
Require icanboogie/datetime v3.0

1404 of 1678 relevant lines covered (83.67%)

21.16 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\ActiveRecord\Config\ConnectionDefinition;
6
use InvalidArgumentException;
7
use PDO;
8
use PDOException;
9
use RuntimeException;
10
use Throwable;
11

12
use function explode;
13
use function strtr;
14

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

27
        'mysql' => Driver\MySQLDriver::class,
28
        'sqlite' => Driver\SQLiteDriver::class,
29

30
    ];
31

32
    public readonly string $id;
33

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

43
    /**
44
     * Charset for the connection. Also used to specify the charset while creating tables.
45
     */
46
    public readonly string $charset;
47

48
    /**
49
     * Used to specify the collate while creating tables.
50
     */
51
    public readonly string $collate;
52

53
    /**
54
     * Timezone of the connection.
55
     */
56
    public readonly string $timezone;
57

58
    /**
59
     * Driver name for the connection.
60
     */
61
    public readonly string $driver_name;
62
    public readonly Driver $driver;
63

64
    public readonly PDO $pdo;
65
    public readonly ConnectionTelemetry $telemetry;
66

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

81
        $this->table_name_prefix = $definition->table_name_prefix
82✔
82
            ? $definition->table_name_prefix . '_'
28✔
83
            : '';
54✔
84

85
        [ $this->charset, $this->collate ] = extract_charset_and_collate(
82✔
86
            $definition->charset_and_collate ?? $definition::DEFAULT_CHARSET_AND_COLLATE
82✔
87
        );
82✔
88

89
        $this->timezone = $definition->time_zone;
82✔
90
        $this->driver_name = $this->resolve_driver_name($dsn);
82✔
91
        $this->driver = $this->resolve_driver($this->driver_name);
82✔
92
        $this->telemetry = new ConnectionTelemetry($this->id);
82✔
93

94
        $options = $this->make_options();
82✔
95

96
        $this->pdo = new PDO($dsn, $definition->username, $definition->password, $options);
82✔
97

98
        $this->after_connection();
81✔
99
    }
100

101
    /**
102
     * Resolve the driver name from the DSN string.
103
     */
104
    protected function resolve_driver_name(string $dsn): string
105
    {
106
        return explode(':', $dsn, 2)[0];
82✔
107
    }
108

109
    /**
110
     * Resolves driver class.
111
     *
112
     * @return class-string<Driver>
113
     * @throws DriverNotDefined
114
     *
115
     */
116
    private function resolve_driver_class(string $driver_name): string
117
    {
118
        return self::DRIVERS_MAPPING[$driver_name]
82✔
119
            ?? throw new DriverNotDefined($driver_name); // @phpstan-ignore-line
82✔
120
    }
121

122
    /**
123
     * Resolves a {@link Driver} implementation.
124
     */
125
    private function resolve_driver(string $driver_name): Driver
126
    {
127
        $driver_class = $this->resolve_driver_class($driver_name);
82✔
128

129
        return new $driver_class(
82✔
130
            function () {
82✔
131
                return $this;
51✔
132
            }
82✔
133
        );
82✔
134
    }
135

136
    /**
137
     * Called before the connection.
138
     *
139
     * May alter the options according to the driver.
140
     *
141
     * @return array<PDO::*, mixed>
142
     */
143
    private function make_options(): array
144
    {
145
        if ($this->driver_name != 'mysql') {
82✔
146
            return [];
81✔
147
        }
148

149
        $init_command = 'SET NAMES ' . $this->charset;
1✔
150
        $init_command .= ', time_zone = "' . $this->timezone . '"';
1✔
151

152
        return [
1✔
153

154
            PDO::MYSQL_ATTR_INIT_COMMAND => $init_command,
1✔
155

156
        ];
1✔
157
    }
158

159
    private function after_connection(): void
160
    {
161
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
81✔
162
    }
163

164
    /**
165
     * Overrides the method to resolve the statement before it is prepared, then set its fetch
166
     * mode and connection.
167
     *
168
     * @throws StatementNotValid if the statement cannot be prepared.
169
     */
170
    public function prepare(string $statement): Statement
171
    {
172
        $statement = $this->resolve_statement($statement);
42✔
173

174
        try {
175
            $statement = $this->pdo->prepare($statement);
42✔
176
        } catch (PDOException $e) {
3✔
177
            throw new StatementNotValid($statement, original: $e);
3✔
178
        }
179

180
        return new Statement($statement, $this->telemetry);
40✔
181
    }
182

183
    /**
184
     * Overrides the method to prepare (and resolve) the statement and execute it with
185
     * the specified arguments and options.
186
     *
187
     * @param mixed[] $args
188
     */
189
    public function query(string $statement, array $args = []): Statement
190
    {
191
        $statement = $this->prepare($statement);
41✔
192

193
        $this->telemetry->record_execute_duration(
39✔
194
            $statement,
39✔
195
            static fn() => $statement->execute($args)
39✔
196
        );
39✔
197

198
        return $statement;
38✔
199
    }
200

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

219
        try {
220
            // @phpstan-ignore-next-line
221
            return $this->telemetry->record_execute_duration(
51✔
222
                $statement,
51✔
223
                fn() => $this->pdo->exec($statement)
51✔
224
            );
51✔
225
        } catch (PDOException $e) {
×
226
            throw new StatementNotValid($statement, original: $e);
×
227
        }
228
    }
229

230
    public function get_last_insert_id(): int
231
    {
232
        $id = $this->pdo->lastInsertId();
×
233

234
        if ($id === false) {
×
235
            throw new RuntimeException("Unable to retrieve last inserted ID");
×
236
        }
237

238
        return (int)$id;
×
239
    }
240

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

259
    /**
260
     * Alias for the `beginTransaction()` method.
261
     *
262
     * @see PDO::beginTransaction
263
     */
264
    public function begin(): bool
265
    {
266
        return $this->pdo->beginTransaction();
×
267
    }
268

269
    /**
270
     * @see PDO::quote()
271
     */
272
    public function quote(string $string, int $type = PDO::PARAM_STR): string
273
    {
274
        $quoted = $this->pdo->quote($string, $type);
×
275

276
        // @phpstan-ignore-next-line
277
        if ($quoted === false) {
×
278
            throw new InvalidArgumentException("Unsupported quote type: $type");
×
279
        }
280

281
        return $quoted;
×
282
    }
283

284
    public function quote_identifier(string $identifier): string
285
    {
286
        return $this->driver->quote_identifier($identifier);
1✔
287
    }
288

289
    public function cast_value(mixed $value, string $type = null): mixed
290
    {
291
        return $this->driver->cast_value($value, $type);
×
292
    }
293

294
    /**
295
     * @throws Throwable
296
     */
297
    public function create_table(string $unprefixed_table_name, Schema $schema): void
298
    {
299
        $this->driver->create_table($this->table_name_prefix . $unprefixed_table_name, $schema);
51✔
300
    }
301

302
    /**
303
     * Determines if a table exists in the database.
304
     */
305
    public function table_exists(string $unprefixed_name): bool
306
    {
307
        return $this->driver->table_exists($this->table_name_prefix . $unprefixed_name);
37✔
308
    }
309

310
    public function optimize(): void
311
    {
312
        $this->driver->optimize();
×
313
    }
314
}
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