SonataAdminBundle - multiple list views of one entity - symfony

Let's just say i have entity named Offer.
I want to make multiple list views for Offer entity. Each list view should contain offers with different states (i.e. draft, active, inactive...).
So far I created two offer admins: DraftOfferAdmin and ActiveOfferAdmin. Here I defined custom queries:
public function createQuery($context = 'list')
{
/** #var ModelManager $modelManager */
$modelManager = $this->getModelManager();
$entityManager = $modelManager->getEntityManager($this->getClass());
$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder
->select('o')
->from($this->getClass(), 'o')
->where('o.state = :state')
->setParameter('state', 'draft');
$query = new ProxyQuery($queryBuilder);
foreach ($this->extensions as $extension) {
$extension->configureQuery($this, $query, $context);
}
return $query;
}
Query seems to be working fine!
I defined Admins in services:
services:
admin.draft_offer:
class: IndexBundle\Admin\Offer\DraftOfferAdmin
arguments:
- null
- IndexBundle\Entity\Offer
- IndexBundle:CRUD
tags:
- { name: sonata.admin, manager_type: orm, group: Offers, label: Draft Offers }
admin.unverified_offer:
class: IndexBundle\Admin\Offer\UnverifiedOfferAdmin
arguments:
- null
- IndexBundle\Entity\Offer
- IndexBundle:CRUD
tags:
- { name: sonata.admin, manager_type: orm, group: Offers, label: Unverified Offers }
But both list view pages share the same URL http://domain.com/admin/index/offer/list. Any ideas what do I miss in my configurations?

This happens probably because while the admin classes are different, your entity class is the same. I`d recommend this article in order to achieve the functionality you require, more user friendly too.
Now that I need exactly this functionality in one project :
In your admin class you need to set the route and the route pattern like
class ClassAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'class-route';
protected $baseRouteName = 'class-route';
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
$query->join($query->getRootAlias() . '.status', 'st');
$query->andWhere('st.id = :status')
->setParameter('status', $statis);
return $query;
}
//admin class code..
}
and include it the standart way..

Related

Doctrine event listener and softdeleted filter

I have entity answers and I use softdeleted filter for them, and when I remove entity in some action everything fine, I have deletedAt datetime, but when I try remove this entity in OnFlushEvent my entity is gone from DB, why ?
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$em->getFilters()->enable('softdeleteable');
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof Questions) {
$existAnswers = $this->container->get('app.repository.question_answers')
->findOneBy(['questions' => $entity]);
$em->remove($existAnswers);
}
}
}
entity
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class QuestionAnswers
config service
app.doctrine_listener:
class: AppBundle\Listener\DoctrineListener
calls:
- [setContainer, ['#service_container']]
tags:
- { name: doctrine.event_subscriber, connection: default }
I checked, this filter enabled, I try force enable but no helped
I think the issue might be related to subscribers priority.
In fact seems that also SoftDeleteableListener implements a subscriber which collect entities to softdelete using onFlush() event as we can see here. Therefore if your event is fired after the softdeletable one, your entities will be normally deleted from Doctrine.
To avoid this I would set priotity on your subscriber in order to fire your events before SoftDeleteableListener ones
app.doctrine_listener:
class: AppBundle\Listener\DoctrineListener
calls:
- [setContainer, ['#service_container']]
tags:
- { name: doctrine.event_subscriber, connection: default, priority: -256 }

Creating notifications for menu items in sonata admin

Does somebody know how to create a notification system for the menu items in the sidebar?
For example if you have a sidebar entry
Articles
and in the background, a new article has been added (e.g. by importing via a sql script). Then the menu entry should be displayed as
Articles (1)
Is there a tutorial for my concern?
You can modify the sidebar menu items.
To do this, you must create a listener that configure the menu in the manner that you want. You can do this with this code:
app.menu_listener:
class: AppBundle\EventListener\MenuBuilderListener
tags:
- { name: kernel.event_listener, event: sonata.admin.event.configure.menu.sidebar, method: addMenuItems }
calls:
- [ setDependencies, [ #doctrine.orm.entity_manager ] ]
After that you can write the class that modify the menu:
namespace AppBundle\EventListener;
use AppBundle\Entity\Configuration;
use Sonata\AdminBundle\Event\ConfigureMenuEvent;
use Doctrine\ORM\EntityManager;
class MenuBuilderListener {
/** #var EntityManager $em */
private $em;
public function addMenuItems(ConfigureMenuEvent $event)
{
$articles = $this->em->getRepo('AppBundle:Article')->findAll();
$menu = $event->getMenu();
$articleMenu = $menu->getChild('sonata.admin.group.articles');
$articleMenu->setLabel('Articles <span>' . $articles->count() . '</span>')
}
public function setDependencies(EntityManager $em, Translator $translator) {
$this->em = $em;
}
}
This is only an example, but is the way that i will take if i need to do this feature, i hope this may help you
You have more info about this here: https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_knp_menu.html

sonata admin label on breadcrumb

I have a little problem with sonata admin on Symfony.
I would like to change the default admin label in the breadcrumb:
but I can't find any solution. Can someone help me?
I found this function, but it doesn't work. It looks like that this function is not called.
public function buildBreadcrumbs($action, MenuItemInterface $menu = null) {
$breadCrumb = parent::buildBreadcrumbs($action, $menu);
return $breadCrumb;
}
I use Symfony 2.8.
Try to override classNameLabel property in your admin class:
// in your ProductAdmin class
public function configure()
{
$this->classnameLabel = "Products";
}
The simplest way to achieve what you want is to change translations messages.
If you really want to change the labels you can implement your own label generation strategy.
namespace Blast\CoreBundle\Translator;
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
/**
* Class LibrinfoLabelTranslatorStrategy.
*
* Provides a specific label translation strategy for Librinfo.
* It is based on UnderscoreLabelTranslatorStrategy, but without the context,
* and labels are prefixed by "librinfo.label."
*
* i.e. isValid => librinfo.label.is_valid
*/
class LibrinfoLabelTranslatorStrategy implements LabelTranslatorStrategyInterface
{
/**
* {#inheritdoc}
*/
public function getLabel($label, $context = '', $type = '')
{
$label = str_replace('.', '_', $label);
return sprintf('%s.%s.%s', "librinfo", $type, strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $label)));
}
}
define it as a service
blast_core.label.strategy.librinfo:
class: Blast\CoreBundle\Translator\LibrinfoLabelTranslatorStrategy
then pass it to the definition of your admin service like so:
crm.organism:
class: Librinfo\CRMBundle\Admin\OrganismAdmin
arguments: [~, Librinfo\CRMBundle\Entity\Organism, LibrinfoCRMBundle:OrganismAdmin]
tags:
- name: sonata.admin
manager_type: orm
group: Customers Relationship Management
label_translator_strategy: blast_core.label.strategy.librinfo
You will have full control of your admin labels
Also see: SonataAdmin: replace ID in breadcrumbs

What is the best way to display my entities at Back-office home page with the Sonata Admin Bundle

I am doing a back-office with the SONATA ADMIN BUNDLE and I was wondering how you get your different entities in the back-office side to get all my different objects at home-page. It's could be very great to have something like the demo : http://demo.sonata-project.org/admin/dashboard.
Has someone else experienced about this and can explain me?
Well, I guess you have a service defined in your services.yml. In that service you need to grab the information you want..
So you'll need some doctrine class, but your service doesn't have these.. You can inject them in your services.yml, for example:
sonata.block.service.statistics:
class: Evince\ObjectsBundle\Block\Service\StatisticsService
tags:
- { name: sonata.block }
arguments:
- ~
- '#templating'
calls:
- [ setDoctrine, ["#doctrine.orm.entity_manager"]] # <- here add the doctrine entity manager
In your service you have to implement an 'setDoctrine' function:
private $doctrine;
/**
* #param mixed $doctrine
*/
public function setDoctrine($doctrine)
{
$this->doctrine = $doctrine;
}
And a getDoctrine function:
/**
* #return mixed
*/
public function getDoctrine()
{
return $this->doctrine;
}
Now, when symfony builds your service when you need it, it will inject the doctrine entity manager for you.. This is called 'Dependency Indjection'.
In the execute function you can do something like this:
$repo = $this->getDoctrine()->getRepository('AcmeYourBundle:YourEntityClass');
$query = $repo->createQueryBuilder()
->from('foo', 'f')
->where('foo.bar = :id')
->setParameter('id', $someId)
->getQuery();
$results = $query->getResult();
$resultCount = count($results);
//-> pass the resultCount to your template
See the symfony website for information how to use doctrine.

Custom i18n routing in Symfony

I'm using JMS\I18nRoutingBundle, Gedmo\Translatable and Gedmo\Sluggable. Routes with default locations works as well, but other locales works without translated slug. My i18n routing have following settings:
# Doctrine extensions
stof_doctrine_extensions:
default_locale: %locale%
translation_fallback: true
orm:
default:
#…
sluggable: true
translatable: true
loggable: false
#…
jms_i18n_routing:
default_locale: cs_CZ
locales: [cs_CZ, en_US]
strategy: custom
hosts:
cs_CZ: example.cz
en_US: example.com
redirect_to_host: true
When I set up route like this:
hw_category:
pattern: /category/{slug}
defaults: { _controller: AcmeSiteBundle:Category:detail }
/**
* #Template
*/
public function detailAction(Category $category)
{}
This routes works
example.cz/category/slug-in-czech
example.com/category/slug-in-czech
But I want to get work example.com/category/slug-in-english which throws 404 exception object not found.
In your controller, you have to override method used in entity repository:
/**
* #Template
* #ParamConverter(
* "category",
* class = "AcmeSiteBundle:Category",
* options = {
* "repository_method" = "findByTranslatedSlug"
* }
* )
*/
public function detailAction(Category $category)
{}
namespace Acme\Bundle\SiteBundle\Entity;
use Doctrine\ORM\EntityRepository;
class CategoryRepository extends EntityRepository
{
public function findByTranslatedSlug($slug)
{
$qb = $this->createQueryBuilder('c')
->where('c.slug = :slug')
->setParameters($slug);
$query = $qb->getQuery();
// set the translation query hint
$query->setHint(
\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
);
return $query->getOneOrNullResult();
}
}
As i see you are using the ParamConverter to automatically fetch your category.
If slug-in-englishis an existing slug in your database but doctrine refuses to fetch it.
You probably don't have the TranslatableListener added to your EntityManager at that point.
Example:
$translatableListener = new \Gedmo\Translatable\TranslationListener();
$translatableListener->setTranslatableLocale('en_us');
$em->addEventSubscriber($translatableListener);
If you're using StofDoctrineExtensionsBundle:
stof_doctrine_extensions:
default_locale: en_US
orm:
default:
# ...
translatable: true
I had the same issue and as suggested by jkucharovic, you can use the Doctrine ParamConverter to convert your request parameters to an object
To fetch an object from the database, the Doctrine converter uses by default find() method. But since we use Translatable, therefore multiple tables, it is not enough to manage translations, that's why we need to define our own. Here comes findByTranslatedSlug.
/**
* #Template
* #ParamConverter(
* "category",
* class = "AcmeSiteBundle:Category",
* options = {
* "id" = "slug",
* "repository_method" = "findByTranslatedSlug"
* }
* )
*/
public function detailAction(Category $category)
{}
Some details about ParamConverter parameters:
First parameter "category" refers to the name of the method argument (here $category)
"id" option refers to the route placeholder you want to pass(here {slug}) to the customised repository method (findByTranslatedSlug()). Without setting this option, it would throw a PDO exception.
namespace Acme\Bundle\SiteBundle\Entity;
use Doctrine\ORM\EntityRepository;
class CategoryRepository extends EntityRepository
{
public function findByTranslatedSlug($slug)
{
$qb = $this->createQueryBuilder('c')
->where('c.slug = :slug')
->setParameter('slug',$slug);
$query = $qb->getQuery();
// set the translation query hint
$query->setHint(
\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
);
// If you need to set manually the locale to en_US, you can set this query hint
//$query->setHint(\Gedmo\Translatable\TranslatableListener::HINT_TRANSLATABLE_LOCALE, 'en_US');
return $query->getOneOrNullResult();
}
}
I hope this can help
Docs:
Doctrine ParamConverter
Translatable ORM Query Hint
For Symfony > 4
the work around is
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
* #Route("/{slug}", name="your_route_name"))
* #Entity("your_entity_name", expr="repository.findByTranslatedSlug(slug)")
and use the repo method from the accepted answer ;)
Searching for this the whole day, i'm glad i found your post !

Resources