Skip to content

Releases: gulaandrij/google-sheets-bundle

v1.3.0 — SheetsServiceInterface, SheetsWriteEvent, profiler/test-fake fixes

09 Jun 08:23

Choose a tag to compare

Interface + events release. Consumer code can now type-hint against SheetsServiceInterface (the bundle wires the autowire alias next to the existing concrete one), and subscribers can listen for SheetsWriteEvent to react to writes. Also bundles a round of correctness fixes on the profiler tracer and the in-memory fake.

Added

  • SheetsServiceInterface captures the consumer-facing read/write/discovery/metadata contract. SheetsService, TraceableSheetsService, CachedSheetsService, and InMemorySheetsService all implement it. The bundle registers SheetsServiceInterface $<name> autowire aliases alongside the existing SheetsService $<name> aliases, and SheetsRegistry::service() now returns the interface. Escape hatches (client(), driveService()) intentionally stay on the concrete SheetsService only.
  • SheetsWriteEvent (PSR-14) dispatched after every successful append, update, clear, addSheet, and deleteSheet. Subscribe with #[AsEventListener(event: SheetsWriteEvent::class)] for cache invalidation, audit logging, or sync workflows. The event exposes operation, spreadsheetId, sheetName, range, and rowCount. Read events are deliberately not dispatched — they would fire on every row of streaming reads.
  • Optional ?EventDispatcherInterface constructor argument on SheetsService (last position), TraceableSheetsService, CachedSheetsService, and InMemorySheetsService. Bundle wires it via service(EventDispatcherInterface::class)->nullOnInvalid() so existing apps without symfony/event-dispatcher still boot.

Fixed

  • TraceableSheetsService::readAssocIterable no longer drops the trace on early break. The previous try/catch only recorded on full exhaustion or exception — a caller doing foreach (...) { if (...) break; } produced zero profiler entries. Switched to try/finally so the trace fires unconditionally.
  • TraceableSheetsService::findSheetNameById is now wrapped. The parent's comment anticipated this override ("Call the private helper directly so a TraceableSheetsService subclass doesn't double-record"), but the wrapper was missing — findSheetNameById hit the parent silently with zero trace recorded.
  • captureOrigin backtrace depth raised from 12 to 25 frames. In a real Symfony app, bundle-internal frames + the kernel + middleware can consume the first dozen slots, so captureOrigin silently returned null for the call-site in most production paths.
  • InMemorySheetsService::append() now enforces the homogeneous-row shape check that the real SheetsService runs. Tests using the fake no longer silently accept mixed positional/assoc rows or assoc rows with divergent keys — they throw MixedRowShapeException matching production.
  • PeekCommand guards range(0, -1) so an empty leading row no longer emits a phantom A column header. PHP returns [0] for range(0, -1), which produced a misleading single-column table for empty range fetches.

Changed

  • TraceableSheetsService::trace() collapses its duplicated success/catch bodies into a single finally that calls record() once with $error ?? null. Behaviour identical, half the code.
  • SheetsCollector::getTotalDurationMs() is now lazy — it sums calls[*].duration_ms on demand instead of maintaining a running scalar. The running scalar was vulnerable to type drift after Symfony's profiler serialisation, which the previous defensive is_float / is_int cast hinted at.
  • SheetsRegistry::service() and metadata() share a single notFoundMessage() helper instead of repeating the sprintf template.

Full changelog: https://github.com/gulaandrij/google-sheets-bundle/blob/v1.3.0/CHANGELOG.md

v1.2.1 — review fixes

07 Jun 20:05

Choose a tag to compare

Review-driven follow-up to 1.2.0. Ten findings addressed — three user-visible bugs, the rest cleanup and UX polish.

User-visible fixes

  • CachedSheetsService now supports `readEntities()` — the previous wiring dropped the serializer, breaking typed reads on any cached binding.
  • CachedSheetsService now invalidates the cache on writes — `append()` / `update()` / `clear()` / `addSheet()` / `deleteSheet()` drop every key this instance populated, so the next read returns fresh data.
  • TraceableSheetsService now records `readEntities` and `readAssocIterable` — both methods were invisible in the profiler before.

Cleanup + UX

  • `InMemorySheetsService` accepts an explicit `sheetIds` map for realistic stable-ID lookups in tests.
  • `google-sheets:doctor --strict` flag — missing bound sheets are a warning by default, failure under `--strict`.
  • `google-sheets:peek --with-header` flag — `--range` no longer auto-strips the first row.
  • Cache pool typo (`cache: { pool: missing }`) fails at boot with a bundle-scoped error.
  • `captureOrigin` filter narrowed so bundle tests can verify the captured frame.
  • `buildColumnMap` memoised per class.

105 tests / 290 assertions, PHPStan max + strict + deprecation clean.

No public API changes. Safe to upgrade from 1.2.0 without any code changes — and recommended if you use the cache feature.

```bash
composer update gulaandrij/google-sheets-bundle:^1.2
```

Full CHANGELOG.

v1.2.0 — DX bundle

07 Jun 19:36

Choose a tag to compare

DX-focused release. Seven new capabilities, no breaking changes from 1.1.x.

What's new

Introspection commands

  • google-sheets:list — every configured binding
  • google-sheets:tabs <binding> — tabs in a bound spreadsheet
  • google-sheets:peek <binding> [sheet] [--rows --range] — preview rows as a table
  • google-sheets:doctor — probe each binding, non-zero exit on failure (deploy-pipeline friendly)

Typed reads

`SheetsService::readEntities(MyDto::class)` denormalizes rows into typed DTOs via the Symfony Serializer. Use the new `#[SheetColumn('Header Name')]` attribute on DTO properties for friendly-name mapping.

Streaming

`SheetsService::readAssocIterable(int $batchSize = 500): \Generator` walks giant sheets in column-letter-paged batches — no full-sheet load.

Per-binding caching

Add `cache: {ttl: , pool: cache.app}` to a spreadsheet entry and reads memoise through Symfony cache contracts. Tracing wins over caching in dev so the profiler shows real Sheets calls.

Profiler enhancements

  • Per-binding groups in the panel with sub-totals
  • Caller-origin column links each call back to the file that triggered it

Test fake

`Gulaandrij\GoogleSheetsBundle\Test\InMemorySheetsService` — drop-in for SheetsService backed by an in-memory map. Skip Google entirely in functional tests.

Other

`SheetsRegistry` service exposes the bindings catalog programmatically. `SheetsService` is no longer final (subclasses are bundle internals).

Install / upgrade

```bash
composer require gulaandrij/google-sheets-bundle:^1.2
```

Full CHANGELOG.

v1.1.1 — proper profiler icon

07 Jun 19:00

Choose a tag to compare

Replaces the WebProfiler email-icon placeholder with a Google-Sheets-style SVG icon for the toolbar item and menu entry.

v1.1.0 — Symfony Web Profiler integration

07 Jun 18:54

Choose a tag to compare

Adds Symfony Web Profiler integration.

What's new

When kernel.debug is true the bundle now:

  • Registers a SheetsCollector (extending AbstractDataCollector).
  • Wraps every named SheetsService with TraceableSheetsService — a subclass that records each call (service binding, method, spreadsheet ID, sheet, range, duration in ms, error if any) into the collector.
  • Renders a Web Profiler toolbar item with total call count + total time, and a panel listing every call with status badges.

In production (kernel.debug = false) neither the decorator nor the collector are wired — zero overhead.

Behaviour notes

  • SheetsService is no longer final so the traceable subclass can extend it.
  • Internal cross-method calls (readAssocreadRaw, listSheets / findSheetNameByIdlistSheetsWithIds) now go through private helpers so the trace fires exactly once per public call.

No public API changes; safe to upgrade from 1.0.x without any code changes.

Install / upgrade

composer require gulaandrij/google-sheets-bundle:^1.1

Full CHANGELOG.

v1.0.0 — first stable release

07 Jun 10:29

Choose a tag to compare

First stable release of gulaandrij/google-sheets-bundle.

Highlights

  • Named spreadsheets under google_sheets.spreadsheets.<name> — declare each spreadsheet (and optionally its default tab) once in config, inject a bound SheetsService by variable name (SheetsService $allocators).
  • Per-call fresh SheetsClient so sticky selectors (range, majorDimension, valueRenderOption, dateTimeRenderOption) never leak between consumers.
  • Full underlying-library coverage: readRaw, readAssoc, firstRow, listSheets/WithIds, findSheetNameById, append, update, clear, addSheet, deleteSheet, spreadsheetProperties, sheetProperties, plus SheetsClientFactory::listSpreadsheets() for the global Drive query.
  • Strict input validation at config-tree (invalid spreadsheet names, empty scopes, missing default_spreadsheet) and at call-site (DuplicateHeaderException, InvalidHeaderException, MixedRowShapeException, MissingCredentialsException, MissingSheetNameException).

Compatibility

  • PHP 8.3+ (8.4 recommended)
  • Symfony 6.4 / 7.x / 8.x
  • revolution/laravel-google-sheets ^7.2
  • google/apiclient ^2.16

Installation

composer require gulaandrij/google-sheets-bundle

See README for configuration and the docs for recipes and architecture.

Full CHANGELOG.