svenpetersen / ux-driver
Product tours with driver.js for Symfony UX, with the seen state stored per user
Requires
- php: >=8.4
- symfony/config: ^8.0
- symfony/dependency-injection: ^8.0
- symfony/framework-bundle: ^8.0
- symfony/http-kernel: ^8.0
- symfony/routing: ^8.0
- symfony/security-bundle: ^8.0
- symfony/security-csrf: ^8.0
- symfony/stimulus-bundle: ^3.0
- symfony/twig-bundle: ^8.0
- twig/twig: ^3.8
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.0 || ^13.0
- symfony/asset-mapper: ^8.0
- symfony/browser-kit: ^8.0
- symfony/css-selector: ^8.0
Suggests
- symfony/asset-mapper: To load the Stimulus controller through AssetMapper instead of Webpack Encore
Provides
None
Conflicts
None
Replaces
None
README
Product tours with driver.js for Symfony UX, across several pages if need be. Whether a user has seen a tour is stored per user, not in the browser.
Inspired by pentiminax/ux-driver. The persistence
layer follows bentools/webpush-bundle: the bundle defines interfaces, the application maps
them onto its own user entity.
Requirements
- PHP 8.4, Symfony 8 (SecurityBundle, StimulusBundle 3), Twig 3.8
- driver.js ^1.8, Stimulus ^3
- AssetMapper or Webpack Encore (with
@symfony/stimulus-bridge)
What the application provides
-
An entity implementing
SvenPetersen\UX\Driver\Persistence\TourViewInterface(getUser(),getTourId(),getSeenAt()), with its mapping and migration. -
A manager implementing
TourViewManagerInterface(find(),factory(),save()), registered with#[AsTourViewManager(userClass: User::class)]. One manager per user class. -
The route to the endpoint, once per firewall whose users see tours:
// config/routes/ux_driver.php return static function (RoutingConfigurator $routes): void { $routes->import('@SvenPetersenUXDriverBundle/config/routes.php')->prefix('/admin'); };
A second import (e.g.
->prefix('/counter')->namePrefix('counter_')) needs that route name as theseenRouteargument ofux_driver_tour(). -
Tours: services implementing
TourProviderInterface.getTourId()is the key the "seen" state is stored under — a new version of a tour needs a new id.
In a template
<button type="button" {{ ux_driver_tour('dashboard-whats-new-1.2') }}> Take the tour </button>
The button carries the tour: on the first visit it starts by itself (if the server reports that the user has not seen it yet), afterwards on click. Every page with a step on it renders the same button; the controller only shows the steps of the current page.
Button and progress texts default to English and can be overridden per tour:
{{ ux_driver_tour('dashboard-whats-new-1.2', {
next: 'Weiter',
previous: 'Zurück',
done: 'Fertig',
progress: '{{current}} von {{total}}',
}) }}
Installation
composer require svenpetersen/ux-driver
Symfony Flex registers the bundle and wires the Stimulus controller into
assets/controllers.json.
- AssetMapper: Flex also adds
driver.jsanddriver.js/dist/driver.csstoimportmap.php; the bundle exposes itsassets/distas@svenpetersen/ux-driver. The controller is loaded lazily, together with driver.js' stylesheet. - Webpack Encore: Flex adds
@svenpetersen/ux-drivertopackage.json; runyarn install --force(ornpm install --force) and make suredriver.jsis installed. The stylesheet comes in throughautoimport.
Building the controller
assets/dist/controller.js is built from assets/src/controller.ts and committed:
cd assets && npm install && npm run build
An application consuming the bundle through a path repository needs yarn install --force
afterwards to refresh its copy in node_modules.
Security notes
- The endpoint only stores ids of tours that exist, for the logged-in user, and requires a
CSRF token (sent by the controller in the
X-CSRF-Tokenheader). CSRF protection with a session must therefore be enabled (framework.csrf_protection). - Step titles and descriptions are plain text; the controller escapes them before driver.js writes them into the page.
Development
composer install vendor/bin/phpunit vendor/bin/phpstan analyse
The tests boot a minimal kernel (tests/Fixtures/TestKernel.php) with an in-memory user and an
in-memory TourViewManagerInterface, so they run without a database.
The Stimulus controller is tested with Vitest in jsdom, against the real driver.js:
cd assets npm install npm test
License
MIT — see LICENSE.