Chapters
57 Chapters
|
6:19:46
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
57.
Toast Notifications
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
"doctrine/annotations": "^1.0", // 1.13.1
"doctrine/doctrine-bundle": "^2.2", // 2.3.2
"doctrine/orm": "^2.8", // 2.9.1
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"sensio/framework-extra-bundle": "^6.1", // v6.1.4
"symfony/asset": "5.3.*", // v5.3.0-RC1
"symfony/console": "5.3.*", // v5.3.0-RC1
"symfony/dotenv": "5.3.*", // v5.3.0-RC1
"symfony/flex": "^1.3.1", // v1.21.6
"symfony/form": "5.3.*", // v5.3.0-RC1
"symfony/framework-bundle": "5.3.*", // v5.3.0-RC1
"symfony/property-access": "5.3.*", // v5.3.0-RC1
"symfony/property-info": "5.3.*", // v5.3.0-RC1
"symfony/proxy-manager-bridge": "5.3.*", // v5.3.0-RC1
"symfony/runtime": "5.3.*", // v5.3.0-RC1
"symfony/security-bundle": "5.3.*", // v5.3.0-RC1
"symfony/serializer": "5.3.*", // v5.3.0-RC1
"symfony/twig-bundle": "5.3.*", // v5.3.0-RC1
"symfony/ux-chartjs": "^1.1", // v1.3.0
"symfony/ux-turbo": "^1.3", // v1.3.0
"symfony/ux-turbo-mercure": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.0-RC1
"symfony/webpack-encore-bundle": "^1.9", // v1.11.2
"symfony/yaml": "5.3.*", // v5.3.0-RC1
"twig/extra-bundle": "^2.12|^3.0", // v3.3.1
"twig/intl-extra": "^3.2", // v3.3.0
"twig/string-extra": "^3.3", // v3.3.1
"twig/twig": "^2.12|^3.0" // v3.3.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
"symfony/debug-bundle": "^5.2", // v5.3.0-RC1
"symfony/maker-bundle": "^1.27", // v1.31.1
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/stopwatch": "^5.2", // v5.3.0-RC1
"symfony/var-dumper": "^5.2", // v5.3.0-RC1
"symfony/web-profiler-bundle": "^5.2", // v5.3.0-RC1
"zenstruck/foundry": "^1.10" // v1.10.0
}
}
What JavaScript libraries does this tutorial use?
// package.json
{
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.13.13
"@fortawesome/fontawesome-free": "^5.15.3", // 5.15.3
"@hotwired/turbo": "^7.0.0-beta.5", // 1.2.6
"@popperjs/core": "^2.9.1", // 2.9.2
"@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
"@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/Resources/assets", // 0.1.0
"@symfony/ux-turbo-mercure": "file:vendor/symfony/ux-turbo-mercure/Resources/assets", // 0.1.0
"@symfony/webpack-encore": "^1.0.0", // 1.3.0
"bootstrap": "^5.0.0-beta2", // 5.0.1
"chart.js": "^2.9.4",
"core-js": "^3.0.0", // 3.13.0
"jquery": "^3.6.0", // 3.6.0
"react": "^17.0.1", // 17.0.2
"react-dom": "^17.0.1", // 17.0.2
"regenerator-runtime": "^0.13.2", // 0.13.7
"stimulus": "^2.0.0", // 2.0.0
"stimulus-autocomplete": "https://github.com/weaverryan/stimulus-autocomplete#toggle-event-always-dist", // 2.0.0
"stimulus-use": "^0.24.0-1", // 0.24.0-2
"sweetalert2": "^11.0.8", // 11.0.12
"webpack-bundle-analyzer": "^4.4.0", // 4.4.2
"webpack-notifier": "^1.6.0" // 1.13.0
}
}
18 Comments
Hey Ryan! Thank you for this awesome tutorial.
Hi All,
What's the difference between:
and
Hey Aries,
Basically, the same package :) The package was renamed, IIRC to include the company name because they provide more than one package and it's easier to distinguish packages related to them, that's it :)
Cheers!
Cool beans. Thanks.
The way I see it, there are 2 approaches to target specific pages with mercure turbo streams.
In (1)'s case, the number of unique topics being subscribed to is low, but there are a lot more updates being sent to the browser (most of them just won't have an effect on the page)
In (2)'s case, there are many more unique topics being subscribed to, but only the necessary updates are being sent to each page.
Let's say we have 100 simultaneous users, all subscribing and getting updates,
Is one of the options better in terms of performance?
Would having too many different topics create a bottleneck in the mercure hub? Is this limited by anything (i.e. available cpu threads)?
Or would having too many updates being sent create a bottleneck in the symfony app?
Hey Nick!
First, I'm not an expert on this. So, I'll tell you what I know, but it's not coming from a place of real-world experience with many subscribers/updates like this.
From the Mercure side of things, the managed pricing page can kind of give you some clues: https://mercure.rocks/pricing. In general, I think the answer is that you'll run into bottlenecks performing too many updates first before you'll run into bottlenecks on Mercure.
For example, on the Hobby plan, you get 250 concurrent connections and 30 POST request / minute. So, with your example, if we have 100 users and each is subscribed to the same ONE topic, then that's 100 concurrent users, still well below the limit. And, you only need to publish ONE update.
If each user is subscribed to a different topic, then that's still 100 concurrent users. In theory, you (I believe) update all 100 topics with ONE update (because you can pass multiple topics to an Update) if all of those updates are identical. I have no idea if there is some upper limit on the number of topics you can pass. Or, if each Update needs to be a bit different (for each topic), then you'd need 100 updates, and that WOULD be a problem (since the limit at this level is 30 / minute).
So, in general, your bottleneck is probably more about how many updates you post vs concurrent users. Or, to say it differently, I would tend to design things so that I need only one / a few updates. Then, I would scale my Mercure instance based on how many concurrent users I need.
Let me know if this helps - it CAN be tricky to get right. Another thing to consider (based on your situation) is that you COULD send 100 updates to Mercure without slowing down your app by using Messenger to send these. The downside would just be that those updates might not be as instant, as it might take some time for your worker to process through them.
Cheers!
Hi Ryan,
We have some questions for you :) Having something out of the box would be great o giving us some easy way to accomplish would be awsome as well :)
Is there any way to load a static content? Let me explain. Imagine a web page with 2 links, one of them needs to load content from server, and another just show a pre-loaded content that was previously hidden. We though about creating a controller which first link loads content via Turbo.visit('url') (needed to import the turbo library) and the other just shows a hidden static content (we find this approach a bit cumbersome).
Is there any way to load dynamic content only once?
Thanks you very much in advance :) !
Hey @SamuelVicent!
Nice to chat with you - I'll do my best to brainstorm :).
I'm not sure I fully understand this still. For the link that loads content from the server, that is just a normal link, right? You shouldn't need to do anything (write any custom JavaScript) - you can just allow the link to be a normal link and then, on click, Turbo will follow it. I feel I'm missing some important detail here (because that answer is too simple) - so please let me know :).
Turbo won't help you here - this is more of a simple "hide/show this element" situation. In my app, if I were doing this with any frequency, I'd create a general-purpose Stimulus controller for this - the end Twig would look something like:
When the button is clicked, the
show()method in the Stimulus controller looks for thecontenttarget and unhides it.Again, I feel like I may not be fully grasping your situation (and so I might be answering the wrong question!) so let me know :).
What do you mean? Are you referring what happens when you click a normal link and Turbo loads the next "page"? Are you working with Turbo frames? Something else?
Let me know some of these extra details and I'll see if I can help further :).
Cheers!
Hi Ryan,
First point understood! thanks a lot. We understand that is not turbo responsability hiding and showing divs :)
Second point, sorry for my description, let me explain a bit better. Using turbo, imagine a page with two links, a user first clicks on LinkA, then on LinkB, and then on LinkA again. The question is: Is there any way to keep the Snapshop previously created when user clicked LinkA for the second time? The objective of this approach is not reloading the same content twice. This behaviour could happen inside a turbo-frame as well.
Thanks a lot!
Hey @SamuelVicent!
Ok, I think I understand then! Here is the flow:
1) User clicks Link A. The URL for LinkA is downloaded then shown
2) User clicks Link B. The URL for LinkB is downloaded then shown.
3) User clicks Link A again. The original, cached version of Link A is shown immediately. But the URL for LinkA IS downloaded again, and then shown to the user when it finishes.
If I understand correctly, you'd like to remove this part:
And thus just show the original, cached version of Link A and do nothing else. Is that correct?
If so... I'm not sure! :). I am not aware of a built-in way to do this (i.e. I am not aware of any
data-turbo="restore"type of attribute you could add somewhere to opt into this). To do this, I would try to "cancel" the visit - https://turbo.hotwired.dev/handbook/drive#canceling-visits-before-they-start - but I don't know if this would work. It would only work if canceling would cancel the request but Turbo still shows the page from cache. I just don't know exactly how Turbo will behave. Also, you would need to somehow figure out whether or not the page is already in the cache or not. That info may be present onevent.detail(?) or by checking of the currenthtmlelement has adata-turbo-previewattribute on it (assuming that the preview is shown BEFORE your event listener is called).So, that's one idea... which may or may not work - I'm not sure :). But I'd be curious to hear if it did!
Cheers!
Hi Ryan,
We made some tests and finally could cancel a second hit to server.
Here you are a minimal code in javascript:
Would have been great having a buit-in for this behaviour (something like you mention like data-turbo="restore"), but this suits our needs :)
Thanks a lot for your help!!!
Hey @SamuelVicent!
Ah, I love this solution and approach - very clever :). Thanks for sharing it.
Cheers!
Very nice tutorial, very informative.
There is one point that I was looking forward that sadly was not covered by the tutorial: private streams and/or different broadcasts.
Three different use cases:
1) I have a "buyer" and a "seller" Entity, when one buys from the other, they both get a toast message, but with a different message. One would say "You just bought for 15EUR" and the other one "You just sold for 10EUR".
So I need a way to broadcast changes depending on the logged in user. Is there any ways to do that ?
I thought about playing with the ids and just streaming both versions of the message, letting the one that is actually on the page be updated, but I'm concerned that one could look at his Mercure stream and see information that he shouldn't (like the "real" price the seller gets).
2) A Facebook-like notification system, where you get notified only on things that concerns you.
3) In the tutorial we did "When a review is updated on the admin site, update the frontend", using Broadcasts. What about doing the opposite ? If the review is changed on the frontend OR by another admin, i'd like my view updated too.
Thanks !
Hey @Growiel!
> There is one point that I was looking forward that sadly was not covered by the tutorial: private streams and/or different broadcasts
Yea... I didn't want to get into the weeds with this, since this tutorial isn't strictly about Mercure. But it's definitely a missing piece.
> 1) I have a "buyer" and a "seller" Entity, when one buys from the other, they both get a toast message, but with a different message. One would > say "You just bought for 15EUR" and the other one "You just sold for 10EUR".
> So I need a way to broadcast changes depending on the logged in user. Is there any ways to do that ?
Say the "buyer" has a user id of "10" in the database and the seller has a user id of "20". You could have the buyer listen to user_10 and the seller listen to user_20. Then, when the transaction is complete, you would post 2 updates: one to user_10 (with the "You just bought" message) and one to user_20 (with the "You just sold" message). There are other slightly variations on how to accomplish this, but I think this is the general idea :).
For both this situation (and the next one described below), you would need to make sure that only the authorized user can listen to their channel (e.g. we need to prevent the seller from being able to listen to the buyer's channel).
> 2) A Facebook-like notification system, where you get notified only on things that concerns you.
I would have each user listen to a user_ID channel, very similar to what I described above. The only downside is that this can cause you to have a lot of "listeners" to Mercure, which could cause you to need more Mercure resources (each user would always be listening to at least 1 channel).
> 3) In the tutorial we did "When a review is updated on the admin site, update the frontend", using Broadcasts. What about doing the opposite ? If the review is changed on the frontend OR by another admin, i'd like my view updated too.
That's a cool idea! The Broadcast feature would work nicely with this: we could some additional stream elements to the template that target elements that are only found in the admin section. Then, we just need to make sure that, in the admin section, we are also listening to that entity's channel.
That's the simplest solution: make the broadcast template update ALL possible elements, both on the frontend and backend. The downside is that a clever user could look into the stream and see the HTML from your admin area. Depending on the situation, that could definitely be a problem :). If it is, then you will, one way or another, need to dispatch two updates whenever you change a review: one for the frontend and one for the admin area (each update would be publishing to a different channel).
Let me know if that helps :). It's a bit of a challenge sometimes to figure out the best arrangement of "channels" to optimize "number of listeners" and "number of updates" and "privacy".
Cheers!
Thanks a lot for answering, somehow I did not a notification from Disqus.
As you can see all of my use cases rely upon one thing: privacy. We don't want someone seeing something he shouldn't by using his devtools to look at he stream(s).
I already thought of the "one user = one stream" technique, and it's the one I'm using at the moment, just wanted to pick your brain on if there's something more elegant because it does have some limitations.
For the last use case, we'd have to go with custom updates right ? Broadcasts would only allow us to broadcast either the front template or the admin one (unless we're okay with the security issues).
Hey @Growiel!
> Thanks a lot for answering, somehow I did not a notification from Disqus
That's ok! I'm glad you saw the reply :)
> just wanted to pick your brain on if there's something more elegant because it does have some limitations.
If there is a more elegant solution, I would also like to know it. So, there may be one, but not that I'm aware of.
> For the last use case, we'd have to go with custom updates right ? Broadcasts would only allow us to broadcast either the front template or the admin one (unless we're okay with the security issues).
Exactly. If you used the Broadcast, then the broadcast template would need to include both the frontend and backend HTML... which maybe you're ok with, or maybe not :). So probably you would need to make the Broadcast update just the frontend templates and dispatch a second, custom Update for an admin-only Mercure channel.
Cheers!
That was absolutely massive. I appreciate all the work that must have went into this.
I love the quizzes too.
And the longer course length created opportunities for similar issues to pop up multiple times throughout the course, so after the first time I did something, the next time it came up I could try to figure it out myself before following along with the video. I think this helps to alleviate the one-sided aspect of video courses: when it comes to learning, your brain only keeps what it uses, you have to inhale new info and exhale problem solving with that new info, and this course allowed plenty of room to breath.
I think it took around 20 hours to finish lol but I feel like I'm now part of an exclusive elite group of symfony developers who've made it this far.
Hey Nick!
Thank you for your feedback! We're happy to hear you love this, and that's why we do want our users to code along with the course author - it definitely worth it, the material is learned much better than when you just watch videos without coding.
Cheers!
"Houston: no signs of life"
Start the conversation!