Create custom block in Sonata Admin Bundle causes an error - php

Problem
I used this solution https://stackoverflow.com/a/15167450/2910183, but I have a following error when I'm trying open admin dashboard (http://localhost/app_dev.php/admin/dashboard). This happens also after cleaning cache.
ClassNotFoundException: Attempted to load class "AcmeBlockService" from namespace "Acme\ProductBundle\Block" in ~/htdocs/symfony2training/app/cache/dev/appDevDebugProjectContainer.php line 2216. Do you need to "use" it from another namespace?
Does anybody know where is problem?
Code
Here is part of my app/config/config.yml
sonata_block:
default_contexts: [cms]
blocks:
sonata.admin.block.admin_list:
contexts: [admin]
sonata.user.block.menu: # used to display the menu in profile pages
sonata.user.block.account: # used to display menu option (login option)
acme.block.products:
sonata_admin:
dashboard:
blocks:
# display a dashboard block
- { position: left, type: acme.block.products }
- { position: left, type: sonata.admin.block.admin_list }
Part of my src/Acme/ProductBundle/Resources/config/services.yml
services:
acme.block.products:
id: acme.block.products
class: Acme\ProductBundle\Block\ProductsBlockService
arguments:
- { name: service, id: templating }
tags:
- { name: sonata.block }
My src/Acme/ProductBundle/Document/Products.php file in :
<?php
namespace Acme\ProductBundle\Block;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
class ProductsBlockService extends BaseBlockService {
public function getName() {
return 'Products';
}
public function getDefaultSettings() {
return array();
}
public function validateBlock(ErrorElement $errorElement, BlockInterface $block) {
}
public function buildEditForm(FormMapper $formMapper, BlockInterface $block) {
}
public function execute(BlockInterface $block, Response $response = null) {
$settings = array_merge($this->getDefaultSettings(), $block->getSettings());
return $this->renderResponse('AcmeProductBundle:Block:admin_products.html.twig', array(
'block' => $block,
'settings' => $settings
), $response);
}
}
Solution
I looked into another block (vendor/sonata-project/user-bundle/Sonata/UserBundle/Block/AccountBlockService.php) and I realized, that its file name is like class name. So I changed src/Acme/ProductBundle/Document/Products.php to src/Acme/ProductBundle/Document/ProductsBlockService.php
It works, but another error appeared:
FatalErrorException: Compile Error: Declaration of Acme\ProductBundle\Block\ProductsBlockService::execute() must be compatible with Sonata\BlockBundle\Block\BlockServiceInterface::execute(Sonata\BlockBundle\Block\BlockContextInterface $blockContext, Symfony\Component\HttpFoundation\Response $response = NULL) in ~/htdocs/symfony2training/src/Acme/ProductBundle/Block/ProductsBlockService.php line 0
The solution is written in the error message: Acme\ProductBundle\Block\ProductsBlockService::execute() must be compatible with Sonata\BlockBundle\Block\BlockServiceInterface::execute(). So I looked into my helper (AccountBlockService.php from 1.), compare execute() methods and write my own solution.
So in the end my src/Acme/ProductBundle/Document/ProductsBlockService.php file looks as below.
<?php
namespace Acme\ProductBundle\Block;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\BlockBundle\Block\BaseBlockService;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\UserBundle\Menu\ProfileMenuBuilder;
use Sonata\UserBundle\Model\UserInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
class ProductsBlockService extends BaseBlockService {
public function getName() {
return 'Products';
}
public function validateBlock(ErrorElement $errorElement, BlockInterface $block) {
}
public function buildEditForm(FormMapper $formMapper, BlockInterface $block) {
}
public function setDefaultSettings(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'template' => 'AcmeProductBundle:Block:admin_products.html.twig',
'ttl' => 0
));
}
public function execute(BlockContextInterface $blockContext, Response $response = null) {
return $this->renderPrivateResponse($blockContext->getTemplate(), array(
'block' => $blockContext->getBlock(),
'context' => $blockContext,
), $response);
}
}

Related

Symfony 4 TwigFunction does not get registered

I am writing a Twig function in Symfony 4 but I cannot get it to work...
The extension class
<?php
namespace App\Twig;
use App\Utils\XXX;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class XXXExtension extends AbstractExtension
{
/**
* #return array|TwigFunction|TwigFunction[]
*/
public function getFunctions()
{
return new TwigFunction('showControllerName', [$this, 'showControllerName']);
}
public function showControllerName($sControllerPath)
{
return XXX::getControllerName($sControllerPath);
}
}
I have autowire set to true in services.yaml but just in case i tried with this also:
App\Twig\XXXExtension:
public: true
tags:
- { name: twig.extension }
usage in html.twig
{% set controllerName = showControllerName(app.request.get('_controller')) %}
and the response i get after this is:
HTTP 500 Internal Server Error
Unknown "showControllerName" function.
You need to return an array of functions, you are only returning one.
...
public function getFunctions()
{
return [
new TwigFunction('showControllerName', [$this, 'showControllerName']),
];
}
...

Display tagged services in Symfony ChoiceType

I implemented a system that lets an admin configure some additional behaviors (that are actually Symfony Services) from a form. At the moment I was using an EntityType, in order to let the admin select one or more services from a table in the data base.
$builder->add('services', EntityType::class, array(
'class' => 'AppBundle:Services',
'multiple' => true,
));
But since I'm registering the services in Symfony itself I just thought that there should be a way to get the services from the container (or similar) and create a new ServiceTagType so I don't have to add them in both the data base and the services.yml and I can do something like:
$builder->add('services', ServiceTagType::class, array(
'tag' => 'some.service.tag',
'multiple' => true,
));
Reading here and there I found out that you can tag services but you can only get the tagged services list when the container is being compiled... I'm struggling trying to find a workaround but it's been no use.
First of all, you have to create the ServiceTagType (I assume it will extend the ChoiceType and not the EntityType) :
How to create a custom type is documented here.
// src/AppBundle/Form/Type/ServiceTagType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class ServiceTagType extends AbstractType
{
private $tags;
public function setTags($tags)
{
$this->tags = $tags;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->tags
));
}
public function getParent()
{
return ChoiceType::class;
}
}
Register your ServiceTagType as a service (Because you need to provide the tags with setter injection)
# services.yml
app.form_type.service_tag:
class: AppBundle\Form\Type\ServiceTagType
tags:
- { name: form.type }
Then, as Bourvill suggest, you can collect your tags in a Compiler pass.
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class TagsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('app.form_type.service_tag')) {
return;
}
$definition = $container->findDefinition(
'app.form_type.service_tag'
);
$taggedServicesIds = array_keys($container->findTaggedServiceIds(
'app.tagged_for_service_tag_type'
));
$taggedServices = array_fill_keys($taggedServicesIds ,$taggedServicesIds);
$definition->addMethodCall('setTags',$taggedServices );
}
}
And don't forget to register this Compiler pass
To register a CompilerPass in the FullStack Framework:
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new TagsCompilerPass());
}
}

Edit content of a custom block SonataPageBundle

I have installed and configured SonataPageBundle as explained in the documentation. i'm trying to figure out how to render in SonataAdmin the content of my custom block. I declared my block in config.yml, i created a service,controller, routing and the page that i want to edit the block of content. I have already run the following command:
php app/console sonata:page:update-core-routes --site=all
php app/console sonata:page:create-snapshots --site=all
Unfortunately the content inside of my block is not render in the backend. i have two pictures that show you my problem:
My backend:
This is how i would like it to be of course with my content in my block.
Sonata Sandbox:
This is what i did so far:
Routing:
block:
path: index/block
defaults: { _controller: FLYBookingsBundle:Default:myblock }
DefaultController:
public function myblockAction()
{
return $this->render('FLYBookingsBundle:Default:myblock.html.twig');
}
Service.yml
sonata.block.service.myblock:
class: FLY\BookingsBundle\Block\MyBlockService
arguments: [ "sonata.block.service.myblock", #templating, #doctrine.orm.entity_manager ]
tags:
- { name: sonata.block }
MyBlockService
<?php
namespace FLY\BookingsBundle\Block;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\CoreBundle\Validator\ErrorElement;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\BlockBundle\Block\BaseBlockService;
use Sonata\BlockBundle\Block\BlockContextInterface;
class MyBlockService extends BaseBlockService
{
protected $em;
public function __construct($type, $templating, $em)
{
$this->type = $type;
$this->templating = $templating;
$this->em = $em;
}
public function getName()
{
return 'MyBlock';
}
public function getDefaultSettings()
{
return array();
}
public function validateBlock(ErrorElement $errorElement, BlockInterface $block)
{
}
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
}
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
$settings = array_merge($this->getDefaultSettings(), $blockContext->getBlock()->getSettings());
$data = count($this->em->getRepository("ApplicationSonataPageBundle:Page")->findAll());
return $this->renderResponse('FLYBookingsBundle:Default:myblock.html.twig', array(
'block' => $blockContext->getBlock(),
'settings' => $settings,
'data' => $data,
), $response);
}
}
myblock.html.twig
{% extends 'SonataPageBundle:Block:block_base.html.twig' %}
{% block block %}
<p> test test test </p>
{% endblock %}
.
sonata_block:
default_contexts: [sonata_page_bundle]
context_manager: sonata.page.block.context_manager
blocks:
sonata.user.block.menu:
sonata.admin.block.admin_list:
contexts: [admin]
sonata.block.service.myblock: ~
sonata.block.service.container:
sonata.page.block.container:
etc....

Custom Twig filter not found when called

I'm trying to create my own Twig filter. I followed this tuto Symfony Official Book.
But I get this error The filter "avatar" does not exist in src/Acme/Bundle/StoryBundle/Resources/views/Story/storyList.html.twig
Here is my AvatarExtension.php
<?php
namespace AppBundle\Twig;
class AvatarExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('avatar', array($this, 'avatar')),
);
}
public function getName()
{
return 'avatar_extension';
}
public function avatar($user)
{
if ($user->getPicture() && $user->getPicture() != '') {
return $user->getPicture();
} else {
return '/images/default-avatar.jpg';
}
}
}
And my AppBundle/Resources/config/services.yml
services:
app.twig.avatar_extension:
class: AppBundle\Twig\AvatarExtension
tags:
– { name: twig.extension }
The template using the filter is not in the same bundle as the Twig extension, but since it is a service, it shouldn't be a problem.
Here is how I call it : {{ story.author|avatar }}
Do you have any idea what the problem might be ?
EDIT
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
globals:
uploadTmpDir: %upload.tmp.relative.dir%
Ok I found the solution. Here is the services.yml
app.twig.avatar_extension:
class: AppBundle\Twig\AvatarExtension
tags:
- { name: twig.extension }
And here is the ExtensionClass:
<?php
namespace AppBundle\Twig;
class AvatarExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('avatar', array($this, 'avatarFilter')),
);
}
public function avatarFilter($user)
{
if ($user->getPicture() && $user->getPicture() != '') {
return $user->getPicture();
} else {
return '/images/default-avatar.jpg';
}
}
public function getName()
{
return 'avatar_extension';
}
}
I guess the function name must have the Filter suffix

Symfony2 Custom Validation with DB connection but no Entity

I'm simply trying to validate a form (with no entity attached) with one field "username". I want to check if the username exists or not. If it doesn't a proper message has to be displayed at form errors level (or form field errors level, I don't care that much).
Here is the relevant part of my controller:
$formGlobalSearch=$this->createFormBuilder()
->add('username', 'text', array('constraints' => new UsernameExists()))
->add('role', 'choice', array('choices' => $rolesListForForm,'required' => false, 'placeholder' => 'Choose a role', 'label' => false, ))
->add('submitUsername', 'submit', array('label' => 'Search username globally'))
->getForm();
$formGlobalSearch->handleRequest($request);
if ($formGlobalSearch->isValid())
{
// do something in the DB and refresh current page
}
return $this->render(//all the stuff needed to render my page//);
}
Here is the relevant part of service.yml
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExists
tags:
- { name: validator.constraint_validator, alias: UsernameExists }
Here is the validator class:
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class UsernameExists extends Constraint
{
public $message = 'The username "%string%" does not exist.';
public function validatedBy()
{
return 'UsernameExists';
}
}
Here is the validator:
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UsernameExistsValidator extends ConstraintValidator
{
public function validate($value)
{
$globalUserToAdd = $this->getDoctrine()->getRepository('AppBundle:User')->findOneBy(
array('username' => $value, 'enabled' => true)
);
if ($globalUserToAdd == null) //I did not find the user
{
$this->context->buildViolation($constraint->message)
->setParameter('%string%', $value)
->addViolation();
}
}
}
The class and the validator are in the directory "AppBundle\Validator\Constraints"
I'm getting the following error:
Expected argument of type
"Symfony\Component\Validator\ConstraintValidatorInterface",
"AppBundle\Validator\Constraints\UsernameExists" given
I of course have the following on top of my controller:
use AppBundle\Validator\Constraints\UsernameExists;
If I add the following to service.yml
arguments: ["string"]
I get the error:
No default option is configured for constraint
AppBundle\Validator\Constraints\UsernameExists
try changing:
public function validatedBy()
{
return 'UsernameExists';
}
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExists
to:
public function validatedBy()
{
return 'UsernameExistsValidator';
}
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExistsValidator
Merging (partially) the suggestions and debugging a bit more here is the final code.
Validator:
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManager;
class UsernameExistsValidator extends ConstraintValidator
{
public function __construct(EntityManager $em) //added
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
$em = $this->em;
$globalUserToAdd = $em->getRepository('AppBundle:User')->findOneBy(
array('username' => $value, 'enabled' => true)
);
if ($globalUserToAdd == null) //I did not find the user
{
$this->context->buildViolation($constraint->message)
->setParameter('%string%', $value)
->addViolation();
}
}
}
service.yml
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExistsValidator
arguments: [ #doctrine.orm.entity_manager, "string"]
tags:
- { name: validator.constraint_validator, alias: UsernameExists }

Categories