Skip to content

Commit

Permalink
Merge pull request #76 from Setono/adding-events
Browse files Browse the repository at this point in the history
Add events and event subscribers related to the conversion processor
  • Loading branch information
loevgaard authored Oct 31, 2023
2 parents 0e8b04b + e71aad6 commit baabca2
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 46 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
],
"require": {
"php": ">=8.1",
"brick/phonenumber": "^0.5.0",
"doctrine/collections": "^1.6",
"doctrine/orm": "^2.7",
"doctrine/persistence": "^1.3 || ^2.2 || ^3.2",
Expand Down
60 changes: 14 additions & 46 deletions src/ConversionProcessor/ConversionProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
namespace Setono\SyliusGoogleAdsPlugin\ConversionProcessor;

use Google\Ads\GoogleAds\Util\V13\ResourceNames;
use Google\Ads\GoogleAds\V13\Common\UserIdentifier;
use Google\Ads\GoogleAds\V13\Enums\UserIdentifierSourceEnum\UserIdentifierSource;
use Google\Ads\GoogleAds\V13\Services\ClickConversion;
use Psr\EventDispatcher\EventDispatcherInterface;
use Setono\SyliusGoogleAdsPlugin\Event\PreSetClickConversionDataEvent;
use Setono\SyliusGoogleAdsPlugin\Event\PreSetClickConversionUserIdentifiersEvent;
use Setono\SyliusGoogleAdsPlugin\Factory\GoogleAdsClientFactoryInterface;
use Setono\SyliusGoogleAdsPlugin\Logger\ConversionLogger;
use Setono\SyliusGoogleAdsPlugin\Model\ConversionInterface;
Expand All @@ -22,6 +23,7 @@ public function __construct(
private readonly WorkflowInterface $workflow,
private readonly GoogleAdsClientFactoryInterface $googleAdsClientFactory,
private readonly ConnectionMappingRepositoryInterface $connectionMappingRepository,
private readonly EventDispatcherInterface $eventDispatcher,
) {
}

Expand Down Expand Up @@ -60,7 +62,7 @@ public function process(ConversionInterface $conversion): void
// Google doesn't allow daylight savings time when uploading, so we need this small hack to turn our time into UTC first
$createdAt = \DateTimeImmutable::createFromInterface($createdAt)->setTimezone(new \DateTimeZone('UTC'));

$clickConversion = new ClickConversion([
$preSetClickConversionDataEvent = new PreSetClickConversionDataEvent($conversion, [
'conversion_action' => ResourceNames::forConversionAction(
(string) $customerId,
(string) $connectionMapping->getConversionActionId(),
Expand All @@ -71,13 +73,16 @@ public function process(ConversionInterface $conversion): void
'order_id' => $order->getId(),
'gclid' => $conversion->getGoogleClickId(),
]);
$this->eventDispatcher->dispatch($preSetClickConversionDataEvent);

$clickConversion->setUserIdentifiers([
new UserIdentifier([
'hashed_email' => self::normalizeAndHashEmailAddress($order->getCustomer()?->getEmailCanonical()),
'user_identifier_source' => UserIdentifierSource::FIRST_PARTY,
]),
]);
$clickConversion = new ClickConversion($preSetClickConversionDataEvent->data);

$preSetUserIdentifiersEvent = new PreSetClickConversionUserIdentifiersEvent($conversion);
$this->eventDispatcher->dispatch($preSetUserIdentifiersEvent);

if ([] !== $preSetUserIdentifiersEvent->userIdentifiers) {
$clickConversion->setUserIdentifiers($preSetUserIdentifiersEvent->userIdentifiers);
}

$conversionUploadServiceClient = $client->getConversionUploadServiceClient();

Expand All @@ -96,41 +101,4 @@ public function process(ConversionInterface $conversion): void

$this->workflow->apply($conversion, ConversionWorkflow::TRANSITION_UPLOAD_CONVERSION);
}

private static function normalizeAndHash(?string $value): string
{
if (null === $value) {
return '';
}

// Uses the SHA-256 hash algorithm for hashing user identifiers in a privacy-safe way, as
// described at https://support.google.com/google-ads/answer/9888656.
return hash('sha256', strtolower(trim($value)));
}

/**
* Returns the result of normalizing and hashing an email address. For this use case, Google
* Ads requires removal of any '.' characters preceding "gmail.com" or "googlemail.com".
*
* @param string $emailAddress the email address to normalize and hash
*
* @return string the normalized and hashed email address
*/
private static function normalizeAndHashEmailAddress(?string $emailAddress): string
{
if (null === $emailAddress) {
return '';
}

$normalizedEmail = strtolower($emailAddress);
$emailParts = explode('@', $normalizedEmail);
if (count($emailParts) > 1 && preg_match('/^(gmail|googlemail)\.com\s*/', $emailParts[1])) {
// Removes any '.' characters from the portion of the email address before the domain
// if the domain is gmail.com or googlemail.com.
$emailParts[0] = str_replace('.', '', $emailParts[0]);
$normalizedEmail = sprintf('%s@%s', $emailParts[0], $emailParts[1]);
}

return self::normalizeAndHash($normalizedEmail);
}
}
5 changes: 5 additions & 0 deletions src/DependencyInjection/SetonoSyliusGoogleAdsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Setono\SyliusGoogleAdsPlugin\DependencyInjection;

use Setono\SyliusGoogleAdsPlugin\ConversionProcessor\ConversionProcessorInterface;
use Setono\SyliusGoogleAdsPlugin\ConversionProcessor\QualificationVoter\QualificationVoterInterface;
use Setono\SyliusGoogleAdsPlugin\Workflow\ConversionWorkflow;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension;
Expand All @@ -25,6 +26,10 @@ public function load(array $configs, ContainerBuilder $container): void
$config = $this->processConfiguration($this->getConfiguration([], $container), $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));

$container->registerForAutoconfiguration(ConversionProcessorInterface::class)
->addTag('setono_sylius_google_ads.conversion_processor')
;

$container->registerForAutoconfiguration(QualificationVoterInterface::class)
->addTag('setono_sylius_google_ads.qualification_processor')
;
Expand Down
17 changes: 17 additions & 0 deletions src/Event/PreSetClickConversionDataEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusGoogleAdsPlugin\Event;

use Setono\SyliusGoogleAdsPlugin\Model\ConversionInterface;
use Symfony\Contracts\EventDispatcher\Event;

final class PreSetClickConversionDataEvent extends Event
{
public function __construct(
public readonly ConversionInterface $conversion,
public array $data,
) {
}
}
20 changes: 20 additions & 0 deletions src/Event/PreSetClickConversionUserIdentifiersEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusGoogleAdsPlugin\Event;

use Google\Ads\GoogleAds\V13\Common\UserIdentifier;
use Setono\SyliusGoogleAdsPlugin\Model\ConversionInterface;
use Symfony\Contracts\EventDispatcher\Event;

final class PreSetClickConversionUserIdentifiersEvent extends Event
{
/** @var list<UserIdentifier> */
public array $userIdentifiers = [];

public function __construct(
public readonly ConversionInterface $conversion,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusGoogleAdsPlugin\EventSubscriber\ConversionProcessing;

use Google\Ads\GoogleAds\V13\Common\UserIdentifier;
use Google\Ads\GoogleAds\V13\Enums\UserIdentifierSourceEnum\UserIdentifierSource;
use Setono\SyliusGoogleAdsPlugin\Event\PreSetClickConversionUserIdentifiersEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class AddEmailUserIdentifierSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
PreSetClickConversionUserIdentifiersEvent::class => 'add',
];
}

public function add(PreSetClickConversionUserIdentifiersEvent $event): void
{
$email = $event->conversion->getOrder()?->getCustomer()?->getEmailCanonical();
if (null === $email) {
return;
}

$email = strtolower(trim($email));
$emailParts = explode('@', $email);
if (count($emailParts) !== 2) {
return;
}

// Google Ads requires removal of any '.' characters preceding "gmail.com" or "googlemail.com".
if (preg_match('/^(gmail|googlemail)\.com\s*/', $emailParts[1])) {
$emailParts[0] = str_replace('.', '', $emailParts[0]);
$email = sprintf('%s@%s', $emailParts[0], $emailParts[1]);
}

$event->userIdentifiers[] = new UserIdentifier([
'hashed_email' => hash('sha256', $email),
'user_identifier_source' => UserIdentifierSource::FIRST_PARTY,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusGoogleAdsPlugin\EventSubscriber\ConversionProcessing;

use Brick\PhoneNumber\PhoneNumber;
use Brick\PhoneNumber\PhoneNumberFormat;
use Google\Ads\GoogleAds\V13\Common\UserIdentifier;
use Google\Ads\GoogleAds\V13\Enums\UserIdentifierSourceEnum\UserIdentifierSource;
use Setono\SyliusGoogleAdsPlugin\Event\PreSetClickConversionUserIdentifiersEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class AddPhoneNumberUserIdentifierSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
PreSetClickConversionUserIdentifiersEvent::class => 'add',
];
}

public function add(PreSetClickConversionUserIdentifiersEvent $event): void
{
$phoneNumber = $event->conversion->getOrder()?->getCustomer()?->getPhoneNumber() ?? $event->conversion->getOrder()?->getBillingAddress()?->getPhoneNumber();
if (null === $phoneNumber) {
return;
}

$countryCode = $event->conversion->getOrder()?->getBillingAddress()?->getCountryCode();
if (null !== $countryCode) {
return;
}

try {
$phoneNumberParsed = PhoneNumber::parse($phoneNumber, $countryCode);
} catch (\Throwable) {
return;
}

$event->userIdentifiers[] = new UserIdentifier([
'hashed_phone_number' => hash('sha256', $phoneNumberParsed->format(PhoneNumberFormat::E164)),
'user_identifier_source' => UserIdentifierSource::FIRST_PARTY,
]);
}
}
1 change: 1 addition & 0 deletions src/Resources/config/services/conversion_processor.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<argument type="service" id="state_machine.setono_sylius_google_ads__conversion"/>
<argument type="service" id="setono_sylius_google_ads.factory.google_ads_client"/>
<argument type="service" id="setono_sylius_google_ads.repository.connection_mapping"/>
<argument type="service" id="event_dispatcher"/>

<tag name="setono_sylius_google_ads.conversion_processor" priority="90"/>
</service>
Expand Down
12 changes: 12 additions & 0 deletions src/Resources/config/services/event_subscriber.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,17 @@

<tag name="kernel.event_subscriber"/>
</service>

<service id="setono_sylius_google_ads.event_subscriber.add_email_user_identifier"
class="Setono\SyliusGoogleAdsPlugin\EventSubscriber\ConversionProcessing\AddEmailUserIdentifierSubscriber">

<tag name="kernel.event_subscriber"/>
</service>

<service id="setono_sylius_google_ads.event_subscriber.add_phone_number_user_identifier"
class="Setono\SyliusGoogleAdsPlugin\EventSubscriber\ConversionProcessing\AddPhoneNumberUserIdentifierSubscriber">

<tag name="kernel.event_subscriber"/>
</service>
</services>
</container>

0 comments on commit baabca2

Please sign in to comment.