Skip to content

Commit ca8d0ab

Browse files
committed
0.3 alpha
1 parent 4685962 commit ca8d0ab

32 files changed

Lines changed: 2199 additions & 1 deletion

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Connection failures, query failures, transaction failures, invalid connection na
3535

3636
## Documentation
3737

38+
- [Documentation index](docs/index.md)
3839
- [Usage](docs/usage.md)
3940
- [Testing](TESTING.md)
4041
- [Contributing](CONTRIBUTING.md)

docs/connections.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Connections
2+
3+
Connections are named definitions for database drivers. A connection can store a driver class plus constructor options, or it can store an already-created driver instance.
4+
5+
## Connection Names
6+
7+
Connection names are normalized by `ConnectionDefinition::normalizeName()`:
8+
9+
- leading and trailing whitespace is trimmed;
10+
- names are lowercased;
11+
- empty names are rejected;
12+
- null bytes are rejected.
13+
14+
```php
15+
ConnectionDefinition::normalizeName(' Main '); // "main"
16+
```
17+
18+
The default connection name is `_default_`.
19+
20+
## ConnectionDefinition
21+
22+
```php
23+
use CommonPHP\Database\ConnectionDefinition;
24+
25+
$definition = new ConnectionDefinition('main', AppDatabaseDriver::class, [
26+
'host' => '127.0.0.1',
27+
], default: true);
28+
```
29+
30+
The definition exposes:
31+
32+
- `name()`
33+
- `driverClass()`
34+
- `options()`
35+
- `hasDriverInstance()`
36+
- `driverInstance()`
37+
- `isDefault()`
38+
39+
## Config Arrays
40+
41+
```php
42+
$definition = ConnectionDefinition::fromConfig([
43+
'name' => 'main',
44+
'driver' => AppDatabaseDriver::class,
45+
'host' => '127.0.0.1',
46+
'options' => [
47+
'dbname' => 'app',
48+
],
49+
'default' => true,
50+
]);
51+
```
52+
53+
Inline keys other than `name`, `driver`, `default`, and `options` become driver options. Explicit `options` override inline options with the same key.
54+
55+
## ConnectionRegistry
56+
57+
`ConnectionRegistry` stores definitions and lazily creates drivers.
58+
59+
```php
60+
use CommonPHP\Database\ConnectionRegistry;
61+
62+
$registry = new ConnectionRegistry();
63+
$registry->add('main', AppDatabaseDriver::class, $options, default: true);
64+
65+
$driver = $registry->get('main');
66+
```
67+
68+
Repeated calls to `get()` return the same driver instance.
69+
70+
## Registry Config
71+
72+
Single connection:
73+
74+
```php
75+
$registry = ConnectionRegistry::fromConfig([
76+
'driver' => AppDatabaseDriver::class,
77+
'host' => '127.0.0.1',
78+
]);
79+
```
80+
81+
Multiple connections:
82+
83+
```php
84+
$registry = ConnectionRegistry::fromConfig([
85+
'connections' => [
86+
'main' => [
87+
'driver' => AppDatabaseDriver::class,
88+
'host' => '127.0.0.1',
89+
],
90+
'reporting' => [
91+
'driver' => AppDatabaseDriver::class,
92+
'host' => '10.0.0.20',
93+
'default' => true,
94+
],
95+
],
96+
]);
97+
```
98+
99+
## Registry Operations
100+
101+
The registry supports:
102+
103+
- `register(ConnectionDefinition $definition)`
104+
- `add(string $name, string|DatabaseDriverInterface $driver, array $options = [])`
105+
- `remove(string $name)`
106+
- `has(string $name)`
107+
- `definition(?string $name = null)`
108+
- `get(?string $name = null)`
109+
- `isResolved(string $name)`
110+
- `setDefault(string $name)`
111+
- `defaultName()`
112+
- `defaultDefinition()`
113+
- `all()`
114+
- `resolved()`
115+
- `clear()`
116+
117+
It is countable and iterable over connection definitions.

docs/drivers.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Drivers
2+
3+
Database drivers adapt the core database contracts to a real database engine or client library.
4+
5+
The core package does not include a concrete database engine driver.
6+
7+
## Contract
8+
9+
Drivers implement `DatabaseDriverInterface`.
10+
11+
```php
12+
namespace CommonPHP\Database\Contracts;
13+
14+
interface DatabaseDriverInterface extends DriverInterface
15+
{
16+
public function count(string $query, array $parameters = []): int;
17+
public function execute(string $query, array $parameters = []): int|bool;
18+
public function fetchScalar(string $query, array $parameters = [], mixed $default = null): mixed;
19+
public function fetchOne(string $query, array $parameters = []): array|false;
20+
public function fetchAll(string $query, array $parameters = [], FetchMode $fetchMode = FetchMode::FETCH_ASSOC): array;
21+
public function transaction(callable $callback): mixed;
22+
public function beginTransaction(): void;
23+
public function commit(): void;
24+
public function rollBack(): void;
25+
public function lastInsertId(): string|false;
26+
public function ping(): bool;
27+
}
28+
```
29+
30+
## AbstractDatabaseDriver
31+
32+
`AbstractDatabaseDriver` provides useful defaults:
33+
34+
- `getName()` returns `static::class`;
35+
- `prepare()` creates an executable `Query`;
36+
- `count()` counts `fetchAll()` results;
37+
- `fetchScalar()` reads the first value from `fetchOne()`;
38+
- `transaction()` delegates to `Transaction::run()`;
39+
- protected `parameterType()` detects named, positional, or empty parameters;
40+
- protected `fetchModeValue()` returns the enum value.
41+
42+
Drivers can override defaults when the underlying engine has a more efficient implementation.
43+
44+
## Minimal Driver Shape
45+
46+
```php
47+
use CommonPHP\Database\Contracts\AbstractDatabaseDriver;
48+
use CommonPHP\Database\Enums\FetchMode;
49+
50+
final class ExampleDatabaseDriver extends AbstractDatabaseDriver
51+
{
52+
public function __construct(
53+
private readonly ExampleClient $client,
54+
) {
55+
}
56+
57+
public function execute(string $query, array $parameters = []): int|bool
58+
{
59+
return $this->client->execute($query, $parameters);
60+
}
61+
62+
public function fetchOne(string $query, array $parameters = []): array|false
63+
{
64+
return $this->client->fetchOne($query, $parameters);
65+
}
66+
67+
public function fetchAll(
68+
string $query,
69+
array $parameters = [],
70+
FetchMode $fetchMode = FetchMode::FETCH_ASSOC,
71+
): array {
72+
return $this->client->fetchAll($query, $parameters, $fetchMode->value);
73+
}
74+
75+
public function beginTransaction(): void
76+
{
77+
$this->client->beginTransaction();
78+
}
79+
80+
public function commit(): void
81+
{
82+
$this->client->commit();
83+
}
84+
85+
public function rollBack(): void
86+
{
87+
$this->client->rollBack();
88+
}
89+
90+
public function lastInsertId(): string|false
91+
{
92+
return $this->client->lastInsertId();
93+
}
94+
95+
public function ping(): bool
96+
{
97+
return $this->client->ping();
98+
}
99+
}
100+
```
101+
102+
## Driver Responsibilities
103+
104+
Drivers should own:
105+
106+
- connection strings and client setup;
107+
- parameter binding;
108+
- engine-specific fetch mode mapping;
109+
- transaction calls;
110+
- driver-specific exception conversion;
111+
- any query compilation that belongs to that engine.
112+
113+
Drivers should not own:
114+
115+
- application runtime bootstrapping;
116+
- HTTP request parsing;
117+
- validation;
118+
- authentication;
119+
- session lifecycle;
120+
- UI rendering.

docs/error-handling.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Error Handling
2+
3+
Database has one base exception type: `DatabaseException`.
4+
5+
All package-specific exceptions extend it.
6+
7+
## Connection Errors
8+
9+
Connection definition, registry, and creation failures throw connection exceptions:
10+
11+
- `InvalidConnectionNameException`
12+
- `ConnectionExistsException`
13+
- `ConnectionNotFoundException`
14+
- `ConnectionException`
15+
- `DatabaseDriverException`
16+
17+
Examples:
18+
19+
```php
20+
$database->with('missing'); // ConnectionNotFoundException
21+
$database->connect('', AppDatabaseDriver::class); // InvalidConnectionNameException
22+
```
23+
24+
## Query Errors
25+
26+
Manager query methods wrap unexpected driver runtime failures in `QueryException`.
27+
28+
```php
29+
try {
30+
$database->execute('bad sql');
31+
} catch (QueryException $exception) {
32+
$previous = $exception->getPrevious();
33+
}
34+
```
35+
36+
If a driver already throws a `DatabaseException`, the manager does not wrap it again.
37+
38+
## Driver Operation Errors
39+
40+
Non-query driver operations such as `ping()` and `lastInsertId()` wrap unexpected runtime failures in `DatabaseDriverException`.
41+
42+
## Transaction Errors
43+
44+
Transaction failures throw `TransactionException`.
45+
46+
Runtime failures from transaction callbacks are wrapped after rollback. Existing database exceptions are rolled back and rethrown.
47+
48+
```php
49+
try {
50+
$database->transaction($callback);
51+
} catch (TransactionException $exception) {
52+
// Transaction begin, commit, rollback, or callback failed.
53+
}
54+
```
55+
56+
## Recommended Integration
57+
58+
Application layers should catch database exceptions at boundaries that can add context:
59+
60+
- API packages can translate them into problem details;
61+
- console packages can print a concise error and return a failing exit code;
62+
- logging packages can record the connection name, action, and query safely;
63+
- UI packages should avoid exposing raw SQL errors to users.
64+
65+
Keep database exceptions technical. User-facing wording belongs at the boundary.

docs/events-and-profiling.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Events And Profiling
2+
3+
Database uses Runtime's `EventEmitterTrait` for lightweight package events.
4+
5+
## ConnectedEvent
6+
7+
`ConnectedEvent` is emitted when a lazily-defined connection is first resolved.
8+
9+
```php
10+
use CommonPHP\Database\Events\ConnectedEvent;
11+
12+
$database->subscribe(ConnectedEvent::class, function (ConnectedEvent $event): void {
13+
$event->connectionName;
14+
$event->driver;
15+
$event->definition;
16+
});
17+
```
18+
19+
Connections registered with an already-created driver instance are already resolved, so they do not emit the lazy connection event on first `with()`.
20+
21+
## QueryExecutedEvent
22+
23+
`QueryExecutedEvent` is emitted only when profiling is enabled.
24+
25+
```php
26+
use CommonPHP\Database\Events\QueryExecutedEvent;
27+
28+
$database->enableProfiling();
29+
30+
$database->subscribe(QueryExecutedEvent::class, function (QueryExecutedEvent $event): void {
31+
$event->action;
32+
$event->query;
33+
$event->parameters;
34+
$event->connectionName;
35+
$event->driver;
36+
$event->duration;
37+
$event->errors;
38+
});
39+
```
40+
41+
The event exposes `succeeded()` and `failed()` helpers.
42+
43+
## Profiling
44+
45+
Profiling is disabled by default.
46+
47+
```php
48+
$database->enableProfiling();
49+
$database->disableProfiling();
50+
$database->isProfiling();
51+
```
52+
53+
When profiling is enabled, the manager logs successful queries at debug level and failed queries at error level using the injected PSR-3 logger.
54+
55+
```php
56+
$database = new DatabaseManager(logger: $logger);
57+
$database->enableProfiling();
58+
```
59+
60+
## Manual Profiling
61+
62+
Driver packages can use `profileQuery()` if they execute work outside the manager's direct query helpers.
63+
64+
```php
65+
$database->profileQuery(
66+
action: 'bulk import',
67+
query: 'copy users from stdin',
68+
parameters: [],
69+
connection: $driver,
70+
duration: 0.25,
71+
errors: false,
72+
connectionName: 'main',
73+
);
74+
```
75+
76+
When profiling is disabled, `profileQuery()` returns without logging or emitting events.

0 commit comments

Comments
 (0)