Sonata admin tabs in page - symfony

I use Sonata admin Generator.
I'd like to create multiple lists from a symfony class.
For example I have a list of invoices and I would like to create a tab with paid bills, an other tab with pending bills and a last tab with invoices disabled.
This status is in the class.
I saw this page(admin/admin) in the Sonata demo who use context but I wouldn't like install mediabundle if it's possible.

It is possible without the mediabundle.
I had the same use-case as you with 'project-statuses'. These statuses are in my database.
A few steps are necessary:
Override the CRUDController. Maybe this step isn't needed, but I couldn't figure out how to without. I want to display the different statuses as tabs, so I inject a collection in the list-template.
Make your own CRUDController:
namespace Your\OwnBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
class ProjectCrudController extends Controller
{
/**
* {#inheritdoc}
*
* #param Request $request
*/
public function render($view, array $parameters = array(), Response $response = null, Request $request = null)
{
$projectStatusRepo = $this->getDoctrine()->getRepository('EvinceObjectsBundle:ProjectStatus');
// here inject the params you'll need
// you can do it only when $parameters['action'] == 'list' if you want
$parameters['projectStatuses'] = $projectStatusRepo->findAll();
$parameters['activeProjectStatus'] = $request->get('status', 1);
return parent::render($view, $parameters, $response);
}
}
Inject your own CRUDController in the services.yml (or xml)
sonata.admin.project:
class: Your\OwnBundle\Admin\ProjectAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "project", label: "Project" }
arguments:
- ~
- Your\OwnBundle\Entity\Project
- YourOwnBundle:ProjectCrud
calls:
- [ setLabelTranslatorStrategy, ["#sonata.admin.label.strategy.underscore"]]
- [ setTemplate, [list, YourOwnBundle:ProjectAdmin:list.html.twig]]
Notice the setTemplate-call, so lets create your template
Create your own list-template
{% extends 'SonataAdminBundle:CRUD:base_list.html.twig' %}
{% block preview %}
<ul class="nav nav-pills">
<li><a><strong>{{ "label.select_projectstatus"|trans({}, 'SonataProjectBundle') }}</strong></a></li>
{% for projectStatus in projectStatuses %}
{% set active = false %}
{% if projectStatus.id == activeProjectStatus %}
{% set active = true %}
{% endif %}
<li class="{% if active %}active{% endif %}" >{{ projectStatus.status }}</li>
{% endfor %}
</ul>
{% endblock %}
Override the Admin::getFilterParameters function in your own admin class. Here you want to set the filter based on your requestparam:
/**
* {#inheritdoc}
*/
public function getFilterParameters()
{
$parameters = parent::getFilterParameters();
return array_merge(array(
'status' => array(
'type' => '',
'value' => $this->getRequest()->get('status', 1),
)
), $parameters);
}

Related

reusable dynamic sidebar in Symfony 4 (Twig)?

I recently started using Symfony 4 and I am creating my first website with this wonderful framework right now.
I have a sidebar that should be displayed in about half of my routes and the content of the sidebar should be filled with some data from a database.
Currently I use DI in all this routes and pass the result of the injected repository to the template (which includes my sidebar.html.twig) for the route.
public function chalupaBatman(FancyRepository $repository)
{
$sidebarObjects = $repository->getSidebarObjects();
$this->render('controllername/chalupabatman.html.twig', [
'sidebarObjects' => $sidebarObjects,
]);
}
I am wondering if there is a way to avoid this for every route I define in my controllers.
So far I found this topic on stackoverflow.
The User Mvin described my problem in a perfect way and also provided some solutions.
However there is still no answer to "what is the best practice" part also the topic is from 2017; therefor, the way to solve this may have changed in Symfony 4.
I ended up with a TwigExtension solution. I'll describe how to achieve it and it would be great if you guys could provide some feedback.
Let me know if I produce massive overhead or miss something essential ;-)
Alright, first of all I created a TwigExtension via command-line
php bin/console make:twig-extension AppExtension
And then I modified the class to look like this:
<?php
namespace App\Twig;
use App\Repository\ArticleRepository;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFunctions(): array
{
return [
new TwigFunction('article_sidebar', [$this, 'getArticleSidebar'], ['needs_environment' => true, 'is_safe' => ['html']]),
];
}
public function getArticleSidebar(\Twig_Environment $twig)
{
$articleRepository = $this->container->get(ArticleRepository::class);
$archive = $articleRepository->myAwesomeLogic('omegalul');
return $twig->render('/article/sidebar.html.twig', [
'archive' => $archive,
]);
}
public static function getSubscribedServices()
{
return [
ArticleRepository::class,
];
}
}
In order to activate Lazy Performance so our Repository and the additional Twig_Environment doesn't get instantiated everytime when we use Twig
we implement the ServiceSubscriberInterface and add the getSubscribedServices-method.
Therefor, our Repo and Twig_Environment only gets instantiated when we actually call {{ article_sidebar() }} inside a template.
{# example-template article_base.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="row">
<div class="col-10">
{% block article_body %}{% endblock %}
</div>
<div class="col-2">
{{ article_sidebar() }}
</div>
</div>
{% endblock %}
Now I am able to define my templates for the article-routes like this:
{# example-template /article/show.html.twig #}
{% extends 'article_base.html.twig' %}
{% block article_body %}
{# display the article here #}
{% endblock %}

What is the better solution to get the Entity I want in a biderectional OneToMany relation?

I have one entity Article and an other entity Image with a bidrectional relation OneToMany and ManyToOne :
class Article
{
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Image", mappedBy="article")
*/
private $images;
}
class Image
{
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Article", inversedBy="images")
* #ORM\JoinColumn(nullable=true)
*/
private $article;
}
In my controller I use #paramconverter to get the article I want :
/**
* #Route("/blog/{slug}", name="article")
* #ParamConverter("article", class="AppBundle:Article")
*/
public function articleAction(Article $article)
{
return $this->render('default/article.html.twig', array(
'article' => $article,
));
}
Now my problem is that I want to identify the ONLY image with the attributes "main = true" in all the "article.images" I have.
What is the best solution?
In my wiew I can do somehting like this but it's not the best I think :
{% for image in article.images %}
{% if image.main %}
<img src="{{ asset( image.src ) }}" alt="{{ image.alt }}" title="{{ image.title }}">
{% endif %}
{% endfor %}
I'd like to use something like :
{{ article.mainImg }}
How can I achieve this please? And is this the best solution?
Doctrine provides a collection filter mechanism you could use to get the "main image":
public function articleAction(Article $article)
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("main", true))
->setMaxResults(1);
$mainImg = $article->getImages()->matching($criteria)->first();
return $this->render('default/article.html.twig', array(
'article' => $article,
'mainImg' => $mainImg
));
}
More information on filtering doctrine collections: Filtering collections
I did not test the code myself, but it should convey the idea of how it can be done.

Overriding SensioGeneratorBundle's twig template: trying to translate the delete button

I'm trying to override Resources/crud/actions/delete.php.twig from SensioGeneratorBundle.
I created this file located at app/Resources/SensioGeneratorBundle/skeleton/crud/actions/delete.php.twig:
{% extends "#SensioGenerator/Resources/crud/actions/delete.php.twig" %}
{% block form %}
/**
* Creates a form to delete a {{ entity }} entity by id.
*
* #param mixed $id The entity id
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm($id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('{{ route_name_prefix }}_delete', array('id' => $id)))
->setMethod('DELETE')
->add('submit', 'submit', array('label' => 'Supprimer'))
->getForm()
;
}
{% endblock form %}
Problem is that #SensioGenerator is not recognized:
There are no registered paths for namespace "SensioGenerator" in "crud/controller.php.twig" at line 58.
I tried to manually register the namespace in app/config/config.yml:
twig:
[...]
paths:
"%kernel.root_dir%/../vendor/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle/": SensioGenerator
But still not working. Idea?
Why you didn't do it like in docs ? SensioGeneratorDocs
{% extends "skeleton/crud/actions/delete.php.twig" %}
{% block form %}
/**
* Creates a form to delete a {{ entity }} entity by id.
*
* #param mixed $id The entity id
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm($id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('{{ route_name_prefix }}_delete', array('id' => $id)))
->add('submit', 'submit', array('label' => 'Supprimer'))
->getForm()
;
}
{% endblock form %}
If you're using a project that's based-on or is similar-to the Symfony Standard Edition, then the SensioGeneratorBundle is only loaded for the dev environment.
This is configured in both the composer.json and the AppKernel.
So, make sure your execution is using the dev environment or alter these configurations so that the bundle is always loaded.

Custom Repository Classes in Symfony2

I am creating a Task Management Application Demo project just to get the hold of symfony2. So far I have finished CRUD operations.
This is my defaultcontroller. I am calling the getNumberOfTasks() here.
namespace TaskManagerBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use TaskManagerBundle\Entity\Projects;
use TaskManagerBundle\Form\ProjectType;
class DefaultController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('TestBundle:Projects')
->findAll();
$tasks = $em->getRepository('TestBundle:Projects')
->getNumberOfTasks();
return $this->render('TestBundle:Default:index.html.twig', [
'projects' => $entities,
'tasks' => $tasks,
]
);
}
This is my ProjectRepository, where I have defined the getNumberOfTasks method.
<?php
namespace TaskManagerBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
use TaskManagerBundle\Entity\Projects;
use TaskManagerBundle\Entity\Tasks;
/**
* ProjectsRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class ProjectsRepository extends EntityRepository
{
public $id;
public function getNumberOfTasks()
{
//$projects = new Projects();
$query = $this->getEntityManager()
->createQuery(
'SELECT p FROM TestBundle:Tasks p WHERE p.projects = :project_id'
)
->setParameter('project_id', $this->id )
->getResult();
return count($query);
}
}
This is my index.html.twig
{% for project in projects %}
<tr>
<td>
{% if project.completed == 0 %}
{{ tasks }}
{% else %}
Completed
{% endif %}
</td>
</tr>
{% endfor %}
I am trying to get the number of tasks for each project. How do I do that?
In yii2 I could just do $this->id but here I am not able to do that.
You don't need the "getNumberOfTasks" method at all.
What you should be doing is specifying the associations in your Doctrine Entities.
You can read about it here:
http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
Based on your naming, I would suspect a OneToMany relationship from Project to Task, and a ManyToOne relationship from Task to Project.
When mapped correctly, in Twig you can then use:
{{ project.tasks|length }}
to get the number of tasks in a project. All you need to do is to pass the Project Entity to Twig and Symfony will handle the rest, including loading in all relationships.
This is the best way of doing what you are trying to do.

Passing multiple arguments through twig path

I have this twig code:
<div style="padding-left: 5em" class="comment">
<p>{{ comment.author.name }} - {{ comment.created|date('j. n. Y H:i') }}</p>
<p>{{ comment.text }}</p>
<p>Odpovědět na komentář</p>
{% for child in comment.children %}
{% include 'BlogApplicationBundle:Post:_comment.html.twig' with {'comment' : child}%}
{% endfor %}
</div>
and this is function that processes the output from link in twig code:
/**
* #Route("/post/{id}/newcommentresponse", name="comment_response_new")
* #Template("BlogApplicationBundle:Post:form.html.twig")
*/
public function commentResponceAction($id,$idc)
{
$comment = new Comment();
$form = $this->createForm(new CommentType(), $comment);
return array(
'form' => $form->createView()
);
}
when i try to run code i get this error :
Controller "Cvut\Fit\BiWt1\Blog\ApplicationBundle\Controller\CommentController::commentResponceAction()"
requires that you provide a value for the "$idc" argument (because
there is no default value or because there is a non optional argument
after this one).
So it seems that second argument passsed through link is ignored and i have no idea what am i doing wrong.
You are missing the $idc definition in your #Route annotation. It should look something like this:
#Route("/post/{id}/newcommentresponse/{idc}", name="comment_response_new")
or this:
#Route("/post/{id}/{idc}/newcommentresponse", name="comment_response_new")
You can also leave it out of the route and function declaration and grab it directly from the Controller:
/**
* #Route("/post/{id}/newcommentresponse", name="comment_response_new")
* #Template("BlogApplicationBundle:Post:form.html.twig")
*/
public function commentResponceAction($id)
{
$idc = $request->query->get('idc');

Resources