I have a controller that generates an image and returns the image in the response.
use FOS\RestBundle\Controller\Annotations as Rest;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
...
/**
* #Rest\Get("/image/{name}")
*/
public function getImage($name) {
$imageService = $this->get('image.service');
$tempImage = $imageService->genImage($name);
return new BinaryFileResponse($tempImage);
}
This works great but temp image never gets deleted.
How do I delete the temp image after the response is sent?
I read though the implementation of BinaryFileResponse. Turns out there is a public method deleteFileAfterSend
I just need
return (new BinaryFileResponse($tempImage))->deleteFileAfterSend(true);
Related
I want to include some commented code above my resource controllers class declarations, ideally to be added when generating the controller using php artisan make:controller MyController -resource. Namely, I want to add the path aliases at the top of each controller file:
/*
Verb URI Action Route Name desc
GET /photos index photos.index Display a listing of the resource.
GET /photos/create create photos.create Show the form for creating a new resource.
POST /photos store photos.store Store a newly created resource in storage.
GET /photos/{photo} show photos.show Display the specified resource.
GET /photos/{photo}/edit edit photos.edit Show the form for editing the specified resource.
PUT/PATCH /photos/{photo} update photos.update Update the specified resource in storage.
DELETE /photos/{photo} destroy photos.destroy Remove the specified resource from storage.
*/
This is purely a convenience example, but there have been other times when I want to add other stuff to the models and migrations I am generating with artisan. Can this be done? Would I need to recompile the artisan binaries?
It's a bit tricky but if you know how to find your way around the container you should be fine.
First you have to overwrite the ArtisanServiceProvider by extending the default one and change this method.
/**
* Register the command.
*
* #return void
*/
protected function registerControllerMakeCommand()
{
$this->app->singleton('command.controller.make', function ($app) {
return new ControllerMakeCommand($app['files']);
});
}
By doing this you simply allow yourself to assign a custom ControllerMakeCommand in the container.
Then you simply copy that class as well and change code you want.
In your case the stub file.
/**
* Get the stub file for the generator.
*
* #return string
*/
protected function getStub()
{
$stub = null;
if ($this->option('parent')) {
$stub = '/stubs/controller.nested.stub';
} elseif ($this->option('model')) {
$stub = '/stubs/controller.model.stub';
} elseif ($this->option('invokable')) {
$stub = '/stubs/controller.invokable.stub';
} elseif ($this->option('resource')) {
$stub = '/stubs/controller.stub';
}
if ($this->option('api') && is_null($stub)) {
$stub = '/stubs/controller.api.stub';
} elseif ($this->option('api') && ! is_null($stub) && ! $this->option('invokable')) {
$stub = str_replace('.stub', '.api.stub', $stub);
}
$stub = $stub ?? '/stubs/controller.plain.stub';
return __DIR__.$stub;
}
And of course you have to copy the stub file and edit it according to your needs.
For a travel application, the mobile application needs to get a default image for each city from their city code.
For example: example.com/imageCache/thumbnail/JFK.png
Where thumbnail is custom filter defined as:
/**
* Sample filter for image manipulation
* via image cache
*/
namespace App\ImageFilters;
use Intervention\Image\Filters\FilterInterface;
use Intervention\Image\Image;
use Intervention\Image\ImageManagerStatic;
class Thumbnail implements FilterInterface
{
/**
* Applies filter to given image
*
* #param Image $image
* #return Image
*/
public function applyFilter(Image $image)
{
//TODO: Do something to check if the image doesn't exist.
$gradient = ImageManagerStatic::make(public_path('images/gradient.png'));
return $image->fit(200, 200)->insert($gradient,'center')->blur();
}
}
The application however throws 404 even before this function is called.
I would like to show a default image, if the image is not found.
Thanks in Advance.
The URL manipulation as it is may not work in this case.
Write a route getCityImage/{cityCode} as:
public function getCityImage($cityCode){
if(file_exists('path_to_city_images/'.$cityCode.'.png'){
$image = Intervention\Image\Image::make('path_to_city_images/'.$cityCode.'.png');
return $image->filter(new Thumbnail());
}
else {
return $your_default_image;
}
}
Im trying to pass the verot image editing class to a custom class that I created, but it doesnt seem to work, it doesnt do anything when I try to run it. How do I pass the verot image class to my class?
//Edit.php
//Now I run my class
$process = new ProcessEventLogo();
$process->editEventLogo($event_id,$file_ext,$savepath,new upload(''));
Here is my custom class. I thought by running upload('') to this method, Im passing a copy of the verot upload class that I can access in my custom class method. But when I run it, it doesnt even get past the $mainimg->uploaded path. In fact $mainimg = $fileupload->upload($savefile); returns NULL when I var_dump it. What am I doing wrong?
class ProcessEventLogo {
public function editEventLogo($eventid,$fext,$url,$savepath,$fileupload)
{
//We generate the file name to save this image to
$savefile = $savepath .'event_' .$eventid .'.' .$fext;
//We check to see if the event image is there
$mainimg = $fileupload->upload($savefile);
//We now resize the image
if($mainimg->uploaded)
{
$mainimg->file_overwrite = TRUE;
$mainimg->image_ratio_crop = TRUE;
$mainimg->image_resize = TRUE;
$mainimg->image_x = 50;
$mainimg->image_y = 50;
$mainimg->process($savefile);
if($mainimg->processed)
{
echo $mainimg->error;
}
}
}
It appears this worked, can someone verify this is the proper way of doing this? So instead of this line:
//We check to see if the event image is there
$mainimg = $fileupload->upload($savefile);
This worked.
//We check to see if the event image is there
$mainimg = new $fileupload($savefile);
#mr.void Right, so how else would I pass this class to my custom class
method? Just seems like this is the wrong way
I think writing a little Factory is the right way for this:
class uploadFac {
public function getUploadInstance($file)
{
return new upload($file)
}
}
And use it like this:
$uplFac = new uploadFac();
$process = new ProcessEventLogo();
$process->editEventLogo($event_id,$file_ext,$savepath,$uplFac);
and in the method:
public function editEventLogo($eventid,$fext,$url,$savepath,$uplFac)
{
//We generate the file name to save this image to
$savefile = $savepath .'event_' .$eventid .'.' .$fext;
$mainimg = $uplFac->getUploadInstance($savefile);
i use FOSRestbundle to manage my api. I implemented a action to upload a file on symfony that it's working well. But the problem is that i need to obtain the file size... My action get the file on this way:
$uploadedfile = $request->files->get('file');
this obtains an array of all files that i upload.
Reading the doc of symfony for the $uploadedfile object, there is a method called:
getClientSize()
so i used and always return 0:
$fileSize = $uploadedfile->getClientSize();
There are another way to get the file size? I'm doing something wrong?
Thanks a lot.
how about the solution here?
http://symfony.com/doc/current/reference/constraints/File.html#maxsize
// src/AppBundle/Entity/Author.php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* #Assert\File(
* maxSize = "1024k",
* mimeTypes = {"application/pdf", "application/x-pdf"},
* mimeTypesMessage = "Please upload a valid PDF"
* )
*/
protected $bioFile;
}
you might have to create an entity for that, if you have not done so.
So if you want to use validation in your controller, you would do something like
// ...
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Author;
// ...
public function authorAction()
{
$author = new Author();
// ... do something to the $author object
$validator = $this->get('validator');
$errors = $validator->validate($author);
if (count($errors) > 0) {
$errorsString = (string) $errors;
return new Response($errorsString);
}
return new Response('The author is valid! Yes!');
}
you can test it, by uploading a file larger than the maxsize, set in your given Author Entity in that case you would get the message "Please upload a valid PDF"
Have you successfully moved the file into the intended folder? if you have, you can just call a native php function filesize().
http://php.net/manual/en/function.filesize.php
I took a look at this other question. I am looking for a way to do what the OP of that question wants as well, and that is to continue processing php after sending http response, but in Symfony2.
I implemented an event that fires after every kernel termination. So far so good, but what I want is for it to fire after CERTAIN terminations, in specific controller actions, for instance after a form was sent, not every single time at every request. That is because I want to do some heavy tasks at certain times and don't want the end user to wait for the page to load.
Any idea how can I do that?
<?php
namespace MedAppBundle\Event;
use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\Tag;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use JMS\DiExtraBundle\Annotation\Inject;
/**
* Class MedicListener
* #package MedAppBundle\EventListener
* #Service("medapp_test.listener")
* #Tag(name="kernel.event_subscriber")
*/
class TestListener implements EventSubscriberInterface
{
private $container;
private $logger;
/**
* Constructor.
*
* #param ContainerInterface $container A ContainerInterface instance
* #param LoggerInterface $logger A LoggerInterface instance
* #InjectParams({
* "container" = #Inject("service_container"),
* "logger" = #Inject("logger")
* })
*/
public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
{
$this->container = $container;
$this->logger = $logger;
}
public function onTerminate()
{
$this->logger->notice('fired');
}
public static function getSubscribedEvents()
{
$listeners = array(KernelEvents::TERMINATE => 'onTerminate');
if (class_exists('Symfony\Component\Console\ConsoleEvents')) {
$listeners[ConsoleEvents::TERMINATE] = 'onTerminate';
}
return $listeners;
}
}
So far I've subscribed the event to the kernel.terminate one, but obviously this fires it at each request. I made it similar to Swiftmailer's EmailSenderListener
It feels kind of strange that the kernel must listen each time for this event even when it's not triggered. I'd rather have it fired only when needed, but not sure how to do that.
In the onTerminate callback you get an instance of PostResponseEvent as first parameter. You can get the Request as well as the Response from that object.
Then you should be able to decide if you want to run the actual termination code.
Also you can store custom data in the attributes bag of the Request. See this link: Symfony and HTTP Fundamentals
The Request class also has a public attributes property, which holds special data related to how the application works internally. For the Symfony Framework, the attributes holds the values returned by the matched route, like _controller, id (if you have an {id} wildcard), and even the name of the matched route (_route). The attributes property exists entirely to be a place where you can prepare and store context-specific information about the request.
Your code could look something like this:
// ...
class TestListener implements EventSubscriberInterface
{
// ...
public function onTerminate(PostResponseEvent $event)
{
$request = $event->getRequest();
if ($request->attributes->get('_route') == 'some_route_name') {
// do stuff
}
}
// ...
}
Edit:
The kernel.terminate event is designed to run after the response is sent. But the symfony documentation is saying the following (taken from here):
Internally, the HttpKernel makes use of the fastcgi_finish_request PHP function. This means that at the moment, only the PHP FPM server API is able to send a response to the client while the server's PHP process still performs some tasks. With all other server APIs, listeners to kernel.terminate are still executed, but the response is not sent to the client until they are all completed.
Edit 2:
To use the solution from here, you could either directly edit the web/app.php file to add it there (but this is some kind of "hacking core" imo, even though it would be easier to use than the following). Or you could do it like this:
Add a listener to kernel.request event with a high priority and start output buffering (ob_start).
Add a listener to kernel.response and add the header values to the response.
Add another listener with highest priority to kernel.terminate and do the flushing (ob_flush, flush).
Run your code in a separate listener with lower priority to kernel.terminate
I did not try it, but it should actually work.
To solve this issue for some of my use cases I simply create symfony commands to do the heavy tasks, and call them via exec() to make them run in a separate process.
I used these answers to write a Response class that has this functionality:
https://stackoverflow.com/a/28738208/1153227
This implementation will work on Apache and not just PHP FPM. However, to make this work we must prevent Apache from using gzip (by using an invalid Content-Encoding) so it makes sense to have a custom Response class to specify exactly when having an early response is more important than compression.
use Symfony\Component\HttpFoundation\Response;
class EarlyResponse extends Response
{
// Functionality adapted from this answer: https://stackoverflow.com/a/7120170/1153227
protected $callback = null;
/**
* Constructor.
*
* #param mixed $content The response content, see setContent()
* #param int $status The response status code
* #param array $headers An array of response headers
*
* #throws \InvalidArgumentException When the HTTP status code is not valid
*/
public function __construct($content = '', $status = 200, $headers = array(), $callback = null)
{
if (null !== $callback) {
$this->setTerminateCallback($callback);
}
parent::__construct($content, $status, $headers);
}
/**
* Sets the PHP callback associated with this Response.
* It will be called after the terminate events fire and thus after we've sent our response and closed the connection
*
* #param callable $callback A valid PHP callback
*
* #throws \LogicException
*/
public function setTerminateCallback($callback)
{
//Copied From Symfony\Component\HttpFoundation\StreamedResponse
if (!is_callable($callback)) {
throw new \LogicException('The Response callback must be a valid PHP callable.');
}
$this->callback = $callback;
}
/**
* #return Current_Class_Name
*/
public function send() {
if (function_exists('fastcgi_finish_request') || 'cli' === PHP_SAPI) { // we don't need the hack when using fast CGI
return parent::send();
}
ignore_user_abort(true);//prevent apache killing the process
if (!ob_get_level()) { // Check if an ob buffer exists already.
ob_start();//start the output buffer
}
$this->sendContent(); //Send the content to the buffer
static::closeOutputBuffers(1, true); //flush all but the last ob buffer level
$this->headers->set('Content-Length', ob_get_length()); // Set the content length using the last ob buffer level
$this->headers->set('Connection', 'close'); // Close the Connection
$this->headers->set('Content-Encoding', 'none');// This invalid header value will make Apache not delay sending the response while it is
// See: https://serverfault.com/questions/844526/apache-2-4-7-ignores-response-header-content-encoding-identity-instead-respect
$this->sendHeaders(); //Now that we have the headers, we can send them (which will avoid the ob buffers)
static::closeOutputBuffers(0, true); //flush the last ob buffer level
flush(); // After we flush the OB buffer to the normal buffer, we still need to send the normal buffer to output
session_write_close();//close session file on server side to avoid blocking other requests
return $this;
}
/**
* #return Current_Class_Name
*/
public function callTerminateCallback() {
if ($this->callback) {
call_user_func($this->callback);
}
return $this;
}
}
You also need to add a method to your AppKernel.php to make this work (don't forget to add a use statement for your EarlyResponse class)
public function terminate(Request $request, Response $response)
{
ob_start();
//Run this stuff before the terminate events
if ($response instanceof EarlyResponse) {
$response->callTerminateCallback();
}
//Trigger the terminate events
parent::terminate($request, $response);
//Optionally, we can output the beffer that will get cleaned to a file before discarding its contents
//file_put_contents('/tmp/process.log', ob_get_contents());
ob_end_clean();
}