Giter VIP home page Giter VIP logo

php-ga4's Introduction

Package

Version License PHPVersion Size Code Coverage Badge

Development

Version Issues Pulls Contributors LastCommit

composer require alexwestergaard/php-ga4

GDPR Notice

European Union have noticed that default setup of Google Analytics does not comply with GDPR as data is sent unrestricted to an american service possibly outside of Europe. This includes the use of gtag.js as JavaScript pushes the request from visitors device including their IP-Address.

Server Side Tracking, however, does only send information specified inside the body and about your server.

Relying solely on Google Analytics 4 Events - that is not pushed through the gtag.js script - can be scraped of GDPR-related information.

Getting started

To setup Analytics you need a Measurement ID and API Secret.

Go to Administrator (bottom left) and then select your Account -> Data Streams -> your stream.
Here you will find Measurement-ID at top from and further down Api Secrets for Measurement Protocol, in there you can create yourself an API Secret.

PLEASE note that Google Analytics will look out from traffic gathered by the gtag.js library, as Server Side Events are supposed to supplement the frontend through its _ga/_gid cookie sessions.

use AlexWestergaard\PhpGa4\Analytics;

$analytics = Analytics::new(
    measurement_id: 'G-XXXXXXXX',
    api_secret: 'xYzzX_xYzzXzxyZxX',
    debug: true|false
);

// You can set CONSENT here if not done through the gtat.js
// Read full docs here: https://support.google.com/tagmanager/answer/13802165
$consent = $analytics->consent(); // returns a consent handler

// Sets consent for sending user data from the request's events  
// and user properties to Google for advertising purposes.
$consent->setAdUserDataPermission();
$consent->getAdUserDataPermission();
$consent->clearAdUserDataPermission();

// Sets consent for personalized advertising for the user.
$consent->setAdPersonalizationPermission();
$consent->getAdPersonalizationPermission();
$consent->clearAdPersonalizationPermission();

Data flow

session_id > Google Analytics does not specify a required type of session or user id. You are free to use any kind of unique identifier you want; the catch, however, is that Google Analytics populates some internal data with gtag.js, that is then referenced to their _ga cookie session id. Just be aware that gtag.js is using client-side Javascript and can therefore have some GDPR complications as requests back to Google Analytics contains client information; such as their IP Address.

  1. Acquire proper GDPR Consent
  2. Client/GTAG.js sends session_start and first_visit to GA4
  3. GA4 sends _ga and _gid cookies back to Client/GTAG.js
  4. Server uses _ga (or _gid; or your unique session_id) to populate events

Note: It is entirely possible to push events to backend without acquiring the session cookies from Google Analytics; you will however lose information bundled inside the GTAG.js request if you do not figure out how to push that via backend too.

Layers

The code is following 3 layers that should be considered; 5 layers at max.

Analytics [
    Events [
        Event {
            Parameters
            ? Items [
                Item Parameters
            ]
        }
    ]
    User Properties [
        Properties {
            Key: Value
        }
    ]
]

Events

This is a list of prebuilt events as shown in the documentation. All events have the following parameters to locate trigger location of each event.

// Manual setting of each event
$event->setLanguage(string $var);
$event->setPageLocation(string $var);
$event->setPageReferrer(string $var);
$event->setPageTitle(string $var);
$event->setScreenResolution(string $var);
// Fillable for multiple events
$eventPage = AlexWestergaard\PhpGa4\Helper\EventParamsHelper();
$event->setEventPage($eventPage);

Default

badge badge badge badge badge badge badge badge badge badge badge badge

E-commerce

badge badge badge badge badge badge badge badge badge badge

Engagement / Gaming

badge badge badge badge badge badge badge badge

Frontend & Backend Communication

This library is built for backend server side tracking, but you will probably trigger most events through frontend with Javascript or Websockets. There will be 2 examples, one as pure backend for logged/queued events and one for frontend to backend communication.

Logging / Queue

use AlexWestergaard\PhpGa4\Exception;
use AlexWestergaard\PhpGa4\Analytics;
use AlexWestergaard\PhpGa4\Event;
use AlexWestergaard\PhpGa4\Item;

// require vendor/autoload.php

$visitors = getVisitorsAndEvents(); // pseudo function, make your own logic here

foreach ($visitors as $collection) {
    // Group of events, perhaps need logic to change from json or array to event objects
    // Maybe its formatted well for the > ConvertHelper::parseEvents([...]) < helper
    $groups = $collection['events'];

    // If gtag.js, this can be the _ga or _gid cookie
    // This can be any kind of session identifier
    // Usually derives from $_COOKIE['_ga'] or $_COOKIE['_gid'] set by GTAG.js
    $visitor = $collection['session_id'];

    // load logged in user/visitor
    // This can be any kind of unique identifier, readable is easier for you
    // Just be wary not to use GDPR sensitive information
    $user = $collection['user_id'];

    // Render events grouped on time (max offset is 3 days from NOW)
    foreach ($groups as $time => $data) {
        try {
                $analytics = Analytics::new($measurementId, $apiSecret)
                    ->setClientId($visitor)
                    ->setTimestampMicros($time);

                if ($user !== null) {
                    $analytics->setUserId($user);
                }

                $analytics->addUserParameter(...$data['userParameters']); // pseudo logic for adding user parameters
                $analytics->addEvent(...$data['events']); // pseudo logic for adding events

                $analytics->post(); // send events to Google Analytics
        } catch (Exception\Ga4Exception $exception) {
            // Handle exception
            // Exceptions might be stacked, check: $exception->getPrevious();
        }
    }
}

Frontend => Backend

Frontend

// array< array< eventName, array<eventParams> > >
axios.post("/your-api-endpoint/ga4-event-receiver", [
  // Note each event is its own object inside an array as
  // this allows to pass the same event type multiple times
  {
    addToCart: {
      currency: "EUR",
      value: 13.37,
      items: [
        {
          item_id: 1,
          item_name: "Cup",
          price: 13.37,
          quantity: 1,
        },
      ],
    },
  },
]);

Backend

use AlexWestergaard\PhpGa4\Helper\ConvertHelper;
use AlexWestergaard\PhpGa4\Exception;
use AlexWestergaard\PhpGa4\Analytics;
use AlexWestergaard\PhpGa4\Event;

// require vendor/autoload.php

try {
    $events = ConvertHelper::parseEvents($_POST);

    Analytics::new($measurementId, $apiSecret)
        ->addEvent(...$events)
        ->post();
} catch (Exception\Ga4Exception $exception) {
    // Handle exception
    // Exceptions might be stacked, check: $exception->getPrevious();
}

Custom Events

You can build your own custom events. All you need is to implement and fullfill the AlexWestergaard\PhpGa4\Facade\Type\EventType facade/interface. If you want ease of life features, then you can extend your event from AlexWestergaard\PhpGa4\Helper\EventHelper and overwrite as you see fit.

// EventHelper implements AlexWestergaard\PhpGa4\Facade\Type\EventType
class ExampleEvent extends AlexWestergaard\PhpGa4\Helper\EventHelper
{
    // variables should be nullable as unset() will set variable as null
    protected null|mixed $my_variable;
    protected null|mixed $my_required_variable;

    // Arrays should always be instanciated empty
    protected array $my_array = [];

    public function getName(): string
    {
        return 'example_event';
    }

    public function getParams(): array
    {
        return [
            'my_variable',
            'my_array',
        ];
    }

    public function getRequiredParams(): array
    {
        return [
            'my_required_variable',
        ];
    }

    public function setMyVariable(string $value)
    {
        $this->my_variable = $value;
        return $this; // Allows chained events
    }

    public function setMyRequiredVariable(string $value)
    {
        $this->my_required_variable = $value;
        return $this; // Allows chained events
    }
}

Debug

Measurement protocol for GA4 has debug functionality that can be enabled with the debug parameter in the Analytics constructor.

$analytics = Analytics::new(
    measurement_id: 'G-XXXXXXXX',
    api_secret: 'xYzzX_xYzzXzxyZxX',
    debug: true // default: false
);

When Debug is enabled then events are sent to https://www.google-analytics.com/debug/mp/collect where issues will be caught with GA4Exception (Be aware of $exception->getPrevious() stacks); such response will look as follows:

{
  "validationMessages": [
    {
      "fieldPath": "events",
      "description": "Event at index: [0] has invalid name [_badEventName]. Names must start with an alphabetic character.",
      "validationCode": "NAME_INVALID"
    }
  ]
}

Notice: This library already validates that events are properly formatted when added to analytics ($analytics->addEvent(...)).

Two important points:

  • Events sent to the Validation Server will not show up in reports.
  • There is no way for events sent through measurement protocol (Server Side) to show up in the debugView in Google Analytics Admin.

Additional information

  • Geographic information is only available via automatic collection from gtag, Google Tag Manager, or Google Analytics for Firebase.
  • The page_view event works, however it's not documented in the official documentation, so do not rely on it.

Documentation

php-ga4's People

Contributors

8ctopus avatar aawnu avatar dependabot[bot] avatar jordykouters avatar tobz-nz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

php-ga4's Issues

Convert GTAG Parameters into Server-Side Parameters too

Studying the PageView request made by GTAG in JavaScript it seems that parameter names are the same as V1 but with limited parameter scope.

https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters

GET Parameters

v: 2
tid: G-XXXXX
gtm: 2oe7d0
_p: 1486226803
_z: ccd.v9B
cid: 00000.000000
ul: en-us
sr: 1920x1080
sid: 1657899578
sct: 3
seg: 1
dl: http%3A%2F%2F127.0.0.1%2F
dr: http%3A%2F%2F127.0.0.1%2F
dt: Chttr.link
_s: 1

Body Parameters

en=page_view&_ee=1
en=scroll&epn.percent_scrolled=90&_et=8

Server only tracking, user tracking?

I implemented tracking on a new property that tracks servers ONLY.

I see the pageviews correctly, but I don't see the users.
It's as if the events were not launched by users and therefore I can't understand their behavior, they all seem to be unconnected events.

I call the library and set setClientId to a unique value for each user.

Am I doing something wrong or is it not possible to have the concept of user by tracking only servers?

begin checkout

Hi, is this how to do a begin checkout in the backend?

use AlexWestergaard\PhpGa4\Exception;
use AlexWestergaard\PhpGa4\Analytics;
use AlexWestergaard\PhpGa4\Event\BeginCheckout;
use AlexWestergaard\PhpGa4\Item;

try {
            // If gtag.js, this can be the _ga or _gid cookie
            // This can be any kind of session identifier
            $session = $_COOKIE['_ga'] ?? $_COOKIE['_gid'] ?? $_COOKIE['PHPSESSID'];

            $analytics = Analytics::new( 'G-XXXXXXXX','xYzzX_xYzzXzxyZxX')
            ->setClientId($session);

            $analytics->addEvent(new BeginCheckout);
            $analytics->setCurrency('usd');
            $analytics->setValue('60.00');
            $analytics->addItem([
                'item_id' => 'bla bla',
                'currency'=> 'USD',
                'price'   =>'60.00',
                'quantity'=> 1
            ]);
            $analytics->post();
        } catch (Exception\Ga4Exception $exception) {
            // Handle exception
            if (YII_DEBUG) dd($exception);
            Yii::error($exception);
        }

i'm getting this error

AlexWestergaard\PhpGa4\Exception\Ga4IOException#1
(
    [*:message] => 'Missing required parameter: items'
   .....
   .....

how do i fix this? Thank you.

Update GA4Exception with improved error reporting

Transform GA4Exception to handle stacking on its own until being thrown; avoid having to pass it between classes when throwing is stalled until right before returning/exiting.

This should also make it simpler to use GA4Exception when extending the base Model class for custom event types.

Move support up to PHP 8+

Official support for PHP 7 will soon end. Updating the supported versions of this library will provide better options for type checking and stricter data control.

  • v1.0 will be moved into own branch for legacy support of executable errors | composer 1.0.*
  • Future versions with updated support will be released as v1.1.x | composer ^1.1

https://www.php.net/supported-versions.php

User location

In your experience, what happens if:

  • user comes to your website and gets a new session from gtag.js
  • then user buys and your website backend sends a purchase event using this library.

In which country will the purchase take place? The country of the user's IP address or the country of your server's IP address?

rationale for Analytics::new()

Hi again Alex,

What is the rationale for Analytics::new('G-XXXXXX', 'eMVPMEcTbaI1iuWtrPw', true) instead of new Analytics('G-XXXXXX', 'eMVPMEcTbaI1iuWtrPw', true)?

php-ga4: ^1.1 - Events are not appearing in GA4 realtime .

Hi there,
I'm trying to use your package to record GA4 backend events and I'm not having much luck. I'm using Laravel 8 with php 8.1.
The code seems to execute (exception code does not fire), however the events are not appearing, specifically add_to_cart. Here is my code...

`
$measurementId = env('GA4_MEASUREMENT_ID');
$ga4ApiSecret = env('GA4_API_KEY');

    $clientID = null;

    if(isset($_COOKIE['_ga'])) {
        $gaCookie = $_COOKIE['_ga'];
        $clientID = explode('.', $gaCookie)[2];
    }

    try {
        $events = Converter::parseEvents($event);
        Analytics::new($measurementId, $ga4ApiSecret)
            ->setClientId($clientID)
            ->addEvent(...$events)
            ->post();
    } catch (Exception\Ga4Exception $exception) {
        // Handle exception
        Log::alert('GA4 Event failed');
        Log::alert($exception);
        Log::alert($event);

    }

`

My event looks like this...

array ( 'add_to_cart' => array ( 'currency' => 'AUD', 'value' => 38, 'items' => array ( 0 => array ( 'item_id' => 29, 'item_name' => '500g Musk Scrolls', 'price' => 38, 'quantity' => 1, ), ), ), )

The GA4 credentials and event data appear to be fine as I have recorded the event with another package but I like the way this package calls are made and it better fits my application.

Can you suggest how I can debug the problem? Many thanks

If you follow the code then you will see there is 3 layers.

If you follow the code then you will see there is 3 layers.

Analytics[ Events[ Items ] ] ]

Add src/Item.php to your src/Event/BeginCheckout.php event, then add the event to src/Analytics.php.

You should not be able to addItem on Analytics, if this is doable then I will fix it later.

Originally posted by @aawnu in #55 (comment)

sorry i really dont understand what you mean. how do i add items to my events?

in your begincheckout class you do have this

  public function addItem(Facade\Type\ItemType $item)
    {
        $this->items[] = $item->toArray();
        return $this;
    }

and i did add it.

$analytics->addItem([
                'item_id' => 'bla bla',
                'currency'=> 'USD',
                'price'   =>'60.00',
                'quantity'=> 1
            ]);

Dashboard not reflecting the posted report

I tried posting a pageview, and the code worked without a glitch, but the usage is not reported or shown in the analytics dashboard. Am I doing something wrong?

include_once(__DIR__ . '/vendor/autoload.php');

use AlexWestergaard\PhpGa4\Analytics;
use AlexWestergaard\PhpGa4\Event\PageView;

$clientId = $_COOKIE['_ga'] ?? $_COOKIE['_gid'] ?? $_COOKIE['PHPSESSID'] ?? null;

if(isset($_COOKIE['_ga'])) {
    $gaCookie = $_COOKIE['_ga'];
    $clientID = explode('.', $gaCookie)[2];
}


$event = PageView::new()
->setLanguage('en')
->setScreenResolution('1024x768')
->setPageLocation('/home')
->setPageTitle('Home');

// GA4 Initialize
$analytics = Analytics::new ('G-xxxxxxxx', 'xxxxxxxxxxxxxxxxxxxxxxxx')
->setClientId($clientId)
->setTimestampMicros(time())
->addEvent($event)
->post();

Refactor structure and use of facades/abstraction

In order to give developers more freedom of how they want functionality to work inside each model, the type checks should depend on method access with stronger data validation rather than expect base functionality to not be overwritten.

  • Update folder structure for simpler code organization
  • Replace use of (abstract) models to interfaces for simpler point of entry
  • Update event facades to use grouping of reusable variable facades (more code, simpler bulk editing)

Enable debug_mode support?

This is a really nice library, thank you for building it. One thing I noticed is I couldn't find a way to enable debug_mode in events (not to be confused with the debug url).

This is supposed to allow measurement protocol events to show up in the "Debug View" in the Admin Console, making it easier to see in real-time if the right stuff is getting into Analytics.

If this is somehow supported and I just missed it please let me know. Otherwise it would be a cool setting to see added.

Problem with Event Refund

In GA4 documentation there are two types of Refund - partial and full.
When I use event for Full refund, I need to set Items, but only transaction_id should be required. Items (selected) are required for partial refund.

Status of page_view event

The page_view event is not officially supported by measurement protocol, maybe that should be reflected in the README.

question: How to get data to show up in debug?

I've been trying without success to get the debug mode working, in order to view events as soon as they are generated in debugView (I have some events that google says are not correct and I would like to figure out why)

https://analytics.google.com/analytics/web/#/a62992619p355170503/admin/debugview/overview

Do you know how to do it? I will be happy to submit a PR for the readme as I believe it will be useful for others.

Here's my sample code:

$clientId = $_COOKIE['_ga'] ?? $_COOKIE['_gid'] ?? null;

if (!$clientId) {
    throw new Exception('GA cookie not set');
}

$debug = true;

$analytics = Analytics::new($trackingId, $apiSecret, $debug)
    ->setClientId($clientId)
    ->setTimestampMicros(time());

$price = 1;
$transactionId = strtoupper(bin2hex(random_bytes(10)));

$purchase = Event\Purchase::new()
    ->setCurrency('USD')
    ->setValue($price)
    ->setTransactionId($transactionId);

$products = [
    [
        'name' => 'ball',
        'amount' => 5.00,
    ], [
        'name' => 'pen',
        'amount' => 5.00,
    ],
];

foreach ($products as $product) {
    $item = Item::new()
        ->setItemName($product['name'])
        ->setQuantity(1)
        ->setPrice($product['amount']);

    $purchase->addItem($item);
}

$analytics->addEvent($purchase);
$analytics->post();

Add import method of array values

I would like to add arraycreation of events and items, where it is currently done through chained events only.

Currently:

use AlexWestergaard\PhpGa4\Event;

$event = Event\ViewCart::new()
    ->setCurrency('EUR')
    ->setValue(10.00);

I would like to also allow:

use AlexWestergaard\PhpGa4\Event;

$event = Event\ViewCart::fromArray([
    'currency' => 'DKK', // eq ViewCart::$currency
    'value' => 10.00', // eq ViewCart::$value
]);

It should also have proper validation for sub-model imports, so that ViewCart::addItem(Item $item) would be triggered on <items=>Object|Array> either by executing addItem if object or import through item::fromArray()->toArray() if Array.

the purpose of this should be the ability to build larger datasets and then import as array post-process rather than in-process.

User Properties - Question

Hello, first of all, thank you for the great library.

I've a few questions about the user properties, is anyone able to help me?

  1. GA4 docs isn't very clear about the user-properties, do you know if I can set any key-value there or they've specific user-properties that I can use?
  2. I know that I need to set up those user properties in the GA4 configurations, do you know what happens if I send a request with a user property that wasn't previously set up there? Will that still work fine? And the opposite, if I don't send an expected key?
  3. Do you've any links that might help me to understand better how to use the user-properties? The ones that I found in the GA4 docs weren't useful to understand certain detail.

Item -> item_category - Validation error [VALUE_INVALID]

I think there might be an issue with item_category

Item.php

    public function addItemCategory(string $category)
    {
        $this->item_category[] = $category;
        return $this;
    }

I'm adding a category name like this

$item->addItemCategory( $category->name );

But then I get a validation error

jsonBody: {"non_personalized_ads":false,"client_id":"GA1.1123123123","events":[{"name":"view_item","params":{"currency":"GBP","value":2.6000000000000001,"items":[{"item_id":"443","item_name":"Complete Food Meal","currency":"GBP","item_category":["Food"],"price":2.6000000000000001,"quantity":1}]}}]}

Formatted jsonBody

{
    "non_personalized_ads": false,
    "client_id": "GA1.1123123123",
    "events": [
        {
            "name": "view_item",
            "params": {
                "currency": "GBP",
                "value": 2.6,
                "items": [
                    {
                        "item_id": "443",
                        "item_name": "Complete Food Meal",
                        "currency": "GBP",
                        "item_category": [
                            "Food"
                        ],
                        "price": 2.6,
                        "quantity": 1
                    }
                ]
            }
        }
    ]
}

Response code: 200
URL: https://www.google-analytics.com/debug/mp/collect?measurement_id=G-123&api_secret=XYZ

PHP Fatal error

Uncaught AlexWestergaard\PhpGa4\Exception\Ga4Exception: Validation Message > VALUE_INVALID [events.params.items]: Item param [item_category] has unsupported value. Value [list_value     { values { string_value: "Food" } }] is unsupported list value. in /vendor/alexwestergaard/php-ga4/src/Exception/Ga4Exception.php:77

According to https://developers.google.com/analytics/devguides/collection/ga4/ecommerce?client_type=gtag#view_item_details
item_category should be a string and it allows to add up to 5 indexed item_categoryINDEX params

      item_category: "Apparel",
      item_category2: "Adult",
      item_category3: "Shirts",
      item_category4: "Crew",
      item_category5: "Short sleeve",

I'm happy to update the code to work properly.

Thanks
Michal

Add document title

Hello!

I need your help, i send data with your extension but my Views box in analytics is empty, how to add document title param to your code?

kép

Client id must have been created by gtag.js?

I have found this in the google doc:

In order for an event to be valid, it must have a client_id that has already been used to send an event from gtag.js. You will need to capture this ID client-side and include it in your call to the Measurement Protocol. In send an event to your property, we use "client_id" as the client_id. You will need to replace this with a real client_id that comes from gtag.js.

Does your practical experience corroborate the quote or not?

If affirmative, I think it should be added to the doc.

I'm adding the stackoverflow question that made me think about this if you're interested https://stackoverflow.com/questions/69105735/google-analytics-4-measurement-protocol-api-used-without-gtag-js-or-firebase

README _gid cookie question

I never get the _gid cookie, does it still exist? If not, we should remove it from the readme

$session = $_COOKIE['_ga'] ?? $_COOKIE['_gid'] ?? $_COOKIE['PHPSESSID'];

Add core events of GA.js/GTAG.js as SST-Events

PHP 8.3 support

Please add support for the recently released PHP 8.3.

Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.