Sylius/Symfony: Image Upload with custom entity - symfony

I'm trying to use Sylius' ImagesUploadListener for a custom entity.
In the documentation it says I should listen to an event like "sylius.shipping_method.pre_create". For my custom entity there is no event called that I could hook on.
So what I tried was hooking on the product.pre_create event and giving my entity as a parameter, but it seems that the image upload is only triggered on the product entity and my entity configuration is ignored. Although the ImagesUploadListener is triggered two times, once from the core and once from my configuration.
The error I get is "Column 'path' cannot be null" which basically means that the ImagesUploadListener was not performing the image upload before saving the entity.
app.listener.images_upload:
class: Sylius\Bundle\CoreBundle\EventListener\ImagesUploadListener
parent: sylius.listener.images_upload
autowire: true
autoconfigure: false
public: false
tags:
- { name: kernel.event_listener, event: sylius.product.pre_create, entity: MyBundle\Entity\MyEntity, method: uploadImages }

There should be an event to hook on if you created the entity correctly (the Sylius way). You need to define the entity as a Resource:
# config/packages/sylius_resource.yaml
sylius_resource:
resources:
app.name_of_etity:
driver: doctrine/orm
classes:
model: App\Entity\NameOfEntity
If you defined the resource like this, the events would be:
event: app.system_manual.pre_create
event: app.app.name_of_entity.pre_update
Follow this guide:
https://docs.sylius.com/en/1.6/cookbook/entities/custom-model.html
Update
Because you are managing your custom entity through the existing product form the above will not work. To make it work, you can create your own event listener.
final class ProductSeoTranslationImagesUploadListener
{
/** #var ImageUploaderInterface */
private $uploader;
public function __construct(ImageUploaderInterface $uploader)
{
$this->uploader = $uploader;
}
public function uploadImages(GenericEvent $event): void
{
$subject = $event->getSubject();
// Add a ProductSeoInterface so you can use this check:
Assert::isInstanceOf($subject, ProductSeoInterface::class);
foreach ($subject->getSeo()->getTranslations() as $translation) {
Assert::isInstanceOf($translation, ImagesAwareInterface::class);
$this->uploadSubjectImages($translation);
}
}
private function uploadSubjectImages(ImagesAwareInterface $subject): void
{
$images = $subject->getImages();
foreach ($images as $image) {
if ($image->hasFile()) {
$this->uploader->upload($image);
}
// Upload failed? Let's remove that image.
if (null === $image->getPath()) {
$images->removeElement($image);
}
}
}
}
Tip: Create a (Product)SeoInterface so you can perform the type check.
Don't forget to register the eventListener:
App\EventListener\ProductSeoTranslationImagesUploadListener:
tags:
- {
name: kernel.event_listener,
event: sylius.product.pre_create,
method: uploadImages,
}
- {
name: kernel.event_listener,
event: sylius.product.pre_update,
method: uploadImages,
}

Related

Saving current user id in repository

I try to save initial value for user field in UserService entity. The reason is, I use this entity in EasyAdminBundle and when I build a form, I want to set a default value for user_id (ManyToOne to User entity).
init entity manager in constructor,
I override save method.
I get user from security session context and set to user service object, persist and flush.
...but I still can't see a change during save.
class UserServiceRepository extends ServiceEntityRepository
{
protected $_em;
public function __construct(RegistryInterface $registry)
{
$this->_em = $this->entityManager;
parent::__construct($registry, UserService::class);
}
// I override save method:
public function save(UserService $userService)
{
// Get current user from security:
$user = $this->get('security.token_storage')->getToken()->getUser();
// set to useService...
$userService->setUser($user);
// and persist & flush:
$this->_em->persist($userService);
$this->_em->flush();
}
// I override save method:
You're overriding non-existent method in parent, there's no save method in ServiceEntityRepository nor EntityRepository. So what's the main point of what you are doing and why you're setting default user_id in service repository?
UPDATE:
services:
my.listener:
class: UserServiceListener
arguments:
- "#security.token_storage"
tags:
- { name: doctrine.event_listener, event: prePersist }
Listener:
class UserServiceListener
{
private $token_storage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->token_storage = $token_storage;
}
public function prePersist(LifeCycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof UserService) {
return;
}
$entity->setUser($this->token_storage->getToken()->getUser());
}
}

Symfony Voter supports method receiving ROLE and Request instead attribute and entity

The Voter seems to work on my whole app... except on this controller:
$entity = $em->getReference('AppBundle:Offer',$id);
$this->denyAccessUnlessGranted('overview', $entity);
Where this Voter method is receiving wrong arguments ....
supports($attribute, $subject)
dump($attribute)-> ROLE_USER // instead 'overview'
dump($subject)-> Request Object // instead $entity
The Voter config is:
app_voter:
class: AppBundle\Security\Authorization\AppVoter
public: true
strategy: affirmative
arguments: ['#role_hierarchy', '#security.token_storage']
tags:
- { name: security.voter }
The problem disappears if instead 'overview' I write 'view' on the controller code.
I forgot to add 'overview' to the method 'supports' :
protected function supports($attribute, $subject) {
// if the attribute isn't one we support, return false
if (!in_array($attribute, array(self::OVERVIEW, self::VIEW, self::EDIT))) {
return false;
}
// bypass if the entity is not supported
if (!$this->isSupportedClass($subject)) {
return true;
}
return true;
}

Symfony Custom Error Page By Overriding ExceptionController

what I am trying to do is to have custom error page, not only will they be extending the base layout but also I want extra up selling content in those pages too so changing templates only is not an option
regardless of the reason (404 Not Found or just missing variable) I would like to show my template and my content instead
I have spent hours trying to get this going with no luck
app/console --version
Symfony version 2.5.6 - app/dev/debug
I tried some resources, but couldn't get it working. The name a few:
http://symfony.com/doc/current/reference/configuration/twig.html
http://symfony.com/doc/current/cookbook/controller/error_pages.html
I'm running in dev with no debug, see app_dev.php below:
$kernel = new AppKernel('dev', false);
following the tutorials i got these extra bits
app/config/config.yml
twig:
exception_controller: SomethingAppBundle:Exception:show
in my bundle
<?php
namespace Something\AppBundle\Controller;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ExceptionController extends Controller
{
public function showAction( FlattenException $error, DebugLoggerInterface $debug)
{
print_r($error);
}
}
but my error controller does not get executed,
I am on purpose causing error by trying to echo undefined variable in different controller, since it should handle error from entire application
At the beginning you need to create action in the controller:
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ErrorController extends Controller
{
public function notFoundAction()
{
return $this->render('error/404.html.twig');
}
}
Then you need to create a Listener:
<?php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class NotFoundHttpExceptionListener
{
private $controller_resolver;
private $request_stack;
private $http_kernel;
public function __construct($controller_resolver, $request_stack, $http_kernel)
{
$this->controller_resolver = $controller_resolver;
$this->request_stack = $request_stack;
$this->http_kernel = $http_kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->getException() instanceof NotFoundHttpException) {
$request = new \Symfony\Component\HttpFoundation\Request();
$request->attributes->set('_controller', 'AppBundle:Error:notFound');
$controller = $this->controller_resolver->getController($request);
$path['_controller'] = $controller;
$subRequest = $this->request_stack->getCurrentRequest()->duplicate(array(), null, $path);
$event->setResponse($this->http_kernel->handle($subRequest, HttpKernelInterface::MASTER_REQUEST)); // Simulating "forward" in order to preserve the "Not Found URL"
}
}
}
Now register the service:
#AppBundle/Resources/config/services.yml
services:
kernel.listener.notFoundHttpException:
class: AppBundle\EventListener\NotFoundHttpExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: -10 }
arguments: [ #controller_resolver, #request_stack, #http_kernel ]
Not tested this, but rather it should work;)
EDIT:
Tested, it works. On the rendered page, you have a session, so you have access to app.user, his cart, and other matters related to the session.

Symfony2 : onKernelResponse called twice as MASTER_REQUEST

I'm using the event listener onKernelResponse.
I used :
if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) {
return;
}
It's having a MASTER_REQUEST twice in my action, there is one before the <!DOCTYPE html> <html> <head>etc, and the other one as excepted after the end of the layout.
He is my services.yml :
history.listener:
class: VENDOR\MyBundle\Service\HistoryListener
arguments: [#doctrine.orm.entity_manager, #logger, #history]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
- { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
Did I do something wrong?
Finally found the origin of the problem : the debug toolbar !
It actually sends an ajax request, meaning another MASTER_REQUEST..
My solution is to filter on Controller, with a white/black list of controller's names.
UPDATE:
Here is the code I'm using (so you can easily exclude some other controllers if needed).
public function __construct()
{
$this->classesExcluded = array("Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController");
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller) || HttpKernelInterface::MASTER_REQUEST != $event->getRequestType() || in_array(get_class($controller[0]), $this->classesExcluded)) {
return;
}
// ...
}

symfony 2 upload one up bundle

I use the OneUp bundle for symfony 2, method's listener class is never call
//service.yml
amd_picture.uploadListener:
class: Amd\PictureBundle\Services\UploadListener
arguments: ["#doctrine.orm.entity_manager"]
tag:
- {name: kernel.event_listener, event: oneup_uploader.post_chunk_upload, method: onUpload}
the listener class implements the onUpload method and the corresponding event , is it the rigth event to listen ???
class UploadListener {
private $doctrine;
public function __construct($doctrine) {
$this->doctrine = $doctrine;
}
public function onUpload(PostChunkUploadEvent $event) {
//source code
}
}
the srcipt for the front end template
<script>
YUI().use('uploader', function(Y) {
var uploader = new Y.Uploader(
{
multipleFiles: true,
uploadURL: "{{ oneup_uploader_endpoint('gallery') }}",
width: "300px",
height: "60px"
}).render("#fileupload");
</script>
i don't find why the onUpload method is never call?
you have to use the postPersitentEvent in you use statement:
use Oneup\UploaderBundle\Event\PostPersistEvent;
Small correction:
//service.yml
amd_picture.uploadListener:
class: Amd\PictureBundle\Services\UploadListener
arguments: ["#doctrine.orm.entity_manager"]
tag:
- {name: kernel.event_listener, event: oneup_uploader.post_persist, method: onUpload}
Is not tag. Is:
tags:
- {name: kernel.event_listener, event: oneup_uploader.post_persist, method: onUpload}
The YUI3-Uploader is not able to split files into chunks, therefore the YUI3Controller of the OneupUploaderBundle does not support it either. This means there is no post_chunk_upload which will be dispatched and your EventListener is never called.
If you want to process your file after it is uploaded successfully, try listen to the PostPersistEvent like described in the bundles manual.
//service.yml
amd_picture.uploadListener:
class: Amd\PictureBundle\Services\UploadListener
arguments: ["#doctrine.orm.entity_manager"]
tag:
- {name: kernel.event_listener, event: oneup_uploader.post_persist, method: onUpload}
And be sure to pass a PostPersistEvent object to the listener.
use Oneup\UploaderBundle\Event\PostPersistEvent;
class UploadListener {
public function onUpload(PostPersistEvent $event)
{
//...
}
}

Resources