Skip to content

v1.67.0 - Adhil

Latest

Choose a tag to compare

@MichaelSowah MichaelSowah released this 10 Jul 14:39
593d50d

[1.67.0] - 2026-07-10 — Adhil

Theme: extension seams — four opt-in extension points (independent DB sessions, around-execution
wrappers, write-side row hooks, and blob lifecycle/authorization hooks) that are exact pass-throughs
until an application binds them. No new env vars, no migrations, no default changes.

Added

  • Blob lifecycle + authorization seams on the upload pipeline. Two optional contracts let an
    application observe and gate the framework's blob endpoints without replacing the controller:
    • Glueful\Uploader\Contracts\BlobCreatedHook::onBlobCreated(string $blobUuid, ?string $uploaderUserUuid)
      runs after a blob row is persisted; throwing rejects the upload and the controller compensates —
      it deletes the stored object (checked, not assumed) and hard-deletes the blob row via the new
      BlobRepository::forceDelete(), falling back to a verified status='deleted' quarantine so a failed
      hook can never leave a servable, unattributed blob.
    • Glueful\Uploader\Contracts\BlobAccessPolicy::authorizeAccess(array $blob, BlobAccessContext $ctx)
      runs after the framework's own visibility/auth/signature checks on show/info/delete/signedUrl,
      receiving a BlobAccessContext {action: BlobAction, authenticatedUserUuid, signatureValid} (the
      signature is computed once per request and reused). Returning false yields a 404.
      Both resolve softly from the container (NullBlobCreatedHook/NullBlobAccessPolicy pass-through
      defaults — the framework binds nothing), so existing applications are byte-for-byte unaffected.
  • Deferred thumbnail generation. FileUploader::generateThumbnailFor() generates an upload's
    thumbnail after the fact; the upload controller now defers thumbnail creation until the
    BlobCreatedHook has accepted the blob, so a rejected upload never leaves an orphaned public
    thumbnail. Thumbnail failures after acceptance are logged and degrade to thumb_url: null — a
    committed upload never 500s on a thumbnail error.
  • Independent database sessions. Connection::newPdo() opens a non-pooled PDO using the
    connection's resolved configuration for advisory locks and other session-scoped infrastructure.
  • Around-execution database wrappers. QueryExecutor::addExecutionWrapper() composes generic
    ExecutionWrapperInterface implementations around the actual prepare/execute operation, allowing
    extensions to hold locks or other resources for the full statement boundary. The registry is
    process-level and resettable through clearExecutionWrappers().
  • Process-level write hooks on Connection — the write-side counterpart to the existing table()
    read hooks.
    Connection::addInsertHook(\Closure $hook) registers a
    fn(string $table, array $data): array that runs, in registration order, over the row of every
    QueryBuilder insert(), insertBatch(), and upsert(), so an extension can transparently stamp or
    transform columns (a tenant key, created_by, an encrypted field, …) in one place instead of in every
    repository. clearInsertHooks() resets the process-global registry (mirrors clearTableHooks()) and
    applyInsertHooks() is the seam the query builder calls. Batch inserts are hardened around hook
    output: a hook that produces a non-uniform column set across the batch, or a list-shaped
    (non-associative) row, raises \UnexpectedValueException before SQL generation, and each row's key
    order is normalized to the first row's so a reordered-but-equal column set cannot misalign positional
    binding. When no hook is registered the seam is a pass-through — no behavior change for existing code.