Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The Dashboard Page

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We know that, on a technical level, the dashboard is the key to everything. All Crud controllers run in the context of the dashboard that link to them, which allows us to control things on a global level by adding methods to the dashboard controller.

But the dashboard is also... just a page! A page with a controller that's the homepage of our admin. And so, we can - and should - do something with that page!

The simplest option is just to redirect to a specific CRUD section... so that when the user goes to /admin, they're immediately redirected to, for example, the question admin. In a little while, we'll learn how to generate URLs to specific Crud controllers.

Or to be a little more fun, we can render something real on this page. Let's do that: let's render some stats and a chart.

To get the stats that I want to show, we need to query the database. Specifically, we need to query from QuestionRepository. DashboardController is a normal controller... which means that it's also a service. And so, when a service needs access to other services, we use dependency injection!

Add a constructor... then autowire QuestionRepository $questionRepository. I'll hit Alt+Enter and go to initialize properties to create that property and set it.

... lines 1 - 8
use App\Repository\QuestionRepository;
... lines 10 - 22
class DashboardController extends AbstractDashboardController
{
private QuestionRepository $questionRepository;
public function __construct(QuestionRepository $questionRepository)
{
$this->questionRepository = $questionRepository;
}
... lines 31 - 93
}

If you're wondering why I'm not using action injection - where we add the argument to the method - I'll explain why in a few minutes. But it is possible.

Before we render a template, let's prepare a few variables: $latestQuestions equals $this->questionRepository->findLatest(). That's a custom method I added before we started. Also set $topVoted to $this->questionRepository->findTopVoted(): another custom method.

... lines 1 - 33
public function index(): Response
{
$latestQuestions = $this->questionRepository
->findLatest();
$topVoted = $this->questionRepository
->findTopVoted();
... lines 40 - 44
}
... lines 46 - 95

Finally, at the bottom, like almost any other controller, return $this->render() to render, how about, admin/index.html.twig. Pass in the two variables: latestQuestions and topVoted.

... lines 1 - 33
public function index(): Response
{
... lines 36 - 40
return $this->render('admin/index.html.twig', [
'latestQuestions' => $latestQuestions,
'topVoted' => $topVoted,
]);
}
... lines 46 - 95

Awesome! Let's go add that! In templates/admin/, create a new index.html.twig... and I'll paste in the contents.

{% extends '@EasyAdmin/page/content.html.twig' %}
{% block page_title %}
Cauldron Overflow Dashboard
{% endblock %}
{% block main %}
<div class="row">
<div class="col-6">
<h3>Latest Questions</h3>
<ol>
{% for question in latestQuestions %}
<li>
<a href="{{ path('app_question_show', {'slug': question.slug}) }}">{{ question.name }}</a>
<br>- {{ question.createdAt|date }}
</li>
{% endfor %}
</ol>
</div>
<div class="col-6">
<h3>Top Voted</h3>
<ol>
{% for question in topVoted %}
<li>
<a href="{{ path('app_question_show', {'slug': question.slug}) }}">{{ question.name }}</a> ({{ question.votes }})
</li>
{% endfor %}
</ol>
</div>
</div>
{% endblock %}

But there's nothing tricky here. I am extending @EasyAdmin/page/content.html.twig. If you ever need to render a custom page... but one that still looks like it lives inside the admin area, this is the template you want.

If you open it up... hmm, there's not much here! But check out the extends: ea.templatePath('layout'). If you look in the views/ directory of the bundle itself, this is a fancy way of extending layout.html.twig. And this is a great way to discover all of the different blocks that you can override.

Back in our template, the main block holds the content, we loop over the latest questions... and the top voted. Very straightforward. And if you refresh the page, instead of the EasyAdmin welcome message, we see our stuff!

Adding a Chart!

Let's have some fun and render a chart on this page. To do this, we'll use a Symfony UX library. At your terminal, run:

composer require symfony/ux-chartjs

While that's installing, I'll go to the GitHub page for this library and load up its documentation. These days, the docs live on symfony.com and you'll find a link there from here.

Ok, so after installing the library, we need to run:

yarn install --force

And then... sweet! Just like that, we have a new Stimulus controller that has the ability to render a chart via Chart.js.

But I don't want talk too much about this chart library. Instead, we're going to steal the example code from the docs. Notice that we need a service in order to build a chart called ChartBuilderInterface. Add that as a second argument to the controller: ChartBuilderInterface $chartBuilder. I'll hit Alt+Enter and go to initialize properties to create that property and set it.

... lines 1 - 21
use Symfony\UX\Chartjs\Builder\ChartBuilderInterface;
... lines 23 - 24
class DashboardController extends AbstractDashboardController
{
... line 27
private ChartBuilderInterface $chartBuilder;
... line 29
public function __construct(QuestionRepository $questionRepository, ChartBuilderInterface $chartBuilder)
{
... line 32
$this->chartBuilder = $chartBuilder;
}
... lines 35 - 125
}

Then, all the way at the bottom... just to keep things clean... create a new private function called createChart()... that will return a Chart object. Now steal the example code from the docs - everything except for the render - paste it into the method... and, at the bottom return $chart.

Oh, and $chartBuilder needs to be $this->chartBuilder. I'm not going to bother making any of this dynamic: I just want to see that the chart does render.

... lines 1 - 99
private function createChart(): Chart
{
$chart = $this->chartBuilder->createChart(Chart::TYPE_LINE);
$chart->setData([
'labels' => ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
'datasets' => [
[
'label' => 'My First dataset',
'backgroundColor' => 'rgb(255, 99, 132)',
'borderColor' => 'rgb(255, 99, 132)',
'data' => [0, 10, 5, 2, 20, 30, 45],
],
],
]);
$chart->setOptions([
'scales' => [
'y' => [
'suggestedMin' => 0,
'suggestedMax' => 100,
],
],
]);
return $chart;
}
... lines 126 - 127

Back up in the index() method, pass a new chart variable to the template set to $this->createChart().

... lines 1 - 37
public function index(): Response
{
... lines 40 - 44
return $this->render('admin/index.html.twig', [
... lines 46 - 47
'chart' => $this->createChart(),
]);
}
... lines 51 - 127

Finally, to render this, over in index.html.twig, add one more div with class="col-12"... and, inside, render_chart(chart)... where render_chart() is a custom function that comes from the library that we just installed.

... lines 1 - 6
{% block main %}
<div class="row">
... lines 9 - 29
<div class="col-12">
{{ render_chart(chart) }}
</div>
</div>
{% endblock %}

And... that should be it! Find your browser, refresh and... nothing! Um, force refresh? Still nothing. In the console... a big error.

Ok, over in the terminal tab that holds Encore, it wants me to run yarn install --force... which I already did. Hit Ctrl+C to stop Encore... then restart it so that it sees the new files from the UX library:

yarn watch

And... yes! Build successful. And in the browser... we have a chart!

Next: let's do the shortest chapter ever where we talk about the pros, cons and limitations of injecting services into the action methods of your admin controllers versus through the constructor.

Leave a comment!

25
Login or Register to join the conversation
Delenclos-A Avatar
Delenclos-A Avatar Delenclos-A | posted 2 months ago | edited

Hi team,
I'm trying to create a menuItem::linktoCrud with parameter for an Index Page. In a classic controller you can do that easily and get it in the route. I try it with a linkToUrl and it's works fine. However I don't know how to do that in EA. Have you got a solution? How to add a parameter in the url, the same as setEntityId() (or to disabled test when loading setEntity()... CrudMenuItem is a final class so I can't override it (I'm not sure because it's hard for me). Thak's for help

Reply

Hey @Delenclos-A

You'll need to use the AdminUrlGenerator service https://symfony.com/bundles/EasyAdminBundle/current/crud.html#generating-admin-urls
It has a setRoute() method that allows you te define the route name and parameters, then, you get back the final URL, you need to call generateUrl()

I hope it helps. Cheers!

Reply
Marek-J Avatar
Marek-J Avatar Marek-J | posted 3 months ago | edited

I have followed all the steps, upgraded everything, and I am not able to see any chart. There are no errors displayed in the console or the development panel. I have executed commands like yarn install --force and yarn watch, but the chart still doesn't appear. Has anyone successfully run it recently with the current library versions?

The environment I'm using is as follows: PHP 8.1.13, Symfony 6.0.2, Google Chrome, and Mac OS X.

Reply

Hey Marek-J,

Did you download the course code and start following the course from the start/ directory? Did you run composer update command or only composer install?

Could you try to clear the cache also with rm -rf var/cache/ just in case? Also, try to open the page in Chrome Incognito mode, it might still be browser cache, though force refresh should do the trick I think.

Cheers!

Reply
Marek-J Avatar
Marek-J Avatar Marek-J | Victor | posted 3 months ago | edited

I downloaded Start/ at the beginning of the course and followed the coding instructions up until this chapter. Now I run 'composer udpate', rm -rf var/cache, repeated yarn install --force and yarn watch, forced cleard cache, opened chrome in icognito mode, but it still doesn't work without any error.

I checked source code and I have chart code, but I'cant see it.

    <div class="col-12"><canvas data-controller="symfony--ux-chartjs--chart" data-symfony--ux-chartjs--chart-view-value="&#x7B;&quot;type&quot;&#x3A;&quot;line&quot;,&quot;data&quot;&#x3A;&#x7B;&quot;labels&quot;&#x3A;&#x5B;&quot;January&quot;,&quot;February&quot;,&quot;March&quot;,&quot;April&quot;,&quot;May&quot;,&quot;June&quot;,&quot;July&quot;&#x5D;,&quot;datasets&quot;&#x3A;&#x5B;&#x7B;&quot;label&quot;&#x3A;&quot;My&#x20;First&#x20;dataset&quot;,&quot;backgroundColor&quot;&#x3A;&quot;rgb&#x28;255,&#x20;99,&#x20;132&#x29;&quot;,&quot;borderColor&quot;&#x3A;&quot;rgb&#x28;255,&#x20;99,&#x20;132&#x29;&quot;,&quot;data&quot;&#x3A;&#x5B;0,10,5,2,20,30,45&#x5D;&#x7D;&#x5D;&#x7D;,&quot;options&quot;&#x3A;&#x7B;&quot;scales&quot;&#x3A;&#x7B;&quot;y&quot;&#x3A;&#x7B;&quot;suggestedMin&quot;&#x3A;0,&quot;suggestedMax&quot;&#x3A;100&#x7D;&#x7D;&#x7D;&#x7D;"></canvas>            
        </div>
Reply

Hey Marek-J,

Ah, we do not recommend to run composer update command while following the course because it may lead to some unexpected BC breaks. If it possible - I'd recommend you to revert changes that composer update did and just do composer install or composer require if it's shown in the videos. This guarantee that you will have the exact code that the course author has in the video.

It's difficult to say what changes you need to do after you upgraded your project to newer versions.

Cheers!

Reply
Delenclos-A Avatar
Delenclos-A Avatar Delenclos-A | posted 5 months ago

Hi, I read all the tutorial and it's very hard for me (english and EasyAdmin). I have worked with SF2 (in the past). I appreciate tutorials because you go on detal and it makes sense to develop comprehension.

Imagine many admins who,works in one or many countries.
So they have to manage only their countries custommers, products...

I imagine that all is starting in the Dashboard. A query is generate to get all the entities we need in the cruds and only for the connected adminUser country.

I don't know how to do that. If someone can help me I appreciate. Thank's

Reply

Hey @Delenclos-A ,

Actually, not really in the Dashboard :) I suppose you want the Dashboard will look the same for all your customers, i.e. same links to the users list page, etc. But when you click on that Users link - you will see only users related to your country. So, this could be done by overriding the index page query builder in your UsersCrudController, i.e. specifically createIndexQueryBuilder() method - you can add any special conditions to the query to filter the list of users returned. But you would probably want to add some extra checks to avoid guessing the user URL in the address bar, otherwise some admins may open not their own users by going directly to the user id page, i.e. changing entity_id query parameter in the addres bar :)

Actually, I would recommend you to watch this tutorial till the end, we're talking about restricting access to some users in it :)

Cheers!

1 Reply
Ek24 Avatar
Ek24 Avatar Ek24 | posted 11 months ago | edited

For me nether the simulus app for the text-field-preview from the lesson before or the chart is working. No error in the console, nothing.

Tried several package-updates or copying the original code from the zip-file. must be missing something here. node v16, npm v8.18 and yarn v 1.21

not that important to me. more a nice-to-have.

Reply

Hey @Ek24!

Well that is no fun! Hmm. Here are some things to check:

A) First, do you see the data-controller="" attribute in the HTML on the rendered page? This is kind of obvious.... we just want to make sure that the data-controller IS actually rendering.

B) If data-controller IS rendering, then it means that the corresponding Stimulus controllers are missing for some reasons. If the Stimulus controllers were registered, then they would at least load and then have an error. Not seeing anything means that either the data-controller="" is missing, the Stimulus controller is missing, or there is a naming mismatch between them. But the chart controller name is all handled pretty automatically, so I doubt that it's a name mismatch.

So... my guess is that the problem is (B) that, somehow, the Stimulus controllers are missing. I'm not really sure how that could happen - but I would "back up" and try creating a really simple assets/controllers/hello_controller.js with:

import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
    connect() {
        this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
    }
}

Then, on a non-admin page, add <div data-controller="hello"> onto the page. When you refresh, do you see the text above added to the element? We're testing to see if the Stimulus controllers are working at all.

Oh, ALSO make sure you're including the Encore assets in your admin area - https://symfonycasts.com/screencast/easyadminbundle/assets#codeblock-9cb529831f - in that case, we created a new "admin" entry to hold the admin stuff (but use addEntry() not addStyleEntry() as you'll also be including JavaScript... and point addEntry() at admin.js like we do here https://symfonycasts.com/screencast/easyadminbundle/field-javascript#codeblock-0353f83ac2). To make sure Stimulus is loading, that file needs to import the ./bootstrap file - we show that later - https://symfonycasts.com/screencast/easyadminbundle/field-javascript#codeblock-b7c4c00273

Let me know if that helps! Cheers!

2 Reply
Faez-B Avatar

Hello, so for anyone having the same issue, like I was, this was the answer that helped me. If you want the easy steps to follow, here is what I did (I am using Symfony 6.2.6, PHP 8.2.3, Node v19.7.0 and NPM 9.6.0) :

  1. Stop your running symfony server and your watch script running with npm or yarn
  2. Create a .js file in assets directory (name it however you want, here let's say it's dashboard.js)
  3. In this file, you'll have to paste this : import './bootstrap';
  4. Now, go to your webpack.config.js file and add this entry : .addEntry('dashboard', './assets/dashboard.js')
  5. Add this function to your src/Admin/DashboardController.php :
    public function configureAssets(): Assets
    {
     return parent::configureAssets()
         ->addWebpackEncoreEntry('dashboard')
     ;
    }
    

    (Don't forget to import Assets)

  6. You can start your server and run the script again : symfony serve & npm run watch
Reply

Hi @Faez-B!

Thanks for posting that! I can't remember why I didn't need to do this in the actual tutorial, but this is sound advice. The point is: somehow, you need to make sure the Stimulus engine is running. Creating an admin-specific entry (that imports './bootstrap' ) like you suggest is a completely valid way to do that. The other way would be to make sure your normal app entrypoint is included in your admin area, though this could also introduce extra styling from your frontend that you don't want.

Anyways - good stuff, thanks for sharing this 👍!

Reply

Hi, I have problem with displaying chart (I use Mac, Safari) I can see that render is complete


<div class="col-12">
    <canvas data-controller="symfony--ux-chartjs--chart" data-symfony--ux-chartjs--chart-view-value="{&quot;type&quot;:&quot;line&quot;,&quot;data&quot;:{&quot;labels&quot;:[&quot;January&quot;,&quot;February&quot;,&quot;March&quot;,&quot;April&quot;,&quot;May&quot;,&quot;June&quot;,&quot;July&quot;],&quot;datasets&quot;:[{&quot;label&quot;:&quot;My First dataset&quot;,&quot;backgroundColor&quot;:&quot;rgb(255, 99, 132)&quot;,&quot;borderColor&quot;:&quot;rgb(255, 99, 132)&quot;,&quot;data&quot;:[0,10,5,2,20,30,45]}]},&quot;options&quot;:{&quot;scales&quot;:{&quot;y&quot;:{&quot;suggestedMin&quot;:0,&quot;suggestedMax&quot;:100}}}}"></canvas>
</div>

But I can not see the result, try yarn install --force and after yarn watch, no result. In console no errors...

What I do wrong ?

Reply

Hey Mepcuk!

Sorry for the slow reply! Hmm. When absolutely nothing happens (including no errors), it "feels" like the Stimulus controller isn't been registered. What does your controllers.json file look like? Also package.json? Something is short-circuiting I think... but I'm not sure what. Also, if you have the latest of all of the js packages (e.g. run yarn upgrade or npm update), then you should see some console logs from Stimulus itself. It'll say something about Stimulus "starting" and then tell you about any controllers that are starting. Even if the controller is missing, if Stimulus is running, you should see some logs from Stimulus about IT booting up.

Let me know what you find out :).

Cheers!

Reply

Solved - S6 Php8.1 - need to restart local web server (symfony serve) - any commands like yarn watch and other not help me.

Reply

Hey Maxim,

Thank you for sharing this tip with others! I'm happy to hear it fixed the problem for you

Cheers!

Reply
Nick-F Avatar

Do you know why I'm not getting autocompletion for path() within the admin twig templates in phpstorm anymore and how to fix it?

Reply

Unfortunately, I don't know how to fix it :/. That's... surprising that you've lost auto-complete... and I'd definitely check that the Symfony plugin is enabled for the project. But sometimes, autocompletion in Twig gets a bit inconsistent (it's usually inconsistent with what variables it autocompletes... which makes sense, since it's tricky to figure out which controller is rendering the template... to know which variables it's providing), but I don't have as many problems with actual Twig function, like path().

Cheers!

Reply
Nick-F Avatar

oh, the path() function itself autocompletes, but none of the route names are autocompleting within the call to path.
Actually that's not true, only the paths in the Controller/Admin directory controllers as well as web profiler paths; but none of the paths in the controllers in the Controllers directory like "app_profiler_show" and "app_homepage". The symfony plugin is enabled

Reply

Ah yes, this happens to me sometimes too... but I'm not sure why. This magic comes from the Symfony plugin... so something isn't quite right there, but I don't know how you'd go about fixing or debugging that :/.

Reply

Many thanks! Could you please share with us the way you have created findLatest() or topVoted() methods please?

Reply

Hey Lubna

There it is:


    public function findLatest(): array
    {
        return $this->createQueryBuilder('question')
            ->orderBy('question.createdAt', 'DESC')
            ->setMaxResults(3)
            ->getQuery()
            ->getResult();
    }

    public function findTopVoted(): array
    {
        return $this->createQueryBuilder('question')
            ->orderBy('question.votes', 'DESC')
            ->setMaxResults(5)
            ->getQuery()
            ->getResult();
    }

By the way, you can find them inside the QuestionRepository.php file.

Cheers!

1 Reply
Default user avatar

Dear all, thanks for all those great courses. I have a doubt: I can set different controllers for the same entity (alternate detail pages), but can I set a common url for the actions?

Reply

Hey @Luc!

Hmm. It sounds like you want to, for example, "share" (for example) the "edit" action (or some other action) between two Crud controllers for the same entity? If so, I do not believe this is possible: the controllers operate completely separately. However, if you have custom logic inside of one of the actions that you want to share, you could certainly add that custom logic to one of your Crud controllers and make the other *extend* that controller to share the logic.

Let me know if that helps :).

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/doctrine-bundle": "^2.1", // 2.5.5
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.2.1
        "doctrine/orm": "^2.7", // 2.10.4
        "easycorp/easyadmin-bundle": "^4.0", // v4.0.2
        "handcraftedinthealps/goodby-csv": "^1.4", // 1.4.0
        "knplabs/knp-markdown-bundle": "dev-symfony6", // dev-symfony6
        "knplabs/knp-time-bundle": "^1.11", // 1.17.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.1
        "symfony/console": "6.0.*", // v6.0.2
        "symfony/dotenv": "6.0.*", // v6.0.2
        "symfony/flex": "^2.0.0", // v2.0.1
        "symfony/framework-bundle": "6.0.*", // v6.0.2
        "symfony/mime": "6.0.*", // v6.0.2
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.0
        "symfony/security-bundle": "6.0.*", // v6.0.2
        "symfony/stopwatch": "6.0.*", // v6.0.0
        "symfony/twig-bundle": "6.0.*", // v6.0.1
        "symfony/ux-chartjs": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.7", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.7
        "twig/twig": "^2.12|^3.0" // v3.3.7
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.1
        "symfony/debug-bundle": "6.0.*", // v6.0.2
        "symfony/maker-bundle": "^1.15", // v1.36.4
        "symfony/var-dumper": "6.0.*", // v6.0.2
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.2
        "zenstruck/foundry": "^1.1" // v1.16.0
    }
}
userVoice