Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ jobs:
sleep 2
done

- name: Wait for MinIO bucket initialization
run: |
echo "Waiting for MinIO bucket initialization..."
docker wait bowphp_minio_init || true

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v4
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,21 @@ services:
interval: 10s
timeout: 5s
retries: 5
minio-init:
container_name: bowphp_minio_init
image: minio/mc
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set local http://minio:9000 minioadmin minioadmin;
mc mb --ignore-existing local/tests;
mc anonymous set public local/tests;
exit 0;
"
networks:
- bowphp_network
zookeeper:
container_name: bowphp_zookeeper
image: confluentinc/cp-zookeeper:7.5.0
Expand Down
18 changes: 16 additions & 2 deletions src/Application/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public function run(): bool

// Error management
if ($resolved) {
$this->send($response);
$this->sendResponse($response);
return true;
}

Expand All @@ -192,14 +192,28 @@ public function run(): bool
return false;
}

/**
* Launch the application and send the answer to the customer.
*
* Public alias of {@see Application::run()}.
*
* @return bool
* @throws ReflectionException
* @throws RouterException
*/
public function send(): bool
{
return $this->run();
}

/**
* Send the answer to the customer
*
* @param mixed $response
* @param int $code
* @return void
*/
private function send(mixed $response, int $code = 200): void
private function sendResponse(mixed $response, int $code = 200): void
{
if ($response instanceof ResponseInterface) {
$response->sendContent();
Expand Down
152 changes: 138 additions & 14 deletions src/Database/Connection/AbstractConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,157 @@ abstract class AbstractConnection
protected int $fetch = PDO::FETCH_OBJ;

/**
* The PDO instance
* The write (primary) PDO instance
*
* @var PDO
* @var ?PDO
*/
protected PDO $pdo;
protected ?PDO $write_pdo = null;

/**
* Create an instance of the PDO
* The read (replica) PDO instance
*
* @var ?PDO
*/
protected ?PDO $read_pdo = null;

/**
* The configuration used to build the write connection
*
* @var array
*/
protected array $write_config = [];

/**
* The configuration used to build the read connection,
* or null when the connection is not split (reads use write).
*
* @var ?array
*/
protected ?array $read_config = null;

/**
* AbstractConnection constructor.
*
* Splits the connection configuration into a write (primary)
* configuration and an optional read (replica) configuration.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->config = $config;

$this->write_config = $config;
unset($this->write_config['read']);

if (isset($config['read']) && is_array($config['read'])) {
$this->read_config = array_merge($this->write_config, $config['read']);
} else {
$this->read_config = null;
}

// Validate eagerly so misconfiguration fails fast, while the
// connection itself is still established lazily on first use.
$this->validateConfig($this->write_config);

if ($this->read_config !== null) {
$this->validateConfig($this->read_config);
}
}

/**
* Validate the connection configuration.
*
* @param array $config
* @return void
*/
abstract protected function validateConfig(array $config): void;

/**
* Build a PDO instance from the given configuration.
*
* @param array $config
* @return PDO
*/
abstract protected function makePdo(array $config): PDO;

/**
* Build (eagerly) the write connection.
*
* Kept for backward compatibility with callers that expect to
* (re)establish the connection explicitly.
*
* @return void
*/
abstract public function connection(): void;
public function connection(): void
{
$this->write_pdo = $this->makePdo($this->write_config);
}

/**
* Retrieves the connection
* Retrieves the connection (the write/primary connection)
*
* @return PDO
*/
public function getConnection(): PDO
{
return $this->pdo;
return $this->getWriteConnection();
}

/**
* Retrieves the write (primary) connection, building it lazily
*
* @return PDO
*/
public function getWriteConnection(): PDO
{
if ($this->write_pdo === null) {
$this->write_pdo = $this->makePdo($this->write_config);
}

return $this->write_pdo;
}

/**
* Set the connection
* Retrieves the read (replica) connection, building it lazily.
*
* Falls back to the write connection when the connection is not split.
*
* @return PDO
*/
public function getReadConnection(): PDO
{
if ($this->read_config === null) {
return $this->getWriteConnection();
}

if ($this->read_pdo === null) {
$this->read_pdo = $this->makePdo($this->read_config);
}

return $this->read_pdo;
}

/**
* Whether the write connection has already been established.
*
* Used to inspect transaction state without forcing a connection open.
*
* @return bool
*/
public function hasWriteConnection(): bool
{
return $this->write_pdo instanceof PDO;
}

/**
* Set the connection (the write/primary connection)
*
* @param PDO $pdo
*/
public function setConnection(PDO $pdo): void
{
$this->pdo = $pdo;
$this->write_pdo = $pdo;
}

/**
Expand All @@ -84,10 +204,14 @@ public function setFetchMode(int $fetch): void
{
$this->fetch = $fetch;

$this->pdo->setAttribute(
PDO::ATTR_DEFAULT_FETCH_MODE,
$fetch
);
foreach ([$this->write_pdo, $this->read_pdo] as $pdo) {
if ($pdo instanceof PDO) {
$pdo->setAttribute(
PDO::ATTR_DEFAULT_FETCH_MODE,
$fetch
);
}
}
}

/**
Expand Down Expand Up @@ -137,7 +261,7 @@ public function getCollation(): string
*/
public function getPdoDriver(): string
{
return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
return $this->getConnection()->getAttribute(PDO::ATTR_DRIVER_NAME);
}

/**
Expand Down
46 changes: 22 additions & 24 deletions src/Database/Connection/Adapters/MysqlAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,51 @@ class MysqlAdapter extends AbstractConnection
protected ?string $name = 'mysql';

/**
* MysqlAdapter constructor.
* Validate the connection configuration.
*
* @param array $config
* @param array $config
* @return void
*/
public function __construct(array $config)
protected function validateConfig(array $config): void
{
$this->config = $config;

$this->connection();
// Check the existence of database definition
if (!isset($config['database'])) {
throw new InvalidArgumentException("The database is not defined");
}
}

/**
* Make connexion
* Build a PDO instance from the given configuration.
*
* @return void
* @param array $config
* @return PDO
*/
public function connection(): void
protected function makePdo(array $config): PDO
{
// Build of the mysql dsn
if (isset($this->config['socket']) && !empty($this->config['socket'])) {
$hostname = $this->config['socket'];
if (isset($config['socket']) && !empty($config['socket'])) {
$hostname = $config['socket'];
$port = '';
} else {
$hostname = $this->config['hostname'] ?? null;
$port = (string)($this->config['port'] ?? self::PORT);
}

// Check the existence of database definition
if (!isset($this->config['database'])) {
throw new InvalidArgumentException("The database is not defined");
$hostname = $config['hostname'] ?? null;
$port = (string)($config['port'] ?? self::PORT);
}

// Formatting connection parameters
$dsn = sprintf("mysql:host=%s;port=%s;dbname=%s", $hostname, $port, $this->config['database']);
$dsn = sprintf("mysql:host=%s;port=%s;dbname=%s", $hostname, $port, $config['database']);

$username = $this->config["username"];
$password = $this->config["password"];
$username = $config["username"];
$password = $config["password"];

// Configuration the PDO attributes that we want to set
$options = [
PDO::ATTR_DEFAULT_FETCH_MODE => $this->config['fetch'] ?? $this->fetch,
PDO::ATTR_DEFAULT_FETCH_MODE => $config['fetch'] ?? $this->fetch,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES " . Str::upper($this->config["charset"]),
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES " . Str::upper($config["charset"]),
PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING
];

// Build the PDO connection
$this->pdo = new PDO($dsn, $username, $password, $options);
return new PDO($dsn, $username, $password, $options);
}
}
Loading
Loading