承接 setono/client-bundle 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

setono/client-bundle

Composer 安装命令:

composer require setono/client-bundle

包简介

Integrate the client library into your Symfony application

README 文档

README

Latest Version Software License Build Status Code Coverage

Recognize returning visitors in your Symfony application and attach your own metadata to each of them.

The bundle assigns every visitor a stable client id, stored in a first‑party cookie (setono_client_id by default) that also remembers when the visitor was first and last seen. On top of that you can persist arbitrary metadata per client — for example the first Google click id (gclid) a visitor arrived with, an A/B test variant, or a referrer. Metadata is lazy: if you never read or write it, the bundle never touches the database.

Typical use cases: first‑party analytics, marketing attribution, returning‑visitor personalization.

Contents

Requirements

Version
PHP >= 8.1
Symfony ^6.4 or ^7.0
Doctrine ORM ^2.0 or ^3.0

Installation

composer require setono/client-bundle

If you use Symfony Flex the bundle is registered automatically. Otherwise, enable it manually in config/bundles.php:

return [
    // ...
    Setono\ClientBundle\SetonoClientBundle::class => ['all' => true],
];

The bundle maps a Metadata entity (table setono_client__metadata). If you intend to store metadata, create the table with Doctrine Migrations:

php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate

If you only need the client id cookie and never store metadata, the table is never queried — but generating it now keeps things simple if you add metadata later.

Usage

Get the current client

The simplest way to access the current visitor is to type‑hint Setono\Client\Client in a controller — the bundle resolves it for you:

use Setono\Client\Client;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

final class HomeController extends AbstractController
{
    public function index(Client $client): Response
    {
        return $this->render('home.html.twig', [
            'clientId' => $client->id,
        ]);
    }
}

Anywhere else (services, subscribers) inject ClientContextInterface:

use Setono\ClientBundle\Context\ClientContextInterface;

final class SomeService
{
    public function __construct(private readonly ClientContextInterface $clientContext)
    {
    }

    public function __invoke(): void
    {
        $client = $this->clientContext->getClient();
        // $client->id, $client->metadata
    }
}

Read and write metadata

$client->metadata is a key/value store. Reading a key for the first time lazily loads the metadata from the database; writing marks it dirty so it is persisted at the end of the request.

$metadata = $client->metadata;

$metadata->set('variant', 'B');           // store a value
$metadata->set('promo', 'X', ttl: 3600);  // optional TTL in seconds

$metadata->has('variant');                // true
$metadata->get('variant');                // 'B' — throws if the key is missing, so guard with has()
$metadata->remove('variant');

foreach ($metadata as $key => $value) {   // iterate everything
    // ...
}

Set metadata from a request

A common pattern is to capture data from the incoming request — here, the Google click id:

use Setono\ClientBundle\Context\ClientContextInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

final class GoogleClickIdSubscriber implements EventSubscriberInterface
{
    public function __construct(private readonly ClientContextInterface $clientContext)
    {
    }

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::REQUEST => 'capture'];
    }

    public function capture(RequestEvent $event): void
    {
        if (!$event->isMainRequest() || !$event->getRequest()->query->has('gclid')) {
            return;
        }

        $this->clientContext->getClient()->metadata->set(
            'google_click_id',
            $event->getRequest()->query->get('gclid'),
        );
    }
}

Access the cookie directly

The cookie stores the client id plus the first/last seen timestamps. Read it through CookieProviderInterface:

use Setono\ClientBundle\CookieProvider\CookieProviderInterface;

final class SomeService
{
    public function __construct(private readonly CookieProviderInterface $cookieProvider)
    {
    }

    public function __invoke(): void
    {
        $cookie = $this->cookieProvider->getCookie();
        if (null === $cookie) {
            return; // visitor has no cookie yet
        }

        $cookie->clientId;    // the client id
        $cookie->firstSeenAt; // unix timestamp of first visit
        $cookie->lastSeenAt;  // unix timestamp of the previous visit
    }
}

The cookie is intentionally not HttpOnly, so you can also read it from JavaScript (e.g. to send the client id to a third‑party tag).

Skip storing the cookie (consent)

Because the cookie identifies a visitor, you may need consent before storing it. Listen to PreStoreCookieEvent (dispatched on the response, before the cookie is written) and set $event->store = false to skip it for that request:

use Setono\ClientBundle\Event\PreStoreCookieEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class CookieConsentSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [PreStoreCookieEvent::class => 'onPreStore'];
    }

    public function onPreStore(PreStoreCookieEvent $event): void
    {
        if (!$this->hasConsent($event->request)) {
            $event->store = false;
        }
    }

    // ...
}

Configuration

All options are optional; the defaults are shown below:

setono_client:
    cookie:
        # Name of the cookie that holds the client id.
        # NOTE: changing this makes every visitor with the old cookie name look like a new client.
        name: setono_client_id

        # Cookie lifetime, expressed as any string strtotime() can parse.
        expiration: '+365 days'

    # Entity used to persist metadata. Override it with your own class to add mapped fields
    # or behaviour; it must implement Setono\ClientBundle\Entity\MetadataInterface.
    metadata_class: Setono\ClientBundle\Entity\Metadata

How it works

  • Cookie — on each main response, StoreCookieSubscriber writes/refreshes the cookie and bumps lastSeenAt. A new client id is generated only when no valid cookie is present.
  • Lazy metadata$client->metadata is a lazy ghost object. The database is queried only the first time you read a value, and a row is written only at the end of the request, and only if the metadata was actually changed.
  • Resolution chainClientContextInterface is built from a chain of decorating services (CachedClientContextCookieBasedClientContextDefaultClientContext). The cached layer memoizes the client per request; the cookie‑based layer builds it from the cookie; the default layer creates a fresh, anonymous client.

Extending the bundle

Every moving part is a service behind an interface, so you can swap or decorate it:

Concern Interface
Resolving the current client Setono\ClientBundle\Context\ClientContextInterface
Reading the cookie Setono\ClientBundle\CookieProvider\CookieProviderInterface
Loading metadata Setono\ClientBundle\MetadataProvider\MetadataProviderInterface
Persisting metadata Setono\ClientBundle\MetadataPersister\MetadataPersisterInterface
Metadata entity Setono\ClientBundle\Entity\MetadataInterface

To add behaviour, decorate the relevant service rather than replacing it outright — that is exactly how the bundle composes its own defaults.

Contributing

git clone https://github.com/Setono/client-bundle.git
cd client-bundle
composer install

Quality tooling (also run in CI):

Command Purpose
composer phpunit Run the test suite
composer analyse Static analysis (PHPStan, level: max)
composer check-style / composer fix-style Coding standard (ECS)
composer infection Mutation testing (requires a coverage driver)
vendor/bin/composer-dependency-analyser Detect unused/undeclared dependencies

License

This bundle is released under the MIT License.

setono/client-bundle 适用场景与选型建议

setono/client-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 16.26k 次下载、GitHub Stars 达 1, 最近一次更新时间为 2024 年 03 月 12 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 setono/client-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 setono/client-bundle 我们能提供哪些服务?
定制开发 / 二次开发

基于 setono/client-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 16.26k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 6
  • 依赖项目数: 5
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 1
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-03-12