codific/google-docs-help-bundle
A Symfony bundle that renders help pages from Google Docs content, letting non-technical teams edit documentation in Docs while the application serves it automatically.
Read the code before you read the README. In a repo with no stars, no dependents, and no release cadence, the docs drift and there's no community to ask. Open these in order:
README.md — for the intended config shape (treat it as a suggestion, not a contract).DependencyInjection/Configuration.php — the authoritative list of config keys. Everything else is commentary.DependencyInjection/*Extension.php — how services are wired, aliased, and tagged.Service/ — the real pipeline: Google Docs fetch → parse → HTML render.Everything you actually care about lives in Service/. The bundle wrapper is mostly plumbing around it.
Minimal setup (Symfony host app):
composer require codific/google-docs-help-bundle
config/bundles.php (Flex may handle this).config/packages/codific_google_docs_help.yaml with the credentials path and the document ID (the long string in the Docs URL, not the URL itself).bin/console debug:container | grep -i codific
bin/console config:dump-reference <bundle_alias>
First use case — the one that justifies the package: a public Help/FAQ page where a non-technical author edits a Google Doc using Heading 1/2/3, paragraphs, bullet lists, links, images, and tables, and the site renders a clean styled page. Inject the bundle's renderer service into a controller, pass the document ID, output the HTML in Twig, and wrap the fetch in cache so you aren't billing the Docs API per request.
If you're on Laravel, stop and read this first. This is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/*.php publish. Your realistic options:
Service/ into app/Support/ and bind them in a ServiceProvider (most likely path, ~a few days).google/apiclient directly (the TPM assessment's recommendation, and probably correct).Do not attempt to boot a Symfony DI container inside Laravel just to consume this. Dual-container setups are fragile and will cost you more than the port.
Treat fetch, parse, and render as three separate concerns. Don't assume a single call does everything — inspect Service/ and compose the pieces yourself. The natural seam is:
Docs API client → raw Document JSON → parser → node tree → renderer → HTML
That node-tree boundary is the only place worth integrating against. It makes the package testable, lets you swap the renderer, and lets you feed recorded fixtures instead of live API responses.
Wrap it in your own interface — this is non-negotiable. Whatever you do, don't let controllers or Blade/Twig templates depend on bundle internals:
interface HelpPageRenderer
{
public function render(string $documentId): string;
}
Then implement it with the bundle's services (or your ported copy, or a direct google/apiclient call). This is your seam for swapping implementations, adding sanitization, and surviving the day you fork or drop the dependency.
Cache hard, render cheap. The Docs API is slow and quota-limited. Never call it per request.
return Cache::remember("help:doc:{$documentId}", now()->addHours(1), function () use ($documentId) {
return $this->renderer->render($documentId);
});
Key by document ID. Invalidate on deploy. Expose an artisan/console command (help:refresh) so support can force a re-fetch after an edit without waiting for TTL.
Persist the parse output, not just the HTML. Save the fetched/parsed node tree (JSON) to disk or a cache key as a build step. Benefits: deterministic rendering, page survives Google outages or credential rotation, and you get a diffable history of content changes. For critical pages, commit the artifact.
Sanitize before |raw. Google Docs content is user-controlled — anyone with edit access to the doc can inject whatever the Docs API will return. Run output through symfony/html-sanitizer (or Laravel's mews/purifier) with an allowlist of the tags your authors actually use. Treat image URLs and link hrefs as untrusted.
Override templates rather than patching vendor. Symfony bundles expose templates at templates/bundles/<BundleName>/. Copy the bundle's templates into your app, restyle to your design system, and keep the override thin so bundle updates don't become merge conflicts. Same idea for node renderers: implement the bundle's renderer interface and tag the service rather than editing vendor/.
Drive document IDs from config/env per environment. Staging renders a draft doc; production renders the published one. Put the ID in .env and read it through a config value, not hardcoded in a controller.
Add a smoke test against a recorded fixture. Capture a real Docs API JSON response, commit it, and assert the rendered HTML contains expected headings, list items, and resolved image URLs. These are the tests that catch Google API shape changes before your help page goes blank.
It is a Symfony bundle, not a Laravel package. Repeating because it's the single most expensive misunderstanding here. No service provider, no facade, no publishes(). Confirm this before you commit to an approach.
Google API setup is the classic failure point. Three errors account for nearly all breakage:
| Symptom | Cause |
|---|---|
400 accessNotConfigured |
Docs API or Drive API not enabled in the project |
403 / 404 |
Doc not shared with the service account email |
401 / scope errors |
Wrong OAuth scope, or user OAuth used where a service account was needed |
Also: pass the document ID, not the URL. If you're passing anything containing docs.google.com, that's your bug.
Google Docs markup is noisier than it looks. Expect to normalize in a post-processing pass:
\n inside a paragraph), not just paragraph boundaries.inlineObjects to get a usable URL. This is not automatic in most naive integrations.Build a fixture of a real, messy document and keep it in the test suite. Authors will produce shapes you didn't anticipate.
Config quirks. README examples lag Configuration.php. Trust the config tree:
bin/console config:dump-reference <alias>
If injection fails with "service not found," the service may be private — retry with debug:container --show-private and reference it by alias or inject via interface.
Debug the pipeline in two halves. Dump the raw API response and the parsed node tree separately. In practice, 90% of "the template is broken" bugs are "the parser silently dropped a node type." Once you know which half is wrong, the fix is usually obvious.
Extension points to know. The clean seams are:
If you need anything beyond these, you are past the point where forking is cheaper than patching. Pin a specific commit, vendor it into your org, and document the diff in the repo.
Bus factor is the real risk. Zero stars, zero dependents, no release cadence, no security response. A composer update can silently break a production help page. Mitigate by:
HelpPageRenderer interface as the only contract your app depends on.Budget the replacement cost up front. The TPM assessment puts a from-scratch Docs-to-HTML pipeline at roughly 1–2 engineering weeks against google/apiclient. If you're spending more than that adapting this bundle, you've lost. Treat it as a reference implementation and a code sample, not a dependency you inherit.
How can I help you explore Laravel packages today?