Releases: gulaandrij/google-sheets-bundle
v1.3.0 — SheetsServiceInterface, SheetsWriteEvent, profiler/test-fake fixes
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
SheetsServiceInterfacecaptures the consumer-facing read/write/discovery/metadata contract.SheetsService,TraceableSheetsService,CachedSheetsService, andInMemorySheetsServiceall implement it. The bundle registersSheetsServiceInterface $<name>autowire aliases alongside the existingSheetsService $<name>aliases, andSheetsRegistry::service()now returns the interface. Escape hatches (client(),driveService()) intentionally stay on the concreteSheetsServiceonly.SheetsWriteEvent(PSR-14) dispatched after every successfulappend,update,clear,addSheet, anddeleteSheet. Subscribe with#[AsEventListener(event: SheetsWriteEvent::class)]for cache invalidation, audit logging, or sync workflows. The event exposesoperation,spreadsheetId,sheetName,range, androwCount. Read events are deliberately not dispatched — they would fire on every row of streaming reads.- Optional
?EventDispatcherInterfaceconstructor argument onSheetsService(last position),TraceableSheetsService,CachedSheetsService, andInMemorySheetsService. Bundle wires it viaservice(EventDispatcherInterface::class)->nullOnInvalid()so existing apps withoutsymfony/event-dispatcherstill boot.
Fixed
TraceableSheetsService::readAssocIterableno longer drops the trace on earlybreak. The previous try/catch only recorded on full exhaustion or exception — a caller doingforeach (...) { if (...) break; }produced zero profiler entries. Switched to try/finally so the trace fires unconditionally.TraceableSheetsService::findSheetNameByIdis 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 —findSheetNameByIdhit the parent silently with zero trace recorded.captureOriginbacktrace depth raised from 12 to 25 frames. In a real Symfony app, bundle-internal frames + the kernel + middleware can consume the first dozen slots, socaptureOriginsilently returnednullfor the call-site in most production paths.InMemorySheetsService::append()now enforces the homogeneous-row shape check that the realSheetsServiceruns. Tests using the fake no longer silently accept mixed positional/assoc rows or assoc rows with divergent keys — they throwMixedRowShapeExceptionmatching production.PeekCommandguardsrange(0, -1)so an empty leading row no longer emits a phantomAcolumn header. PHP returns[0]forrange(0, -1), which produced a misleading single-column table for empty range fetches.
Changed
TraceableSheetsService::trace()collapses its duplicated success/catch bodies into a singlefinallythat callsrecord()once with$error ?? null. Behaviour identical, half the code.SheetsCollector::getTotalDurationMs()is now lazy — it sumscalls[*].duration_mson demand instead of maintaining a running scalar. The running scalar was vulnerable to type drift after Symfony's profiler serialisation, which the previous defensiveis_float/is_intcast hinted at.SheetsRegistry::service()andmetadata()share a singlenotFoundMessage()helper instead of repeating thesprintftemplate.
Full changelog: https://github.com/gulaandrij/google-sheets-bundle/blob/v1.3.0/CHANGELOG.md
v1.2.1 — review fixes
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
DX-focused release. Seven new capabilities, no breaking changes from 1.1.x.
What's new
Introspection commands
google-sheets:list— every configured bindinggoogle-sheets:tabs <binding>— tabs in a bound spreadsheetgoogle-sheets:peek <binding> [sheet] [--rows --range]— preview rows as a tablegoogle-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
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
Adds Symfony Web Profiler integration.
What's new
When kernel.debug is true the bundle now:
- Registers a
SheetsCollector(extendingAbstractDataCollector). - Wraps every named
SheetsServicewithTraceableSheetsService— 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
SheetsServiceis no longerfinalso the traceable subclass can extend it.- Internal cross-method calls (
readAssoc→readRaw,listSheets/findSheetNameById→listSheetsWithIds) 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.1Full CHANGELOG.
v1.0.0 — first stable release
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 boundSheetsServiceby variable name (SheetsService $allocators). - Per-call fresh
SheetsClientso 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, plusSheetsClientFactory::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.2google/apiclient ^2.16
Installation
composer require gulaandrij/google-sheets-bundleSee README for configuration and the docs for recipes and architecture.
Full CHANGELOG.