Hotwire Turbo integration for Symfony

Overview

Symfony UX Turbo

Symfony UX Turbo is a Symfony bundle integrating the Hotwire Turbo library in Symfony applications. It is part of the Symfony UX initiative.

Symfony UX Turbo allows having the same user experience as with Single Page Apps but without having to write a single line of JavaScript!

Symfony UX Turbo also integrates with Symfony Mercure or any other transports to broadcast DOM changes to all currently connected users!

You're in a hurry? Take a look at the chat example to discover the full potential of Symfony UX Turbo.

Installation

Symfony UX Turbo requires PHP 7.2+ and Symfony 5.2+.

Install this bundle using Composer and Symfony Flex:

composer require symfony/ux-turbo

# Don't forget to install the JavaScript dependencies as well and compile
yarn install --force
yarn encore dev

Usage

Accelerating Navigation with Turbo Drive

Turbo Drive enhances page-level navigation. It watches for link clicks and form submissions, performs them in the background, and updates the page without doing a full reload. This gives you the "single-page-app" experience without major changes to your code!

Turbo Drive is automatically enabled when you install Symfony UX Turbo. And while you don't need to make major changes to get things to work smoothly, there are 3 things to be aware of:

1. Make sure your JavaScript is Turbo-ready

Because navigation no longer results in full page refreshes, you may need to adjust your JavaScript to work properly. The best solution is to write your JavaScript using Stimulus or something similar.

We also recommend that you place your script tags live inside your head tag so that they aren't reloaded on every navigation (Turbo re-executes any script tags inside body on every navigation). Add a defer attribute to each script tag to prevent it from blocking the page load. See Moving <script> inside and the "defer" Attribute for more info.

2. Reloading When a JavaScript/CSS File Changes

Turbo drive can automatically perform a full refresh if the content of one of your CSS or JS files changes, to ensure that your users always have the latest version.

To enable this, first verify that you have versioning enabled in Encore so that your filenames change when the file contents change:

// webpack.config.js

Encore.
    // ...
    .enableVersioning(Encore.isProduction())

Then add a data-turbo-track="reload" attribute to all of your script and link tags:

# config/packages/webpack_encore.yaml
webpack_encore:
    # ...

    script_attributes:
        defer: true
        'data-turbo-track': reload
    link_attributes:
        'data-turbo-track': reload

For more info, see: Turbo: Reloading When Assets Change

3. Form Response Code Changes

Turbo Drive also converts form submissions to AJAX calls. To get it to work, you do need to adjust your code to return a 422 status code on a validation error (instead of a 200).

If you're using Symfony 5.3, the new handleForm() shortcut takes care of this automatically:

/**
 * @Route("/product/new", name="product_new")
 */
public function newProduct(Request $request): Response
{
    return $this->handleForm(
        $this->createForm(ProductFormType::class, null, [
            'action' => $this->generateUrl('product_new'),
        ]),
        $request,
        function (FormInterface $form) {
            // save...

            return $this->redirectToRoute(
                'product_list',
                [],
                Response::HTTP_SEE_OTHER
            );
        },
        function (FormInterface $form) {
            return $this->render('product/new.html.twig', [
                'form' => $form->createView(),
            ]);
        }
    );
}

If you're not using the handleForm() shortcut, adjust your code manually:

/**
 * @Route("/product/new")
 */
public function newProduct(Request $request): Response
{
    $form = $this->createForm(ProductFormType::class);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        // save...

-        return $this->redirectToRoute('product_list');
+        return $this->redirectToRoute('product_list', [], Response::HTTP_SEE_OTHER);
    }

+    $response = new Response(null, $form->isSubmitted() ? 422 : 200);

    return $this->render('product/new.html.twig', [
        'form' => $form->createView()
-    ]);
+    ], $response);
}

This changes the response status code to 422 on validation error, which tells Turbo Drive that the form submit failed and it should re-render with the errors. This also changes the redirect status code from 302 (the default) to 303 (HTTP_SEE_OTHER). That's not required for Turbo Drive, but 303 is "more correct" for this situation.

NOTE: When your form contains more than one submit button and, you want to check which of the buttons was clicked to adapt the program flow in your controller. You need to add a value to each button because Turbo Drive doesn't send element with empty value:

$builder
    // ...
    ->add('save', SubmitType::class, [
        'label' => 'Create Task',
        'attr' => [
            'value' => 'create-task'
        ]
    ])
    ->add('saveAndAdd', SubmitType::class, [
        'label' => 'Save and Add',
        'attr' => [
            'value' => 'save-and-add'
        ]
    ]);

More Turbo Drive Info

Read the Turbo Drive documentation to learn about the advanced features offered by Turbo Drive.

Decomposing Complex Pages with Turbo Frames

Once Symfony UX Turbo is installed, you can also leverage Turbo Frames:

{# home.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
    <turbo-frame id="the_frame_id">
        <a href="{{ path('another-page') }}">This block is scoped, the rest of the page will not change if you click here!</a>
    </turbo-frame>
{% endblock %}
{# another-page.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
    <div>This will be discarded</div>

    <turbo-frame id="the_frame_id">
        The content of this block will replace the content of the Turbo Frame!
        The rest of the HTML generated by this template (outside of the Turbo Frame) will be ignored.
    </turbo-frame>
{% endblock %}

The content of a frame can be lazy loaded:

{# home.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
    <turbo-frame id="the_frame_id" src="{{ path('block') }}">
        A placeholder.
    </turbo-frame>
{% endblock %}

In your controller, you can detect if the request has been triggered by a Turbo Frame, and retrieve the ID of this frame:

// src/Controller/MyController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class MyController
{
    #[Route('/')]
    public function home(Request $request): Response
    {
        // Get the frame ID (will be null if the request hasn't been triggered by a Turbo Frame)
        $frameId = $request->headers->get('Turbo-Frame');

        // ...
    }
}

Writing Tests

Under the hood, Symfony UX Turbo relies on JavaScript to update the HTML page. To test if your website works properly, you will have to write UI tests.

Fortunately, we've got you covered! Symfony Panther is a convenient testing tool using real browsers to test your Symfony application. It shares the same API as BrowserKit, the functional testing tool shipped with Symfony.

Install Symfony Panther, and write a test for our Turbo Frame:

// tests/TurboFrameTest.php
namespace App\Tests;

use Symfony\Component\Panther\PantherTestCase;

class TurboFrameTest extends PantherTestCase
{
    public function testFrame(): void
    {
        $client = self::createPantherClient();
        $client->request('GET', '/');

        $client->clickLink('This block is scoped, the rest of the page will not change if you click here!');
        $this->assertSelectorTextContains('body', 'This will replace the content of the Turbo Frame!');
    }
}

Run bin/phpunit to execute the test! Symfony Panther automatically starts your application with a web server and tests it using Google Chrome or Firefox!

You can even watch changes happening in the browser by using: PANTHER_NO_HEADLESS=1 bin/phpunit --debug

Read the Turbo Frames documentation to learn everything you can do using Turbo Frames.

Coming Alive with Turbo Streams

Turbo Streams are a way for the server to send partial page updates to clients. There are two main ways to receive the updates:

  • in response to a user action, for instance when the user submits a form;
  • asynchronously, by sending updates to clients using Mercure, WebSocket and similar protocols.

Forms

Let's discover how to use Turbo Streams to enhance your Symfony forms:

// src/Controller/TaskController.php
namespace App\Controller;

// ...
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\UX\Turbo\Stream\TurboStreamResponse;

class TaskController extends AbstractController
{
    public function new(Request $request): Response
    {
        $task = new Task();

        $form = $this->createForm(TaskType::class, $task);
        $form->handleRequest($request);

        $submitted = $form->isSubmitted();
        $valid = $submitted && $form->isValid();

        if ($valid) {
            $task = $form->getData();
            // ... perform some action, such as saving the task to the database

            // πŸ”₯ The magic happens here! πŸ”₯
            if (TurboStreamResponse::STREAM_FORMAT === $request->getPreferredFormat()) {
                // If the request comes from Turbo, only send the HTML to update using a TurboStreamResponse
                return $this->render('task/success.stream.html.twig', ['task' => $task], new TurboStreamResponse());
            }

            // If the client doesn't support JavaScript, or isn't using Turbo, the form still works as usual.
            // Symfony UX Turbo is all about progressively enhancing your apps!
            return $this->redirectToRoute('task_success', [], Response::HTTP_SEE_OTHER);
        }

        // Symfony 5.3+
        return $this->renderForm('task/new.html.twig', $form);

        // Older versions
        $response = $this->render('task/new.html.twig', [
            'form' => $form->createView(),
        ]);
        if ($submitted && !$valid) {
            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        return $response;
    }
}
{# success.stream.html.twig #}

<turbo-stream action="replace" target="my_div_id">
    <template>
        The element having the id "my_div_id" will be replace by this block, without a full page reload!

        <div>The task "{{ task }}" has been created!</div>
    </template>
</turbo-stream>

Supported actions are append, prepend, replace, update and remove. Read the Turbo Streams documentation for more details.

Sending Async Changes using Mercure: a Chat

Symfony UX Turbo also supports broadcasting HTML updates to all currently connected clients, using the Mercure protocol or any other.

To illustrate this, let's build a chat system with 0 lines of JavaScript!

Start by installing the Mercure support on your project:

composer require symfony/ux-turbo-mercure
yarn install --force
yarn encore dev

The easiest way to have a working development (and production-ready) environment is to use Symfony Docker, which comes with a Mercure hub integrated in the web server.

If you use Symfony Flex, the configuration has been generated for you, be sure to update the MERCURE_URL in the .env file to point to a Mercure Hub (it's not necessary if you are using Symfony Docker).

Otherwise, configure Mercure Hub(s) to use:

# config/packages/turbo.yaml
turbo:
    mercure:
        hubs: [default]

Let's create our chat:

// src/Controller/ChatController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\PublisherInterface;

class ChatController extends AbstractController
{
    public function chat(Request $request, PublisherInterface $mercure): Response
    {
        $form = $this->createFormBuilder()
            ->add('message', TextType::class, ['attr' => ['autocomplete' => 'off']])
            ->add('send', SubmitType::class)
            ->getForm();

        $emptyForm = clone $form; // Used to display an empty form after a POST request
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            // πŸ”₯ The magic happens here! πŸ”₯
            // The HTML update is pushed to the client using Mercure
            $mercure->publish(new Update(
                'chat',
                $this->renderView('chat/message.stream.html.twig', ['message' => $data['message']])
            ));

            // Force an empty form to be rendered below
            // It will replace the content of the Turbo Frame after a post
            $form = $emptyForm;
        }

        return $this->render('chat/index.html.twig', [
            'form' => $form->createView(),
         ]);
    }
}
{# chat/index.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
    <h1>Chat</h1>

    <div id="messages" {{ turbo_stream_listen('chat') }}>
        {#
            The messages will be displayed here.
            "turbo_stream_listen()" automatically registers a Stimulus controller that subscribes to the "chat" topic as managed by the transport.
            All connected users will receive the new messages!
         #}
    </div>

    <turbo-frame id="message_form">
        {{ form(form) }}

        {#
            The form is displayed in a Turbo Frame, with this trick a new empty form is displayed after every post,
            but the rest of the page will not change.
        #}
    </turbo-frame>
{% endblock %}
{# chat/message.stream.html.twig #}

{# New messages received through the Mercure connection are appended to the div with the "messages" ID. #}
<turbo-stream action="append" target="messages">
    <template>
        <div>{{ message }}</div>
    </template>
</turbo-stream>

Keep in mind that you can use all features provided by Symfony Mercure, including private updates (to ensure that only authorized users will receive the updates) and async dispatching with Symfony Messenger.

Broadcast Doctrine Entities Update

Symfony UX Turbo also comes with a convenient integration with Doctrine ORM.

With a single attribute, your clients can subscribe to creations, updates and deletions of entities:

// src/Entity/Book.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\UX\Turbo\Attribute\Broadcast;

/**
 * @ORM\Entity
 */
#[Broadcast] // πŸ”₯ The magic happens here
class Book
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    public ?int $id = null;

    /**
     * @ORM\Column
     */
    public string $title = '';
}

To subscribe to updates of an entity, pass it as parameter of the turbo_stream_listen() Twig helper:

<div id="book_{{ book.id }}" {{ turbo_stream_listen(book) }}></div>

Alternatively, you can subscribe to updates made to all entities of a given class by using its Fully Qualified Class Name:

<div id="books" {{ turbo_stream_listen('App\\Entity\\Book') }}></div>

Finally, create the template that will be rendered when an entity is created, modified or deleted:

{# templates/broadcast/Book.stream.html.twig #}

{% block create %}
    <turbo-stream action="append" target="books">
        <template>
            <div id="{{ 'book_' ~ id }}">{{ entity.title }} (#{{ id }})</div>
        </template>
    </turbo-stream>
{% endblock %}

{% block update %}
    <turbo-stream action="update" target="book_{{ id }}">
        <template>
            {{ entity.title }} (#{{ id }}, updated)
        </template>
    </turbo-stream>
{% endblock %}

{% block remove %}
    <turbo-stream action="remove" target="book_{{ id }}"></turbo-stream>
{% endblock %}

By convention, Symfony UX Turbo will look for a template named templates/broadcast/{ClassName}.stream.html.twig. This template must contain at least 3 blocks: create, update and remove (they can be empty, but they must exist).

Every time an entity marked with the Broadcast attribute changes, Symfony UX Turbo will render the associated template and will broadcast the changes to all connected clients.

Each block must contain a list of Turbo Stream actions. These actions will be automatically applied by Turbo to the DOM tree of every connected client. Each template can contain as many actions as needed.

For instance, if the same entity is displayed on different pages, you can include all actions updating these different places in the template. Actions applying to non-existing DOM elements will simply be ignored.

The current entity, the string representation of its identifier(s), the action (create, update or remove) and options set on the Broadcast attribute are passed to the template as variables: entity, id, action and options.

Broadcast Conventions and Configuration

Because Symfony UX Turbo needs access to their identifier, entities have to either be managed by Doctrine ORM, have a public property named id, or have a public method named getId().

Symfony UX Turbo will look for a template named after mapping their Fully Qualified Class Names. For example and by default, if a class marked with the Broadcast attribute is named App\Entity\Foo, the corresponding template will be found in templates/broadcast/Foo.stream.html.twig.

It's possible to configure own namespaces are mapped to templates by using the turbo.broadcast.entity_template_prefixes configuration options. The default is defined as such:

# config/packages/turbo.yaml
turbo:
    broadcast:
        entity_template_prefixes:
            App\Entity\: broadcast/

Finally, it's also possible to explicitly set the template to use with the template parameter of the Broadcast attribute:

#[Broadcast(template: 'my-template.stream.html.twig')]
class Book { /* ... */ }

Broadcast Options

The Broadcast attribute comes with a set of handy options:

  • transports (string[]): a list of transports to broadcast to
  • topics (string[]): a list of topics to use, the default topic is derived from the FQCN of the entity and from its id
  • template (string): Twig template to render (see above)

Options are transport-sepcific. When using Mercure, some extra options are supported:

  • private (bool): marks Mercure updates as private
  • sse_id (string): id field of the SSE
  • sse_type (string): type field of the SSE
  • sse_retry (int): retry field of the SSE

Example:

// src/Entity/Book.php
namespace App\Entity;

use Symfony\UX\Turbo\Attribute\Broadcast;

#[Broadcast(template: 'foo.stream.html.twig', private: true)]
class Book
{
    // ...
}

Using Multiple Transports

Symfony UX Turbo allows sending Turbo Streams updates using multiple transports. For instance, it's possible to use several Mercure hubs with the following configuration:

# config/packages/mercure.yaml
mercure:
    hubs:
        hub1:
            url: https://hub1.example.net/.well-known/mercure
            jwt: snip
        hub2:
            url: https://hub2.example.net/.well-known/mercure
            jwt: snip
# config/packages/turbo.yaml
turbo:
    mercure:
        hubs: [hub1, hub2]

Use the appropriate Mercure HubInterface service to send a change using a specific transport:

// src/Controller/MyController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;

class MyController extends AbstractController
{
    public function publish(HubInterface $hub1): Response
    {
        $id = $hub1->publish(new Update('topic', 'content'));

        return new Response("Update #{$id} published.");
    }
}

Changes made to entities marked with the #[Broadcast] attribute will be sent using all configured transport by default. You can specify the list of transports to use for a specific entity class using the transports parameter:

// src/Entity/Book.php
namespace App\Entity;

use Symfony\UX\Turbo\Attribute\Broadcast;

#[Broadcast(transports: ['hub1', 'hub2'])]
/** ... */
class Book
{
    // ...
}

Finally, generate the HTML attributes registering the Stimulus controller corresponding to your transport by passing an extra argument to turbo_stream_listen():

<div id="messages" {{ turbo_stream_listen('App\Entity\Book', 'hub2') }}></div>

Registering a Custom Transport

If you prefer using another protocol than Mercure, you can create custom transports:

// src/Turbo/Broadcaster.php
namespace App\Turbo;

use Symfony\UX\Turbo\Attribute\Broadcast;
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;

class Broadcaster implements BroadcasterInterface
{
    public function broadcast(object $entity, string $action): void
    {
        // This method will be called everytime an object marked with the #[Broadcast] attribute is changed
        $attribute = (new \ReflectionClass($entity))->getAttributes(Broadcast::class)[0] ?? null;
        // ...
    }
}
// src/Turbo/TurboStreamListenRenderer.php
namespace App\Turbo;

use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
use Symfony\UX\Turbo\Twig\TurboStreamListenRendererInterface;
use Symfony\WebpackEncoreBundle\Twig\StimulusTwigExtension;
use Twig\Environment;

#[AsTaggedItem(index: 'my-transport')]
class TurboStreamListenRenderer implements TurboStreamListenRendererInterface
{
    public function __construct(
        private StimulusTwigExtension $stimulusTwigExtension,
    ) {}

    /**
     * @param string|object $topic
     */
    public function renderTurboStreamListen(Environment $env, $topic): string
    {
        return $this->stimulusTwigExtension->renderStimulusController(
            $env,
            'your_stimulus_controller',
            [/* controller values such as topic */]
        );
    }
}

The broadcaster must be registered as a service tagged with turbo.broadcaster and the renderer must be tagged with turbo.renderer.stream_listen. If you enabled autoconfigure option (it's the case by default), these tags will be added automatically because these classes implement the BroadcasterInterface and TurboStreamListenRendererInterface interfaces, the related services will be.

Backward Compatibility promise

This bundle aims at following the same Backward Compatibility promise as the Symfony framework: https://symfony.com/doc/current/contributing/code/bc.html

However, it is currently considered experimental, meaning it is not bound to Symfony's BC policy for the moment.

Credits

Symfony UX Turbo has been created by KΓ©vin Dunglas. It has been inspired by hotwired/turbo-rails and sroze/live-twig.

You might also like...
Provides integration for Doctrine with various Symfony components.

Doctrine Bridge The Doctrine bridge provides integration for Doctrine with various Symfony components. Resources Contributing Report issues and send P

Provides Amazon SES integration for Symfony Mailer

Amazon Mailer Provides Amazon SES integration for Symfony Mailer. Resources Contributing Report issues and send Pull Requests in the main Symfony repo

Yii Framework Symfony Mailer Integration

Yii Mailer Library - Symfony Mailer Extension This package is an adapter for yiisoft/mailer relying on symfony/mailer. Requirements PHP 7.4 or higher.

symfony solarium integration and solr management

solarium-bundle symfony bundle for solarium integration and solr management. current state of this bundle for now this bundle is me messing about with

Provides SMS77 integration for Symfony Notifier.

Provides SMS77 integration for Symfony Notifier.

Provides Expo integration for Symfony Notifier.

Expo Notifier Provides Expo integration for Symfony Notifier. DSN example EXPO_DSN=expo://TOKEN@default where: TOKEN is your Expo Access Token Resour

Provides One Signal integration for Symfony Notifier.

OneSignal Notifier Provides OneSignal integration for Symfony Notifier. DSN example ONESIGNAL_DSN=onesignal://APP_ID:API_KEY@default?defaultRecipientI

Tabler.io bundle for Symfony - a backend/admin theme for easy integration

Tabler Bundle for Symfony This repository contains a Symfony bundle, integrating the fantastic Tabler.io HTML Template into your Symfony project. It s

Provides KazInfoTeh integration for Symfony Notifier.

KazInfoTeh Notifier Provides KazInfoTeh integration for Symfony Notifier. DSN example KAZ_INFO_TEH_DSN=kaz-info-teh://USERNAME:PASSWORD@default?sender

Sanitize untrustworthy HTML user input (Symfony integration for https://github.com/tgalopin/html-sanitizer)

html-sanitizer is a library aiming at handling, cleaning and sanitizing HTML sent by external users (who you cannot trust), allowing you to store it and display it safely. It has sensible defaults to provide a great developer experience while still being entierely configurable.

Admin Theme based on the AdminLTE Template for easy integration into symfony

Admin Theme based on the AdminLTE Template for easy integration into symfony

AccessibleBundle provides an Accessible integration for your Symfony projects

AccessibleBundle AccessibleBundle provides an Accessible integration for your Symfony projects. This will allow you to define your class behavior usin

Simple php-imap integration for Symfony 2.8, 3.x and 4.x.

PHP-IMAP integration bundle Simple php-imap integration for Symfony 4.x, 5.x and 6.x. The version 1.5 and above are only compatible with Symfony 4+. P

MeteionBundle is the Symfony integration of the Meteion library.

MeteionBundle MeteionBundle is a Symfony integration of the Meteion library. Key features Auto-configuration Commands Services Entities Installation c

Provides Engagespot integration for Symfony Notifier.

Provides Engagespot integration for Symfony Notifier.

Symfony Framework Integration for HTTPlug

HTTPlug Bundle Symfony integration for HTTPlug. Installation To install the bundle with Symfony Flex, use the recipe: $ composer require php-http/http

Integration with your Symfony app & Stimulus!

StimulusBundle: Symfony integration with Stimulus! This bundle adds integration between Symfony, Stimulus and Symfony UX: A) Twig stimulus_* functions

[DEPRECATED -- Use Symfony instead] The PHP micro-framework based on the Symfony Components

Silex, a simple Web Framework WARNING: Silex is in maintenance mode only. Ends of life is set to June 2018. Read more on Symfony's blog. Silex is a PH

Bugsnag notifier for the Symfony PHP framework. Monitor and report errors in your Symfony apps.

Bugsnag exception reporter for Symfony The Bugsnag Notifier for Symfony gives you instant notification of errors and exceptions in your Symfony PHP ap

Comments
  • Update Form example

    Update Form example

    Without specifying the action, Turbo redirect the full page to /.

    name="product_new" is not necessary in this specific example, it however may help newcomer to understand they can have separate route for this action (even if not recommended by the Symfony Form docs).

    Not sure if it is a bug in turbo or me doing wrong, adding this fixes it.

    opened by pierrejoye 1
  • Make use of the HubInterface and the new handleForm method

    Make use of the HubInterface and the new handleForm method

    Replace occurence of renderForm with handleForm, and make use of HubInterface instead of PublisherInterface since I always get an error locally whenever I try to use PublisherInterface.

    opened by mercuryseries 1
  • Documentation: Added extra parameter to setFormat in example

    Documentation: Added extra parameter to setFormat in example

    I copied the example code in my project, but I got an error on

     $request->setFormat(TurboBundle::STREAM_FORMAT);
    

    The setFormat-function seems to expect 2 parameters. When I did

     $request->setFormat(TurboBundle::STREAM_FORMAT, TurboBundle::STREAM_MEDIA_TYPE);
    

    It worked like a charm :-) (on the bleading edge; this doesn't seem to be available in ux-turbo 2.0.1 yet.)

    opened by johanv 1
Chat room demo with Symfony UX Turbo

Symfony UX Turbo Demo Application Chat application demo on how Symfony UX Turbo can be used to make server-rendered applications more dynamic without

Druid 5 Sep 22, 2022
This package gives you a set of conventions to make the most out of Hotwire in Laravel

Introduction This package gives you a set of conventions to make the most out of Hotwire in Laravel (inspired by the turbo-rails gem). There is a comp

Tony Messias 665 Jan 2, 2023
Sandbox for figuring out why the Alpine + Turbo bridge is broken

Podcaster A Turn-Key Podcasting Starter Kit for Statamic 3 Features This kit is deceptively simple – it may look like a 3 page site but there's a whol

Jack McDade 1 Mar 25, 2022
Airbrake.io & Errbit integration for Symfony 3/4/5. This bundle plugs the Airbrake API client into Symfony project

AmiAirbrakeBundle Airbrake.io & Errbit integration for Symfony 3/4/5. This bundle plugs the Airbrake API client into Symfony project. Prerequisites Th

Anton Minin 8 May 6, 2022
Symfony React Blank is a blank symfony and react project, use this template to start your app using Symfony as an backend api and React as a frontend library.

Symfony React Blank Symfony React Blank is a blank symfony and react project, use this template to start your app using Symfony as an backend api and

Antoine Kingue 2 Nov 5, 2021
OAuth client integration for Symfony. Supports both OAuth1.0a and OAuth2.

HWIOAuthBundle The HWIOAuthBundle adds support for authenticating users via OAuth1.0a or OAuth2 in Symfony. Note: this bundle adds easy way to impleme

Hardware Info 2.2k Dec 30, 2022
Provides a Middleware to integration Tideways into Symfony Messenger Processing

Tideways Middleware for Symfony Messenger This package is currently under development and might be moved into the Tideways PHP Extension or stay indep

Tideways 6 Jul 5, 2022
Integration with your Symfony app & Vite

ViteBundle : Symfony integration with Vite This bundle helping you render all of the dynamic script and link tags needed. Essentially, he provide two

Hugues Tavernier 84 Dec 21, 2022
Provides Fake Chat integration for Symfony Notifier.

Fake Chat Notifier Provides Fake Chat (as email during development) integration for Symfony Notifier. DSN example FAKE_CHAT_DSN=fakechat+email://defau

Symfony 8 May 23, 2022
Provides Message Bird integration for Symfony Notifier.

MessageBird Notifier Provides MessageBird integration for Symfony Notifier. DSN example MESSAGEBIRD_DSN=messagebird://TOKEN@default?from=FROM where:

Symfony 7 May 23, 2022