Skip to content

Commit

Permalink
Merge branch 'master' of github.com:aawnu/php-ga4
Browse files Browse the repository at this point in the history
  • Loading branch information
aawnu committed May 28, 2024
2 parents 36871d0 + f227738 commit 7e3aadb
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 62 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/cron.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Cron | Monthly Health Check

on:
workflow_dispatch:
schedule:
- cron: 0 2 1 * *

jobs:
validate-master:
if: github.ref == 'refs/heads/master'
uses: ./.github/workflows/ci.yml
68 changes: 40 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,26 @@ $analytics = Analytics::new(
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.
`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
Expand Down Expand Up @@ -143,7 +158,7 @@ $event->setEventPage($eventPage);
![badge](https://shields.io/badge/AddShippingInfo-informational)
![badge](https://shields.io/badge/Purchase-informational)
![badge](https://shields.io/badge/Refund-informational)

### Engagement / Gaming

![badge](https://shields.io/badge/EarnVirtualCurrency-informational)
Expand Down Expand Up @@ -175,12 +190,12 @@ 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
Expand All @@ -192,14 +207,14 @@ foreach ($visitors as $collection) {
$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
Expand All @@ -216,27 +231,24 @@ foreach ($visitors as $collection) {

```js
// 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
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: [
{
addToCart: {
currency: 'EUR',
value: 13.37,
items: [
{
'item_id': 1,
'item_name': 'Cup',
'price': 13.37,
'quantity': 1
}
]
}
}
]
)
item_id: 1,
item_name: "Cup",
price: 13.37,
quantity: 1,
},
],
},
},
]);
```

#### Backend
Expand Down Expand Up @@ -274,7 +286,7 @@ 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 = [];

Expand Down
46 changes: 20 additions & 26 deletions src/Analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ public function getRequiredParams(): array
return $return;
}

public function setNonPersonalizedAds(bool $exclude)
{
$this->non_personalized_ads = $exclude;
return $this;
}

public function setClientId(string $id)
{
$this->client_id = $id;
Expand Down Expand Up @@ -112,7 +106,7 @@ public function addEvent(Facade\Type\EventType ...$events)
return $this;
}

public function consent()
public function consent(): ConsentHelper
{
return $this->consent;
}
Expand All @@ -130,30 +124,23 @@ public function post(): void
$url = $this->debug ? Facade\Type\AnalyticsType::URL_DEBUG : Facade\Type\AnalyticsType::URL_LIVE;
$url .= '?' . http_build_query(['measurement_id' => $this->measurement_id, 'api_secret' => $this->api_secret]);

$body = $this->toArray();
array_merge_recursive(
$body = array_replace_recursive(
$this->toArray(),
["user_properties" => $this->user_properties],
["consent" => $this->consent->toArray()],
);

$chunkUserProperties = array_chunk($this->user_properties, 25, true);
$this->user_properties = [];

$chunkEvents = array_chunk($this->events, 25);
$this->events = [];

$chunkMax = count($chunkEvents) > count($chunkUserProperties) ? count($chunkEvents) : count($chunkUserProperties);
if (count($chunkEvents) < 1) {
throw Ga4Exception::throwMissingEvents();
}

for ($chunk = 0; $chunk < $chunkMax; $chunk++) {
$body['user_properties'] = $chunkUserProperties[$chunk] ?? [];
if (empty($body['user_properties'])) {
unset($body['user_properties']);
}
$this->user_properties = [];
$this->events = [];

$body['events'] = $chunkEvents[$chunk] ?? [];
if (empty($body['events'])) {
unset($body['events']);
}
foreach ($chunkEvents as $events) {
$body['events'] = $events;

$kB = 1024;
if (($size = mb_strlen(json_encode($body))) > ($kB * 130)) {
Expand Down Expand Up @@ -204,13 +191,20 @@ public static function new(string $measurementId, string $apiSecret, bool $debug
* Deprecated references
*/

/** @deprecated 1.1.1 */
/** @deprecated 1.1.9 Please use `Analytics->consent->setAdPersonalizationPermission()` instead */
public function setNonPersonalizedAds(bool $exclude)
{
$this->consent->setAdPersonalizationPermission(!$exclude);
return $this;
}

/** @deprecated 1.1.1 Please use `Analytics->consent->setAdPersonalizationPermission()` instead */
public function allowPersonalisedAds(bool $allow)
{
$this->setNonPersonalizedAds(!$allow);
$this->consent->setAdPersonalizationPermission($allow);
}

/** @deprecated 1.1.1 */
/** @deprecated 1.1.1 Please use `Analytics->setTimestampMicros()` instead */
public function setTimestamp(int|float $microOrUnix)
{
$this->setTimestampMicros($microOrUnix);
Expand Down
5 changes: 5 additions & 0 deletions src/Exception/Ga4Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,9 @@ public static function throwRequestInvalidBody(array $msg)
static::REQUEST_INVALID_BODY
);
}

public static function throwMissingEvents()
{
return new static("Request must include at least 1 event with a name", static::REQUEST_EMPTY_EVENTLIST);
}
}
3 changes: 2 additions & 1 deletion src/Facade/Type/AnalyticsType.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ public function setTimestampMicros(int|float $microOrUnix);
public function setNonPersonalizedAds(bool $allow);

/**
* The user properties for the measurement
* The user properties for the measurement (Up to 25 custom per project, see link)
*
* @var user_properties
* @param AlexWestergaard\PhpGa4\Facade\Type\UserProperty $prop
* @link https://support.google.com/analytics/answer/14240153
*/
public function addUserProperty(UserPropertyType ...$props);

Expand Down
1 change: 1 addition & 0 deletions src/Facade/Type/Ga4ExceptionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ interface Ga4ExceptionType
const REQUEST_INVALID_BODY = 104005;
const REQUEST_MISSING_MEASUREMENT_ID = 104006;
const REQUEST_MISSING_API_SECRET = 104007;
const REQUEST_EMPTY_EVENTLIST = 104008;
}
4 changes: 2 additions & 2 deletions src/Helper/ConsentHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

class ConsentHelper
{
const GRANTED = "granted";
const DENIED = "denied";
const GRANTED = "GRANTED";
const DENIED = "DENIED";

private ?string $ad_user_data = null;
private ?string $ad_personalization = null;
Expand Down
10 changes: 5 additions & 5 deletions test/Unit/AnalyticsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use AlexWestergaard\PhpGa4\Facade;
use AlexWestergaard\PhpGa4\Event;
use AlexWestergaard\PhpGa4\Analytics;
use AlexWestergaard\PhpGa4\Event\Login;
use AlexWestergaard\PhpGa4Test\TestCase;

final class AnalyticsTest extends TestCase
Expand All @@ -17,7 +18,6 @@ public function test_can_configure_and_export()
$this->prefill['api_secret'],
$debug = true
)
->setNonPersonalizedAds($nonPersonalisedAds = true)
->setClientId($this->prefill['client_id'])
->setUserId($this->prefill['user_id'])
->setTimestampMicros($time = time())
Expand All @@ -27,7 +27,6 @@ public function test_can_configure_and_export()
$asArray = $analytics->toArray();
$this->assertIsArray($asArray);

$this->assertArrayHasKey('non_personalized_ads', $asArray);
$this->assertArrayHasKey('timestamp_micros', $asArray);
$this->assertArrayHasKey('client_id', $asArray);
$this->assertArrayHasKey('user_id', $asArray);
Expand All @@ -36,7 +35,6 @@ public function test_can_configure_and_export()

$timeAsMicro = $time * 1_000_000;

$this->assertEquals($nonPersonalisedAds, $asArray['non_personalized_ads']);
$this->assertEquals($timeAsMicro, $asArray['timestamp_micros']);
$this->assertEquals($this->prefill['client_id'], $asArray['client_id']);
$this->assertEquals($this->prefill['user_id'], $asArray['user_id']);
Expand All @@ -46,7 +44,7 @@ public function test_can_configure_and_export()

public function test_can_post_to_google()
{
$this->assertNull($this->analytics->post());
$this->assertNull($this->analytics->addEvent(Login::new())->post());
}

public function test_converts_to_full_microtime_stamp()
Expand All @@ -68,6 +66,8 @@ public function test_throws_if_microtime_older_than_three_days()

public function test_exports_userproperty_to_array()
{
$this->analytics->addEvent(Login::new());

$userProperty = UserProperty::new()
->setName('customer_tier')
->setValue('premium');
Expand Down Expand Up @@ -138,7 +138,7 @@ public function test_throws_on_too_large_request_package()
$userProperty->setValue($overflowValue);
}

$this->analytics->addUserProperty($userProperty)->post();
$this->analytics->addEvent(Login::new())->addUserProperty($userProperty)->post();
}

public function test_timeasmicro_throws_exceeding_max()
Expand Down
Loading

0 comments on commit 7e3aadb

Please sign in to comment.