Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Google Analytics Api Symfony Laravel Package

mediafigaro/google-analytics-api-symfony

A Symfony bundle that integrates the Google Analytics API, providing configuration and services to query analytics data from your Symfony application. Simplifies authentication and reporting setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a thin Symfony wrapper around google/apiclient for querying the Google Analytics Universal Analytics reporting API. It gives you a configured client/service from container config so you don't hand-roll credentials in every project.

1. Install

composer require mediafigaro/google-analytics-api-symfony

Current release is 1.2, which advertises Query Explorer parity: metrics, dimensions, and sorting. No config-key or service-ID changes are documented in the release note, so an existing 1.x setup should keep working — but confirm the accepted parameter names against the source (see step 4). Also check the resolved google/apiclient version in composer.lock; this bundle dates from 2018 and its constraints may pull an old client.

2. Register the bundle

Symfony 3.x (app/AppKernel.php):

$bundles = [
    // ...
    new MediaFigaro\GoogleAnalyticsApiBundle\MediaFigaroGoogleAnalyticsApiBundle(),
];

Symfony 4+ (config/bundles.php):

return [
    // ...
    MediaFigaro\GoogleAnalyticsApiBundle\MediaFigaroGoogleAnalyticsApiBundle::class => ['all' => true],
];

3. Create a Google service account

  • In Google Cloud Console: create a service account, enable the Analytics API, and download a JSON (or legacy P12) key.
  • In the GA Admin UI: add the service account email (e.g. [email protected]) as a Read user on the property. Enabling the API alone is not enough — this is the single most common setup failure.

4. Configure the bundle (shape is typical; confirm keys against the bundle's DependencyInjection/Configuration.php in vendor/):

# config/packages/media_figaro_google_analytics_api.yaml
media_figaro_google_analytics_api:
    client_id: '%env(GA_CLIENT_ID)%'
    service_account_name: '%env(GA_SERVICE_ACCOUNT_EMAIL)%'
    key_file_path: '%kernel.project_dir%/config/ga-service-account.json'
    app_name: 'my-symfony-app'

5. First use case — pull sessions for the last 30 days in a console command (don't do this in a web request):

// src/Command/GaSmokeTestCommand.php
$analytics = $this->getContainer()->get('media_figaro_google_analytics_api');

$results = $analytics->getResults([
    'ids'        => 'ga:123456789',       // your UA view ID
    'start-date' => '30daysAgo',
    'end-date'   => 'today',
    'metrics'    => 'ga/sessions',       // check ga: vs ga/ prefix against vendor source
    'dimensions' => 'ga/date',
    'sort'       => '-ga/sessions',      // 1.2: sorting supported; verify the array key
]);

foreach ($results as $row) {
    // inspect the returned Google_Service_Analytics object shape first
    dump($row);
}

The exact service ID, parameter keys (including the sort key added in 1.2), and helper method names are the first thing to verify: run bin/console debug:container | grep -i analytics (or debug:container media_figaro on older Symfony) and open vendor/mediafigaro/google-analytics-api-symfony/Resources/config/services.yml plus whatever class getResults() delegates to.

Best smoke test: build the identical query in the Query Explorer first. The release note explicitly targets parity with that tool, so if the numbers match there but differ in your command, it's a parameter problem, not a library problem.

Implementation Patterns

Wrap the bundle in your own repository/service. Never let controllers, templates, or Twig extensions touch the bundle service directly. Create a GaReportRepository that owns the view ID, parameter defaults, sorting, caching, and error handling:

final class GaReportRepository
{
    public function __construct(
        private $analytics,           // bundle service
        private CacheItemPoolInterface $cache,
        private string $viewId,
    ) {}

    public function sessionsByDay(\DateTimeImmutable $from, \DateTimeImmutable $to): array
    {
        $key = sprintf('ga.sessions.%s.%s', $from->format('Ymd'), $to->format('Ymd'));

        return $this->cache->get($key, function () use ($from, $to) {
            return $this->normalize($this->analytics->getResults([
                'ids'        => 'ga:' . $this->viewId,
                'start-date' => $from->format('Y-m-d'),
                'end-date'   => $to->format('Y-m-d'),
                'metrics'    => 'ga/sessions,ga/users,ga/pageviews',
                'dimensions' => 'ga/date',
                'sort'       => 'ga/date',
            ]));
        });
    }
}

This gives you one place to swap the backend later (which you will — see Gotchas). Normalize Google's row objects into plain arrays inside normalize() so nothing downstream depends on Google_Service_Analytics_* classes.

Use 1.2's sorting for "top N" widgets. Ranking is now a request parameter rather than something you do in PHP — pull the report sorted descending and slice:

public function topPages(\DateTimeImmutable $from, \DateTimeImmutable $to, int $limit = 20): array
{
    $rows = $this->normalize($this->analytics->getResults([
        'ids'        => 'ga:' . $this->viewId,
        'start-date' => $from->format('Y-m-d'),
        'end-date'   => $to->format('Y-m-d'),
        'metrics'    => 'ga/pageviews',
        'dimensions' => 'ga/pagePath',
        'sort'       => '-ga/pageviews',   // leading "-" = descending
    ]));

    return array_slice($rows, 0, $limit);   // slice client-side unless the bundle surfaces max-results
}

The Query Explorer sorts with a leading - for descending order; match that convention, and verify the array key the bundle reads for it (some wrappers call it sort, others orderBy/ordering). Sorting does not reduce the amount of data Google computes, so top-N widgets cost the same quota as the full report — cache them.

ETL into local storage rather than live queries. Analytics APIs are slow, rate-limited, and sampled. The reliable pattern is a scheduled command that pulls yesterday's numbers into a local table:

// src/Command/ImportGaDailyCommand.php
protected static $defaultName = 'app:ga:import-daily';

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $rows = $this->gaReportRepository->sessionsByDay(
        new \DateTimeImmutable('yesterday'),
        new \DateTimeImmutable('yesterday')
    );

    foreach ($rows as $row) {
        $this->dailyStats->upsert($row['date'], $row);
    }

    return Command::SUCCESS;
}

Schedule it with cron or symfony/scheduler. Your dashboards then read from your own DB, which is fast and survives API outages.

Handle multiple properties/views. The bundle is configured for one set of credentials. Either inject the service into a factory that builds per-property report objects (view ID is just a request parameter), or define several service definitions with different key_file_path values if you genuinely have separate service accounts.

Batch dimensions wisely. UA limits you to 7 dimensions and 10 metrics per request, and high-cardinality dimensions (ga:pagePath, ga:eventAction) inflate sampling. Prefer several small, cached queries over one giant one, and cache aggressively — historical data never changes, so cache by date range with a long TTL for closed ranges and a short TTL for ranges including today.

Testing. Don't hit Google in tests. Since the service comes from the container, bind an interface (e.g. GaClientInterface) in your own code and register a fake implementation in config/services_test.yaml. If you must test the bundle itself, catch Google_Service_Exception and assert on your repository's normalized output, not on Google's response objects.

Gotchas and Tips

This bundle targets Universal Analytics, which is dead. UA properties stopped processing hits in July 2023 (with a limited 360 extension). The bundle's underlying Google_Service_Analytics / Google_Service_AnalyticsReporting calls will not work for GA4 properties. If you are starting fresh in 2024+, this package is only useful as a reference implementation — migrate to google/analytics-data (GA4 Data API) directly. Treat any "it returns empty results" bug as a probable GA4-vs-UA mismatch before anything else.

1.2 is a query-shape release, not a modernization one. Metrics, dimensions, and sorting are now first-class, but the release note documents no parameter names, no config changes, and no PHP/Symfony bump. Read the class behind getResults() before writing queries — the metric prefix may be ga: or ga/, and the sort key may be sort, orderBy, or ordering. Wrong prefix = empty result set with no exception.

Sorting quirks. You can only sort on a dimension or metric actually present in the request; sorting on a metric you didn't request either errors or is silently ignored depending on the client version. Descending order is a leading -. And don't confuse server-side sorting (correct, quota-efficient) with the temptation to sort in PHP after pulling 10 years of ga:date — sort at the API and cache the result.

Last release was 2018 (until 1.2). Expect friction with PHP 8 and modern Symfony:

  • google/apiclient v2 is EOL; the bundle may pin a version incompatible with current PHP.
  • Deprecated/removed Symfony APIs (kernel.root_dir, getContainer()->get() in commands, old DI aliases).
  • Container compilation errors from old service definitions are common — check var/log/dev.log and bin/console cache:clear first.

If you must use it, pin your PHP/Symfony versions or fork the bundle and modernize services.yml and the composer constraints.

Credentials: JSON vs P12. Older Google client code defaults to .p12; newer to JSON. Match key_file_path to what the installed google/apiclient expects, or you'll get cryptic openssl_pkcs12_read / "invalid key" errors. Prefer JSON and set the service account scopes explicitly if the bundle allows it.

Path resolution. key_file_path is often resolved relative to %kernel.root_dir%. In Symfony 4+, %kernel.root_dir% points to src/, so a path that worked in a Symfony 3 recipe silently points at the wrong file. Use %kernel.project_dir% and an absolute path, and assert is_readable() at boot.

The service account is invisible until you add it to the property. A valid key with API enabled still yields 403 User does not have any Google Analytics account. Add the service account email as a user in GA Admin → Property Access Management, and ensure you're querying a view ID (ga:XXXXXXX), not a property or account ID.

Quotas and sampling. UA applies per-view daily quotas and returns sampled data for large queries. Check containsSampledData on the response and surface it in your UI — silently showing sampled numbers as exact is a reporting integrity bug. Back off on 403 rateLimitExceeded with exponential retry. Sorting a huge dimension set makes sampling worse, not better.

Debugging the HTTP layer. When results are empty or malformed, enable Google client logging (setLogger on Google_Client, or GOOGLE_APPLICATION_CREDENTIALS debugging) and inspect the raw request URL and JSON response. Cross-check the same parameters in the Query Explorer — since 1.2 claims parity with it, a mismatch there isolates the problem to your request array rather than the bundle.

Extension points. The cleanest way to modernize without a hard fork is to decorate the bundle's service with your own class implementing the same methods, translating calls to the GA4 Data API (including translating the sort parameter to GA4's orderBys). Register the decorator in services.yaml and keep your application code pointed at your wrapper interface — then dropping the bundle later is a one-line container change.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource