If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeIn the last video we installed a PHP package - symfony/ux-chartjs
and it's recipe added a new JavaScript package to our package.json
file, which points to a directory in that bundle. By running yarn install --force
, yarn copied that into node_modules/
so that the package works like any normal JavaScript package.
So that was cool because, with basically one command, we got both a new bundle and a new JavaScript package, which contains a Stimulus controller.
Then, just by writing a little PHP code and calling a Twig function, boom! We magically had a JavaScript-powered chart. How the heck did that all work?
Inspect element on the graph. Interesting. All the render_chart
Twig function actually did was render a canvas
element with two important attributes: data-controller
set to some long symfony--ux-chartjs--chart
name, and a data-view
attribute set to a JSON-version of the data that we built inside of PHP. In our controller, you can see that this matches the data we built here.
Look back at the ux-chartjs
package in node_modules
. In src/
, open up controller.js
. This is the controller that's being used to render that chart on our page. And it's beautifully simple. It reads that data-view
attribute then instantiates a new Chart
object. This comes from the chart.js
package. That, ultimately, renders the chart into our element. So simple!
But there is still one missing piece to explain how this all works. Open assets/app.js
. The built version of this file is included on every page on our site. It loads bootstrap.js
, which we opened up at the very beginning of the tutorial. The code inside this file looks a little weird, but its job is simple and important. It reads all of the files in our controllers/
directory and registers them with Stimulus as controllers. This is where the naming convention comes into play. When Stimulus sees a file called counter_controller
, it registers that controller under the name counter
. Then when a data-controller="counter"
element appears on the page, it knows which controller to use.
import { startStimulusApp } from '@symfony/stimulus-bridge'; | |
// Registers Stimulus controllers from controllers.json and in the controllers/ directory | |
export const app = startStimulusApp(require.context( | |
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers', | |
true, | |
/\.(j|t)sx?$/ | |
)); |
If we ever added a data-controller
to our page with a controller name that Stimulus does not know about like - data-controller="eat-pizza"
- Stimulus will do nothing.
The point is: 100% of the controllers that Stimulus is aware of come from this line here, which means they come from the files in our assets/controllers
directory.
But... wait. If you look back at our browser, this rendered a data-controller
attribute set to symfony--ux-chartjs--chart
. And... we do not have a file called symfony--ux-chartjs--chart_controller.js
in this directory. So how the heck did the controller from our ux-chartjs
package get registered as a Stimulus controller with this name?
The answer to that question lives in a file that we haven't really looked at yet: assets/controllers.json
. This file was also automatically updated by the recipe when we composer required symfony/ux-chartjs
. It was basically empty before.
{ | |
"controllers": { | |
"@symfony/ux-chartjs": { | |
"chart": { | |
"enabled": true, | |
"fetch": "eager" | |
} | |
} | |
}, | |
"entrypoints": [] | |
} |
When we first installed Webpack Encore, our package.json
file came pre-filled with a few libraries. One of them is called @symfony/stimulus-bridge
. If you look back at bootstrap.js
, we import a function called a startStimulusApp
from that package.
In reality, when we use that down here, that function does two things. First, it does what we already know: it finds all the files in our controllers/
, directory and registers each as a Stimulus controller. Second, it reads our controllers.json
file and also registers any controllers here as Stimulus controllers.
Let me show you how that works. When startStimulusApp()
parses this file and sees the @symfony/ux-chartjs
key, it finds that package in node_modules/
and opens its package.json
. Then it looks for a special key called symfony
and then controllers
.
It then looks at the chart
key... and uses that to find out exactly where this controller file actually lives: the path under this main
key. We'll talk about the "eager" stuff later.
And that's it! For the controller name, it takes the package name - @symfony/ux-chartjs
- then this controller nick name - chart
and normalizes it into the long string that we see in the browser.
If you don't care about too much about the details of how this all works, here are the cliff notes. Each time we install a Symfony UX package, we instantly - without doing anything other than composer require
and yarn install
- have access to a new Stimulus controller in our application. That's incredibly powerful.
For example, this doesn't exist yet, but you could imagine being able to install a form entity type auto-complete package in your app, which would give you a new Symfony form type that could replace the boring select
element with an auto-completable field for selecting an entity. The possibilities are huge.
Next, let's investigate how we could control the behavior of the third-party Stimulus chart controller. It uses a really interesting pattern that will allow us to make big changes if we need to.
Solution:
`
{
"controllers": {
"@symfony/ux-chartjs": {
"chart": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"chartjs-adapter-date-fns": true
}
}
}
},
"entrypoints": []
}
`
Where is the recipe that does the magic of adding the controller to bootstrap.js and the linked file to package.json? I'm trying to create a ux component (symfony bundle with stimulus controllers) and am not sure how the installation with happen. I've searched https://github.com/symfony/... for chart, ux, webpack, etc. trying to find where the magic happens.
Is there an example of how to create a third-party component, like ux-chartjs? Especially now that Symfony 6.1 makes it so easy to configure a bundle, I'm hoping to be able to contribute something.
Hey Tac-Tacelosky
That's a great question. As far as I know it's an automatic process which is hardcoded in flex package and it uses composer.json "keywords" section. So if you have correct file structure and put in you keywords: 'symfony-ux'
string, than composer will execute some fleex magic and add everything you need.
Cheers!
Tried that, still no luck. Here's my trivial bundle: https://github.com/tacman/T..., and here's my open issue symfony/flex: https://github.com/symfony/...
The work done with Stimulus seems impressive to me, however there are some problems that seem to have not been taken into account when integrating with Symfony.
By having a single bootstrap.js, large amounts of Javascript are included in pages where it should not be. It would be nice to be able to create multiple bootstrap.js per context to include only those controllers that you want, personally I have been trying but I have not found the way to do it.
Hey SamuelVicent
In the following chapters Ryan shows how to controllers and modules dynamically. Perhaps that's what you need
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
"doctrine/annotations": "^1.0", // 1.11.1
"doctrine/doctrine-bundle": "^2.2", // 2.2.3
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
"doctrine/orm": "^2.8", // 2.8.1
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"sensio/framework-extra-bundle": "^5.6", // v5.6.1
"symfony/asset": "5.2.*", // v5.2.3
"symfony/console": "5.2.*", // v5.2.3
"symfony/dotenv": "5.2.*", // v5.2.3
"symfony/flex": "^1.3.1", // v1.18.5
"symfony/form": "5.2.*", // v5.2.3
"symfony/framework-bundle": "5.2.*", // v5.2.3
"symfony/property-access": "5.2.*", // v5.2.3
"symfony/property-info": "5.2.*", // v5.2.3
"symfony/proxy-manager-bridge": "5.2.*", // v5.2.3
"symfony/security-bundle": "5.2.*", // v5.2.3
"symfony/serializer": "5.2.*", // v5.2.3
"symfony/twig-bundle": "5.2.*", // v5.2.3
"symfony/ux-chartjs": "^1.1", // v1.2.0
"symfony/validator": "5.2.*", // v5.2.3
"symfony/webpack-encore-bundle": "^1.9", // v1.11.1
"symfony/yaml": "5.2.*", // v5.2.3
"twig/extra-bundle": "^2.12|^3.0", // v3.2.1
"twig/intl-extra": "^3.2", // v3.2.1
"twig/twig": "^2.12|^3.0" // v3.2.1
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
"symfony/debug-bundle": "^5.2", // v5.2.3
"symfony/maker-bundle": "^1.27", // v1.30.0
"symfony/monolog-bundle": "^3.0", // v3.6.0
"symfony/stopwatch": "^5.2", // v5.2.3
"symfony/var-dumper": "^5.2", // v5.2.3
"symfony/web-profiler-bundle": "^5.2" // v5.2.3
}
}
// package.json
{
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.12.13
"@popperjs/core": "^2.9.1", // 2.9.1
"@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
"@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
"@symfony/webpack-encore": "^1.0.0", // 1.0.4
"bootstrap": "^5.0.0-beta2", // 5.0.0-beta2
"core-js": "^3.0.0", // 3.8.3
"jquery": "^3.6.0", // 3.6.0
"react": "^17.0.1", // 17.0.1
"react-dom": "^17.0.1", // 17.0.1
"regenerator-runtime": "^0.13.2", // 0.13.7
"stimulus": "^2.0.0", // 2.0.0
"stimulus-autocomplete": "^2.0.1-phylor-6095f2a9", // 2.0.1-phylor-6095f2a9
"stimulus-use": "^0.24.0-1", // 0.24.0-1
"sweetalert2": "^10.13.0", // 10.14.0
"webpack-bundle-analyzer": "^4.4.0", // 4.4.0
"webpack-notifier": "^1.6.0" // 1.13.0
}
}
Chartjs needs date adapter to bring Time Axis functionality. I've chosen to use date-fns adapter. But...
How to import it? Do I need some changes in controllers.json?