Skip to content
Draft
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"php": ">=8.0",
"phpnomad/auth": "^1.0",
"phpnomad/asset": "^1.0",
"phpnomad/db": "dev-release/2.2 as 2.2.x-dev",
"phpnomad/db": "dev-codex/operation-local-handler-bridge as 2.2.x-dev",
"phpnomad/datastore": "^2.0",
"phpnomad/event": "^1.0",
"phpnomad/email": "^1.0",
Expand Down
10 changes: 5 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions lib/Strategies/CoordinatedQueryStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use PHPNomad\Database\Exceptions\CoordinatedOperationReportingFailedException;
use PHPNomad\Database\Exceptions\UnsupportedCoordinationException;
use PHPNomad\Database\Interfaces\CoordinatedQueryStrategy as CoordinatedQueryStrategyInterface;
use PHPNomad\Database\Interfaces\QueryBuilder as CoreQueryBuilder;
use PHPNomad\Database\Interfaces\QueryStrategy as CoreQueryStrategy;
use PHPNomad\Database\Interfaces\Table;
use PHPNomad\Database\Strategies\OperationQueryStrategy;
use PHPNomad\Datastore\Exceptions\DatastoreErrorException;
Expand All @@ -30,6 +32,8 @@ class CoordinatedQueryStrategy extends QueryStrategy implements CoordinatedQuery
private ?mysqli $connection = null;
private int $connectionId = 0;
private bool $transactionActive = false;
private ?CoreQueryStrategy $activeOperationStrategy = null;
private ?PinnedQueryStrategy $activePinnedStrategy = null;

public function __construct(private ?LoggerStrategy $logger = null)
{
Expand Down Expand Up @@ -95,6 +99,8 @@ function (string $table) use ($schema): array {
}
);
$operationStrategy = new OperationQueryStrategy($delegate, $participants);
$this->activeOperationStrategy = $operationStrategy;
$this->activePinnedStrategy = $delegate;
$result = $operation($operationStrategy);
} catch (Throwable $failure) {
$this->abort($names, 'callback', $failure);
Expand Down Expand Up @@ -122,10 +128,42 @@ function (string $table) use ($schema): array {
if ($operationStrategy instanceof OperationQueryStrategy) {
$operationStrategy->close();
}
$this->activeOperationStrategy = null;
$this->activePinnedStrategy = null;
$this->clearAttempt();
}
}

/**
* Create a fresh builder for the currently active coordinated callback.
*
* The operation provider factory uses this seam before handlers prepare
* clauses. It never exposes the wpdb object or permits a stale callback.
*/
public function createOperationQueryBuilder(CoreQueryStrategy $operation): CoreQueryBuilder
{
$pinned = $this->assertActiveOperation($operation);

return $pinned->createQueryBuilder();
}

/** Create a fresh operation-local clause builder bound to the pinned wpdb. */
public function createOperationClauseBuilder(CoreQueryStrategy $operation): \PHPNomad\Database\Interfaces\ClauseBuilder
{
$pinned = $this->assertActiveOperation($operation);

return $pinned->createClauseBuilder();
}

private function assertActiveOperation(CoreQueryStrategy $operation): PinnedQueryStrategy
{
if ($this->activeOperationStrategy === null || $operation !== $this->activeOperationStrategy || $this->activePinnedStrategy === null) {
throw new UnsupportedCoordinationException('Operation-local WordPress builders are available only inside the active coordinated callback.');
}

return $this->activePinnedStrategy;
}

private function clearAttempt(): void
{
$this->database = null;
Expand Down
2 changes: 2 additions & 0 deletions lib/Strategies/WordPressInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use PHPNomad\Database\Interfaces\HasCharsetProvider;
use PHPNomad\Database\Interfaces\HasCollateProvider;
use PHPNomad\Database\Interfaces\HasGlobalDatabasePrefix;
use PHPNomad\Database\Interfaces\OperationDatabaseProviderFactory as CoreOperationDatabaseProviderFactory;
use PHPNomad\Database\Interfaces\QueryBuilder as CoreQueryBuilder;
use PHPNomad\Database\Interfaces\QueryStrategy as CoreQueryStrategy;
use PHPNomad\Database\Interfaces\TableCreateStrategy as CoreTableCreateStrategyAlias;
Expand Down Expand Up @@ -99,6 +100,7 @@ public function getClassDefinitions(): array
// one resource-owning instance, while inherited CRUD remains the
// ordinary WordPress implementation.
CoordinatedQueryStrategy::class => [CoreQueryStrategy::class, CoreCoordinatedQueryStrategy::class],
WordPressOperationDatabaseProviderFactory::class => CoreOperationDatabaseProviderFactory::class,
DefaultCacheTtlProvider::class => HasDefaultTtl::class,
TableCreateStrategy::class => CoreTableCreateStrategyAlias::class,
TableUpdateStrategy::class => CoreTableUpdateStrategy::class,
Expand Down
67 changes: 67 additions & 0 deletions lib/Strategies/WordPressOperationDatabaseProviderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace PHPNomad\Integrations\WordPress\Strategies;

use InvalidArgumentException;
use PHPNomad\Database\Interfaces\CoordinatedQueryStrategy as CoreCoordinatedQueryStrategy;
use PHPNomad\Database\Interfaces\DatabaseHandler;
use PHPNomad\Database\Interfaces\OperationDatabaseProviderFactory;
use PHPNomad\Database\Interfaces\QueryStrategy;
use PHPNomad\Database\Providers\DatabaseServiceProvider;
use PHPNomad\Database\Services\OperationCacheableService;
use PHPNomad\Database\Services\OperationEventStrategy;

/**
* Creates database-handler providers for one active core-wpdb operation.
*
* Builder construction is delegated to the coordinator while its pinned
* mysqli session is active. The factory never clones a global builder and it
* cannot be used as a general provider replacement outside a callback.
*/
final class WordPressOperationDatabaseProviderFactory implements OperationDatabaseProviderFactory
{
private CoordinatedQueryStrategy $coordinator;

public function __construct(CoreCoordinatedQueryStrategy $coordinator)
{
if (!$coordinator instanceof CoordinatedQueryStrategy) {
throw new InvalidArgumentException('The WordPress operation factory requires the WordPress coordinator.');
}
$this->coordinator = $coordinator;
}

public function create(
DatabaseHandler $handler,
QueryStrategy $queryStrategy,
OperationCacheableService $cache,
OperationEventStrategy $events
): DatabaseServiceProvider {
$source = $handler->getDatabaseServiceProvider();
// Ask the coordinator first. This refuses stale or out-of-callback
// factory use before inspecting or cloning any provider state.
$queryBuilder = $this->coordinator->createOperationQueryBuilder($queryStrategy);
$clauseBuilder = $this->coordinator->createOperationClauseBuilder($queryStrategy);
if ($source->queryStrategy !== $this->coordinator) {
throw new InvalidArgumentException('The WordPress handler must use the coordinating query strategy.');
}

$provider = $source->forOperation(
$queryStrategy,
$queryBuilder,
$clauseBuilder,
$cache,
$events
);

if ($provider->queryStrategy !== $queryStrategy
|| $provider->cacheableService !== $cache
|| $provider->eventStrategy !== $events
|| $provider->loggerStrategy !== $source->loggerStrategy
|| $provider->queryBuilder === $source->queryBuilder
|| $provider->clauseBuilder === $source->clauseBuilder) {
throw new InvalidArgumentException('The WordPress operation provider did not preserve operation resources.');
}

return $provider;
}
}
Loading
Loading