diff --git a/composer.json b/composer.json index fc81be4..8eb562e 100644 --- a/composer.json +++ b/composer.json @@ -6,14 +6,13 @@ "php": "^8.0", "jean85/pretty-package-versions": "^1.0|^2.0", "posthog/posthog-php": "^3.0", - "symfony/config": "^5.4.0|^6.0", - "symfony/console": "^5.4.0|^6.0", - "symfony/dependency-injection": "^5.4.0|^6.0", - "symfony/event-dispatcher": "^5.4.0|^6.0", - "symfony/http-kernel": "^5.4.0|^6.0", + "symfony/config": "^6.0|^7.0", + "symfony/dependency-injection": "^6.0|^7.0", + "symfony/event-dispatcher": "^6.0|^7.0", + "symfony/http-kernel": "^6.0|^7.0", "symfony/polyfill-php80": "^1.28", - "symfony/security-core": "^5.4.0|^6.0", - "symfony/security-http": "^5.4.0|^6.0" + "symfony/security-core": "^6.0|^7.0", + "symfony/security-http": "^6.0|^7.0" }, "license": "MIT", "autoload": { @@ -45,15 +44,16 @@ "phpstan/phpstan-phpunit": "^1.3", "phpstan/phpstan-symfony": "^1.3", "phpunit/phpunit": "^8.5.14|^9.3.9|^10.4", - "symfony/browser-kit": "^5.4.0|^6.0", - "symfony/cache": "^5.4.0|^6.0", - "symfony/dom-crawler": "^5.4.0|^6.0", - "symfony/framework-bundle": "^5.4.0|^6.0", - "symfony/http-client": "^5.4.0|^6.0", + "symfony/browser-kit": "^6.0|^7.0", + "symfony/cache": "^6.0|^7.0", + "symfony/console": "^6.0|^7.0", + "symfony/dom-crawler": "^6.0|^7.0", + "symfony/framework-bundle": "^6.0|^7.0", + "symfony/http-client": "^6.0|^7.0", "symfony/monolog-bundle": "^3.4", - "symfony/phpunit-bridge": "^5.4.0|^6.0", - "symfony/process": "^5.4.0|^6.0", - "symfony/yaml": "^5.4.0|^6.0", + "symfony/phpunit-bridge": "^6.0|^7.0", + "symfony/process": "^6.0|^7.0", + "symfony/yaml": "^6.0|^7.0", "vimeo/psalm": "^5.15" }, "scripts": { @@ -63,6 +63,9 @@ "phpcs": [ "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run" ], + "phpcs:fix": [ + "vendor/bin/php-cs-fixer fix" + ], "phpstan": [ "vendor/bin/phpstan analyse" ], diff --git a/src/Adapter/PostHogAdapter.php b/src/Adapter/PostHogAdapter.php index 9ec021a..4c6316e 100644 --- a/src/Adapter/PostHogAdapter.php +++ b/src/Adapter/PostHogAdapter.php @@ -6,9 +6,13 @@ use PostHog\Client; use PostHog\PostHog as PH; -use PostHog\PostHogBundle\Exception\NotInitializedException; +use PostHog\PostHogBundle\Model\AliasMessage; +use PostHog\PostHogBundle\Model\GroupIdentifyMessage; +use PostHog\PostHogBundle\Model\IdentifyMessage; +use PostHog\PostHogBundle\Model\Message; +use PostHog\PostHogBundle\PostHogInterface; -class PostHogAdapter +class PostHogAdapter implements PostHogInterface { private Client $client; @@ -18,66 +22,87 @@ public function __construct(Client $client) PH::init(client: $this->client); } - public function capture(array $message): bool + public function capture(Message $message): bool { - return PH::capture($message); + return $this->client->capture($message->toArray()); } - public function identify(array $message): bool + public function identify(IdentifyMessage $message): bool { - return PH::identify($message); + return $this->client->identify($message->toArray()); } - public function groupIdentify(array $message): bool + public function groupIdentify(GroupIdentifyMessage $message): bool { - return PH::groupIdentify($message); + return $this->capture(new Message( + event: '$groupidentify', + distinctId: sprintf('$%s_%s', $message->groupType, $message->groupKey), + properties: [ + '$group_type' => $message->groupType, + '$group_key' => $message->groupKey, + '$group_set' => $message->groupProperties, + ], + )); } - public function isFeatureEnabled(string $key, string $distinctId, array $groups = [], array $personProperties = [], array $groupProperties = [], bool $onlyEvaluateLocally = false, bool $sendFeatureFlagEvents = true): null|bool + /** + * @throws \Exception + */ + public function isFeatureEnabled(string $key, string $distinctId, array $groups = [], array $personProperties = [], array $groupProperties = [], bool $onlyEvaluateLocally = false, bool $sendFeatureFlagEvents = true): bool|null { - return PH::isFeatureEnabled($key, $distinctId, $groups, $personProperties, $groupProperties, $onlyEvaluateLocally, $sendFeatureFlagEvents); + return $this->client->isFeatureEnabled($key, $distinctId, $groups, $personProperties, $groupProperties, $onlyEvaluateLocally, $sendFeatureFlagEvents); } - public function getFeatureFlag(string $key, string $distinctId, array $groups = [], array $personProperties = [], array $groupProperties = [], bool $onlyEvaluateLocally = false, bool $sendFeatureFlagEvents = true): null|bool|string + /** + * @throws \Exception + */ + public function getFeatureFlag(string $key, string $distinctId, array $groups = [], array $personProperties = [], array $groupProperties = [], bool $onlyEvaluateLocally = false, bool $sendFeatureFlagEvents = true): bool|string|null { - return PH::getFeatureFlag($key, $distinctId, $groups, $personProperties, $groupProperties, $onlyEvaluateLocally, $sendFeatureFlagEvents); + return $this->client->getFeatureFlag($key, $distinctId, $groups, $personProperties, $groupProperties, $onlyEvaluateLocally, $sendFeatureFlagEvents); } - public function getAllFlags(string $distinctId, array $groups = [], array $personProperties = [], array $groupProperties = [], bool $onlyEvaluateLocally = false) + /** + * @throws \Exception + */ + public function getAllFlags(string $distinctId, array $groups = [], array $personProperties = [], array $groupProperties = [], bool $onlyEvaluateLocally = false): array { - return PH::getAllFlags($distinctId, $groups, $personProperties, $groupProperties, $onlyEvaluateLocally); + return $this->client->getAllFlags($distinctId, $groups, $personProperties, $groupProperties, $onlyEvaluateLocally); } + /** + * @throws \Exception + */ public function fetchFeatureVariants(string $distinctId, array $groups = []): array { - return PH::fetchFeatureVariants($distinctId, $groups); + return $this->client->fetchFeatureVariants($distinctId, $groups); } - public function alias(array $message): bool + public function alias(AliasMessage $message): bool { - return PH::alias($message); + return $this->client->alias($message->toArray()); } - public function raw(array $message): bool + public function raw(array $message): mixed { - return PH::raw($message); + return $this->client->raw($message); } - /** - * @return bool - */ public function flush(): bool { - $result = PH::flush(); + $result = $this->client->flush(); - if (is_bool($result)) { + if (\is_bool($result)) { return $result; } - if (is_string($result)) { + if (\is_string($result)) { $decoded = json_decode($result, true); - return array_key_exists('status', $decoded); + if (!\is_array($decoded)) { + return true; + } + + return \array_key_exists('status', $decoded); } return false; diff --git a/src/Client/Options/Options.php b/src/Client/Options/Options.php index f0fce4a..b5aaa92 100644 --- a/src/Client/Options/Options.php +++ b/src/Client/Options/Options.php @@ -13,8 +13,14 @@ class Options private bool $debug = false; private int $timeout = 10000; + /** + * @var array + */ private array $consumerOptions = []; + /** + * @return array + */ public function getOptions(): array { $options = $this->consumerOptions; diff --git a/src/Command/PostHogTestCommand.php b/src/Command/PostHogTestCommand.php index 68b524a..e38bbaf 100644 --- a/src/Command/PostHogTestCommand.php +++ b/src/Command/PostHogTestCommand.php @@ -4,7 +4,9 @@ namespace PostHog\PostHogBundle\Command; -use PostHog\PostHogBundle\Adapter\PostHogAdapter; +use PostHog\PostHogBundle\Model\IdentifyMessage; +use PostHog\PostHogBundle\Model\Message; +use PostHog\PostHogBundle\PostHogBundle; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -13,21 +15,25 @@ class PostHogTestCommand extends Command { protected function execute(InputInterface $input, OutputInterface $output): int { - $posthog = PostHogAdapter::getInstance(); + $posthog = PostHogBundle::getInstance(); $output->writeln('Identifying...'); $whoami = shell_exec('whoami'); - $posthog->identify(['distinctId' => $whoami]); + if (!\is_string($whoami)) { + return self::FAILURE; + } + $posthog->identify(new IdentifyMessage($whoami)); $output->writeln('Sending test message...'); - $captured = $posthog->capture([ - 'distinctId' => $whoami, - 'event' => 'test-event' - ]) + return $posthog->capture(new Message( + event: 'test_event', + distinctId: $whoami, + properties: [ + 'test' => 'test', + ], + )) ? self::SUCCESS : self::FAILURE; - - return $captured; } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 67d83be..c50e82a 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -21,6 +21,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('enabled')->defaultValue(false)->end() ->scalarNode('user_prefix')->defaultValue('user')->end() ->end(); + return $builder; } } diff --git a/src/DependencyInjection/PostHogExtension.php b/src/DependencyInjection/PostHogExtension.php index 22ef9d5..1281171 100644 --- a/src/DependencyInjection/PostHogExtension.php +++ b/src/DependencyInjection/PostHogExtension.php @@ -26,6 +26,9 @@ public function getXsdValidationBasePath(): string return __DIR__ . '/../../config/schema/posthog-1.0.xsd'; } + /** + * @param array&array{options: array} $mergedConfig + */ protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void { $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); @@ -35,7 +38,7 @@ protected function loadInternal(array $mergedConfig, ContainerBuilder $container } /** - * @param array $config + * @param array&array{options: array} $config */ private function registerConfiguration(ContainerBuilder $container, array $config): void { @@ -46,7 +49,6 @@ private function registerConfiguration(ContainerBuilder $container, array $confi ->addMethodCall('setSdkIdentifier', [PostHogBundle::SDK_IDENTIFIER]) ->addMethodCall('setSdkVersion', [PostHogBundle::SDK_VERSION]) ->addMethodCall('setApiKey', [$options['key']]) - ->addMethodCall('setOptions', [new Reference()]) ; $container diff --git a/src/EventListener/LoginListener.php b/src/EventListener/LoginListener.php index 9d03cb9..35b3a3f 100644 --- a/src/EventListener/LoginListener.php +++ b/src/EventListener/LoginListener.php @@ -4,7 +4,7 @@ namespace PostHog\PostHogBundle\EventListener; -use PostHog\PostHog; +use PostHog\PostHogBundle\Model\IdentifyMessage; use PostHog\PostHogBundle\PostHogInterface; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -17,15 +17,10 @@ final class LoginListener { - private PostHogInterface $postHog; - private ?TokenStorageInterface $tokenStorage; - public function __construct( - PostHogInterface $postHog, - ?TokenStorageInterface $tokenStorage + private PostHogInterface $postHog, + private ?TokenStorageInterface $tokenStorage ) { - $this->tokenStorage = $tokenStorage; - $this->postHog = $postHog; } /** @@ -70,19 +65,21 @@ private function updateUserContext(TokenInterface $token): void return; } - $message = [ - 'distinctId' => $this->getUserIdentifier($token->getUser()), - ]; + $identifier = $this->getUserIdentifier($token->getUser()); + + if (null === $identifier) { + return; + } + + $message = new IdentifyMessage($identifier); $impersonatorUser = $this->getImpersonatorUser($token); if (null !== $impersonatorUser) { - $message['$set'] = [ - 'impersonator_username' => $impersonatorUser, - ]; + $message->set['impersonator_username'] = $impersonatorUser; } - PostHog::identify($message); + $this->postHog->identify($message); } private function isTokenAuthenticated(TokenInterface $token): bool @@ -94,7 +91,7 @@ private function isTokenAuthenticated(TokenInterface $token): bool return null !== $token->getUser(); } - private function getUserIdentifier($user): ?string + private function getUserIdentifier(mixed $user): ?string { if ($user instanceof UserInterface) { if (method_exists($user, 'getUserIdentifier')) { @@ -102,7 +99,7 @@ private function getUserIdentifier($user): ?string } if (method_exists($user, 'getUsername')) { - return $user->getUsername(); + return (string) $user->getUsername(); } } @@ -132,7 +129,7 @@ protected function isMainRequest(KernelEvent $event): bool return $event->isMainRequest(); } if (method_exists($event, 'isMasterRequest')) { - return $event->isMasterRequest(); + return (bool) $event->isMasterRequest(); } return true; diff --git a/src/EventListener/RequestListener.php b/src/EventListener/RequestListener.php index 23efaa6..52cb3a5 100644 --- a/src/EventListener/RequestListener.php +++ b/src/EventListener/RequestListener.php @@ -4,7 +4,6 @@ namespace PostHog\PostHogBundle\EventListener; -use PostHog\PostHogBundle\Adapter\PostHogAdapter; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -13,7 +12,6 @@ final class RequestListener { public function __construct() { - } public function handleKernelRequestEvent(RequestEvent $event): void diff --git a/src/Exception/NotInitializedException.php b/src/Exception/NotInitializedException.php index 8b9b16b..794bf07 100644 --- a/src/Exception/NotInitializedException.php +++ b/src/Exception/NotInitializedException.php @@ -4,11 +4,9 @@ namespace PostHog\PostHogBundle\Exception; -use Throwable; - class NotInitializedException extends \Exception { - public function __construct(int $code = 0, ?Throwable $previous = null) + public function __construct(int $code = 0, ?\Throwable $previous = null) { parent::__construct('Client is not initialized, please initialze first.', $code, $previous); } diff --git a/src/Model/AliasMessage.php b/src/Model/AliasMessage.php new file mode 100644 index 0000000..82a111a --- /dev/null +++ b/src/Model/AliasMessage.php @@ -0,0 +1,22 @@ + $this->distinctId, + 'alias' => $this->alias, + ]; + } +} diff --git a/src/Model/GroupIdentifyMessage.php b/src/Model/GroupIdentifyMessage.php new file mode 100644 index 0000000..3f99775 --- /dev/null +++ b/src/Model/GroupIdentifyMessage.php @@ -0,0 +1,25 @@ + $groupProperties + */ + public function __construct( + public string $groupType, + public string $groupKey, + public array $groupProperties, + ) { + } + + public function toArray(): array + { + return [ + 'properties' => $this->groupProperties, + ]; + } +} diff --git a/src/Model/IdentifyMessage.php b/src/Model/IdentifyMessage.php new file mode 100644 index 0000000..efd1dc5 --- /dev/null +++ b/src/Model/IdentifyMessage.php @@ -0,0 +1,22 @@ + $this->distinctId, + '$set' => $this->set, + ]; + } +} diff --git a/src/Model/Message.php b/src/Model/Message.php new file mode 100644 index 0000000..9effa6f --- /dev/null +++ b/src/Model/Message.php @@ -0,0 +1,23 @@ + $this->distinctId, + 'properties' => $this->properties, + ]; + } +} diff --git a/src/PostHogBundle.php b/src/PostHogBundle.php index 1adfa16..1ce8de3 100644 --- a/src/PostHogBundle.php +++ b/src/PostHogBundle.php @@ -31,7 +31,7 @@ public static function getInstance(): PostHogAdapter return self::$instance; } - public static function initialize(Client $client = null): void + public static function initialize(?Client $client = null): void { self::$instance = new PostHogAdapter($client ?? new Client( getenv(\PostHog\PostHog::ENV_API_KEY) ?: '', diff --git a/src/PostHogInterface.php b/src/PostHogInterface.php new file mode 100644 index 0000000..facc4ad --- /dev/null +++ b/src/PostHogInterface.php @@ -0,0 +1,58 @@ + $personProperties + * @param array $groupProperties + */ + public function isFeatureEnabled(string $key, string $distinctId, array $groups, array $personProperties, array $groupProperties, bool $onlyEvaluateLocally, bool $sendFeatureFlagEvents): bool|null; + + /** + * @param string[] $groups + * @param array $personProperties + * @param array $groupProperties + */ + public function getFeatureFlag(string $key, string $distinctId, array $groups, array $personProperties, array $groupProperties, bool $onlyEvaluateLocally, bool $sendFeatureFlagEvents): bool|string|null; + + /** + * @param string[] $groups + * @param array $personProperties + * @param array $groupProperties + * + * @return string[] + */ + public function getAllFlags(string $distinctId, array $groups, array $personProperties, array $groupProperties, bool $onlyEvaluateLocally): array; + + /** + * @param string[] $groups + * + * @return string[] + */ + public function fetchFeatureVariants(string $distinctId, array $groups): array; + + public function alias(AliasMessage $message): bool; + + /** + * @param array $message + */ + public function raw(array $message): mixed; + + public function flush(): bool; +} diff --git a/src/functions.php b/src/functions.php index 5f61699..3fbca10 100644 --- a/src/functions.php +++ b/src/functions.php @@ -5,9 +5,8 @@ namespace PostHog\PostHogBundle; use PostHog\Client; -use PostHog\PostHogBundle\Adapter\PostHogAdapter; -function init(Client $client = null): void +function init(?Client $client = null): void { PostHogBundle::initialize($client); } diff --git a/tests/Command/TestPostHogCommandTest.php b/tests/Command/TestPostHogCommandTest.php index 45a610a..1597a7a 100644 --- a/tests/Command/TestPostHogCommandTest.php +++ b/tests/Command/TestPostHogCommandTest.php @@ -5,12 +5,12 @@ namespace PostHog\PostHogBundle\Tests\Command; use PostHog\Client; -use PostHog\PostHog; use PostHog\PostHogBundle\Command\PostHogTestCommand; use PostHog\PostHogBundle\Tests\BaseTestCase; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandTester; + use function PostHog\PostHogBundle\init; class TestPostHogCommandTest extends BaseTestCase @@ -23,7 +23,7 @@ protected function setUp(): void new Client( '', [ - 'host' => 'https://app.posthog.com' + 'host' => 'https://app.posthog.com', ] ) ); diff --git a/tests/End2End/App/Kernel.php b/tests/End2End/App/Kernel.php index 0fecd06..0654e6c 100644 --- a/tests/End2End/App/Kernel.php +++ b/tests/End2End/App/Kernel.php @@ -9,7 +9,6 @@ class Kernel extends SymfonyKernel { - public function registerBundles(): iterable { return [ @@ -23,6 +22,7 @@ public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__ . '/config.yml'); + if (self::VERSION_ID >= 50000) { $loader->load(__DIR__ . '/deprecations_for_5.yml'); } diff --git a/tests/End2End/End2EndTest.php b/tests/End2End/End2EndTest.php index 52c910c..4b67a58 100644 --- a/tests/End2End/End2EndTest.php +++ b/tests/End2End/End2EndTest.php @@ -8,7 +8,6 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Console\Tester\CommandTester; -use function PostHog\PostHogBundle\init; class End2EndTest extends WebTestCase {