Symfony Php: emulate FileUpload and create UploadedFile - php

On a Symfony Commandline ContainerAwareCommand I want to emulate a file upload in order to call the following Method:
namespace AppUserBundle\Services;
use PcMagas\AppImageBundle\Filters\Crop\CropFilter;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Imagine\Image\ImageInterface;
use PcMagas\AppImageBundle\Filters\Resize\ResizeToLimitsKeepintAspectRatio;
use PcMagas\AppImageBundle\Filters\Resize\ResizeParams;
use PcMagas\AppImageBundle\Loader\ImageLoaderInterface;
use PcMagas\AppImageBundle\Saver\SaverInterface;
use Imagine\Image\Box;
class ProfileImageGenerator implements UploadedFileProcessor
{
const CROP_PARAMS='crop';
/**
* #var CropFilter
*/
private $crop=null;
/**
* #var ThumbnailFilterLoader
*/
private $thumbnail=null;
/**
* #var ResizeParams
*/
private $resizeParams=null;
/**
* #var ImageLoaderInterface
*/
private $imageLoader=null;
/**
* #var SaverInterface
*/
private $imageSaver=null;
public function __construct(CropFilter $crop,
ResizeToLimitsKeepintAspectRatio $thumbnail,
ImageLoaderInterface $imageLoader,
SaverInterface $imageSaver,
$thumbnailWidth,
$thumbnailHeight
){
$this->crop=$crop;
$this->thumbnail=$thumbnail;
$this->imageLoader=$imageLoader;
$this->imageSaver=$imageSaver;
if($thumbnailWidth>0 && $this->thumbnailHeight>0){
$this->resizeParams= new Box($thumbnailWidth,$thumbnailHeight);
}
}
/**
* {#inheritDoc}
* #see \AppUserBundle\Services\UploadedFileProcessor::process()
*/
public function process(UploadedFile $f, array $params)
{
$image=$this->openUploadedFileAsImageInterface($f);
//I implement in such a manner to provide extra prossessings over thumbnail image
if(isset($params[self::CROP_PARAMS])){
$image=$this->crop->apply($image, $params[self::CROP_PARAMS]);
}
if($this->resizeParams){
$image=$this->thumbnail->apply($image,$this->resizeParams);
}
return $this->generateFileFromImageInterface($image);
}
/**
* #param UploadedFile $f
* #return ImageInterface
*/
private function openUploadedFileAsImageInterface(UploadedFile $f)
{
return $this->imageLoader($f->getContents());
}
/**
* #param ImageInterface $image
* #return Symfony\Component\HttpFoundation\File
* #throws RuntimeException
*/
private function generateFileFromImageInterface(ImageInterface $image)
{
$tmpName=tempnam(sys_get_temp_dir()).'.png';
return $this->imageSaver->save($image);
}
}
Now I want to see how the method process will behave so I created the following ContainerAwareCommand In order to emulate a file upload:
namespace AppUserBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use AppUserBundle\Services\ProfileImageGenerator;
use PcMagas\AppImageBundle\Filters\Crop\CropParams;
class CreateProfileImageCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('appsuserbundle:create:profile:image')
->setDecrtiption("Process a file image like image profile.")
->setHelp("This command allows you to generate a file like a process image.")
->addArgument('file',InputArgument::REQUIRED,'The image file to process.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$file=$input->getArgument('file');
/**
* #var AppUserBundle\Services\ProfileImageGenerator $container
*/
$imageGenerator=$this->getContainer()->getDefinition('app_user.thumbnail_generator');
$cropParams=new CropParams(5,5,10,10);
$file=null;//How To emulate an uploadedfile with realData?
$processedFile=$imageGenerator->process($file,[ProfileImageGenerator::CROP_PARAMS=>$cropParams])
}
}
But I am stuck on how to create an Uploadedfile from a filesystem image in order to see whether the image works do you have ansy somt of idea how to do that?

Have you looked at the Symfony Api Documentation ?
__construct(string $path, string $originalName, string|null $mimeType = null, int|null $size = null, int|null $error = null, bool $test = false)
=>
new UploadedFile($path, $originalName, $mimeType, $size, $error, $test);
Now you can insert the path you get from the command arguments and pass that through

Related

Doctrine toIterable method is using lots of memory, before looping

Technical stack :
php 7.4.28
symfony 5.4.5
doctrine/orm 2.11
I have a simple entity which consist of only two fields :
<?php
namespace App\Entity\Pro;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\Pro\MarketRepository")
* #ORM\Table()
*/
class Market
{
/**
* #ORM\Id()
* #ORM\Column(name="listing_date", type="custom_datetime", options={"default": "0000-00-00"})
*/
private $listingDate;
/**
* #ORM\Column(type="text")
*/
private $payload;
/**
* #return \DateTime
*/
public function getListingDate(): \DateTime
{
return new \DateTime($this->listingDate);
}
/**
* #param \DateTime $listingDate
* #return self
*/
public function setListingDate(\DateTime $listingDate): self
{
$this->listingDate = $listingDate->format('Y-m-d');
return $this;
}
/**
* #return string
*/
public function getPayload(): string
{
return $this->payload;
}
/**
* #param string $payload
* #return self
*/
public function setPayload(string $payload): self
{
$this->payload = $payload;
return $this;
}
}
The payload field contain a huge JSON field (I think it's where the problem is), I can't really do anything about it because we're getting it from an external service.
I need to loop through those records and do some actions with the payload, so I made a simple repository with an Iterator :
<?php
namespace App\Repository\Pro;
use App\Entity\Pro\Instrument;
use App\Entity\Pro\Market;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\Query;
use Doctrine\Persistence\ManagerRegistry;
/**
* #method Market|null find($id, $lockMode = null, $lockVersion = null)
* #method Market|null findOneBy(array $criteria, array $orderBy = null)
* #method Market[] findAll()
* #method Market[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class MarketRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Market::class);
}
public function findForGraph($types): array
{
$qb = $this->createQueryBuilder('m')
->orderBy('m.listingDate', 'ASC');
$iterator = $qb->getQuery()->toIterable();
echo 'Memory usage before looping: '.round((memory_get_usage() / 1024 / 1024), 2)." mb\n";
foreach ($iterator as $result) {
// doing stuff here
$this->_em->detach($result[0]);
}
return $graphsConfigs;
}
}
The memory usage echo actually shows a memory usage of 800 mb.
It stays constant during the loop, but how is it possible than simply the call of the toIterable method is using this much ? Is there any solution possible without going pure PDO / DBAL mode ?

Converting any base64 file to a file and moving to the targeted path in php / Symfony 4

thanks in advance I am creating the API in symfony 4 for just uploading the base64 image or any file through POSTMAN and I have to move the file to the targeted directory. I have written the below code in the controller. Through controller I am trying to move the file to the directory, but
I am getting the error as :
Uncaught Warning: file_put_contents(images/5c78de505abdd.svg): failed to open stream: No such file or directory {"exception":"[object] (ErrorException(code: 0): Warning: file_put_contents(images/5c78de505abdd.svg): failed to open stream: No such file or directory at /home/ragiththomas/Sites/asco-forum/src/Modules/Forum/ForumController.php:1107)"} []
POSTMAN request for svg file:
{"postFile":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0naXNvLTg4NTktMSc/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICdodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQnPgo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjAzLjU0MyAyMDMuNTQzIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjAzLjU0MyAyMDMuNTQzIj4KICA8Zz4KICAgIDxwYXRoIGZpbGw9IiMwMDQ1N2MiIGQ9Im0xOTQuMTM5LDExLjkxOGgtMzkuMzE1di01LjkxOGMwLTMuMzEzLTIuNjg3LTYtNi02cy02LDIuNjg3LTYsNnY1LjkxOGgtMzUuMDUzdi01LjkxOGMwLTMuMzEzLTIuNjg3LTYtNi02cy02LDIuNjg3LTYsNnY1LjkxOGgtMzMuNTYzdi01LjkxOGMwLTMuMzEzLTIuNjg3LTYtNi02cy02LDIuNjg3LTYsNnY1LjkxOGgtNDAuODA0Yy0zLjMxMywwLTYsMi42ODctNiw2djEzNS41NzJjMCwzLjMxMyAyLjY4Nyw2IDYsNmgzNi44MjZjNS45MDEsMjUuMjE0IDI4LjU1NSw0NC4wNTMgNTUuNTQxLDQ0LjA1M3M0OS42NC0xOC44NCA1NS41NDEtNDQuMDUzaDM2LjgyNmMzLjMxMywwIDYtMi42ODcgNi02di0xMzUuNTcyYzAuMDAxLTMuMzEzLTIuNjg1LTYtNS45OTktNnptLTE0My45MzEsMTJ2NS40MjJjMCwzLjMxMyAyLjY4Nyw2IDYsNnM2LTIuNjg3IDYtNnYtNS40MjJoMzMuNTYzdjUuNDIyYzAsMy4zMTMgMi42ODcsNiA2LDZzNi0yLjY4NyA2LTZ2LTUuNDIyaDM1LjA1M3Y1LjQyMmMwLDMuMzEzIDIuNjg3LDYgNiw2czYtMi42ODcgNi02di01LjQyMmgzMy4zMTV2MjQuNTM2aC0xNzIuNzM1di0yNC41MzZoMzQuODA0em01MS41NjMsMTY3LjYyNWMtMjQuODQyLDAtNDUuMDUzLTIwLjIxMS00NS4wNTMtNDUuMDUzczIwLjIxMS00NS4wNTMgNDUuMDUzLTQ1LjA1MyA0NS4wNTMsMjAuMjEgNDUuMDUzLDQ1LjA1My0yMC4yMSw0NS4wNTMtNDUuMDUzLDQ1LjA1M3ptNTcuMDI4LTQ0LjA1M2MwLjAwNi0wLjMzNCAwLjAyNS0wLjY2NSAwLjAyNS0xIDAtMzEuNDU5LTI1LjU5NC01Ny4wNTMtNTcuMDUzLTU3LjA1M3MtNTcuMDUzLDI1LjU5NC01Ny4wNTMsNTcuMDUzYzAsMC4zMzUgMC4wMiwwLjY2NiAwLjAyNSwxaC0yOS4zNHYtODcuMDM1aDE3Mi43MzV2ODcuMDM1aC0yOS4zMzl6Ii8+CiAgICA8cGF0aCBmaWxsPSIjMDA0NTdjIiBkPSJtMTA3Ljc3MSwxNDguMDA0di0yOS4wMjZjMC0zLjMxMy0yLjY4Ny02LTYtNnMtNiwyLjY4Ny02LDZ2MzEuNTEyYzAsMS41OTEgMC42MzIsMy4xMTcgMS43NTcsNC4yNDNsMTMuNzksMTMuNzkxYzEuMTcyLDEuMTcxIDIuNzA3LDEuNzU3IDQuMjQzLDEuNzU3IDEuNTM1LDAgMy4wNzEtMC41ODYgNC4yNDMtMS43NTcgMi4zNDMtMi4zNDMgMi4zNDMtNi4xNDIgMC04LjQ4NWwtMTIuMDMzLTEyLjAzNXoiLz4KICA8L2c+Cjwvc3ZnPgo="}
CONTROLLER FILE:
/**
* #route("/uploadForumFiles", name="upload_forum_files", methods="POST")
*
* #access public
*
* #return JsonResponse
*/
public function uploadForumFiles(Request $request, PostsInterceptor $apiInterceptor, ContainerInterface $container): JsonResponse {
try {
$fileContent = $requestDtoObj->postFile;
$target_dir = 'images/'; // add the specific path to save the file
$decoded_file = base64_decode($fileContent); // decode the file
$mime_type = finfo_buffer(finfo_open(), $decoded_file, FILEINFO_MIME_TYPE); // extract mime type
$extension = $this->mime2ext($mime_type); // extract extension from mime type
$file = uniqid() .'.'. $extension; // rename file as a unique name
$file_dir = $target_dir. uniqid().'.'.$extension;
file_put_contents($target_dir, $file);
echo $file_dir; die;
} catch (Exception $ex) {
$this->logger->writeLog("ERROR", "Exception while uploading a file " . $ex->getMessage() . 'at line - ' . $ex->getLine() . ' in file' . $ex->getFile());
return $this->responseHandler->getFailedResponse(array($ex->getMessage()), $apiInterceptor);
}
}
public function mime2ext($mime){
$all_mimes = '{"png":["image/png","image/x-png"],"bmp":["image/bmp","image/x-bmp",
"image/x-bitmap","image/x-xbitmap","image/x-win-bitmap","image/x-windows-bmp",
"image/ms-bmp","image/x-ms-bmp","application/bmp","application/x-bmp",
"application/x-win-bitmap"],"gif":["image/gif"],"jpeg":["image/jpeg",
"image/pjpeg"],"xspf":["application/xspf+xml"],"vlc":["application/videolan"],
"wmv":["video/x-ms-wmv","video/x-ms-asf"],"au":["audio/x-au"],
"ac3":["audio/ac3"],"flac":["audio/x-flac"],"ogg":["audio/ogg",
"video/ogg","application/ogg"],"kmz":["application/vnd.google-earth.kmz"],
"kml":["application/vnd.google-earth.kml+xml"],"rtx":["text/richtext"],
"rtf":["text/rtf"],"jar":["application/java-archive","application/x-java-application",
"application/x-jar"],"zip":["application/x-zip","application/zip",
"application/x-zip-compressed","application/s-compressed","multipart/x-zip"],
"7zip":["application/x-compressed"],"xml":["application/xml","text/xml"],
"svg":["image/svg+xml","application/octet-stream"],"3g2":["video/3gpp2"],"3gp":["video/3gp","video/3gpp"],
"mp4":["video/mp4"],"m4a":["audio/x-m4a"],"f4v":["video/x-f4v"],"flv":["video/x-flv"],
"webm":["video/webm"],"aac":["audio/x-acc"],"m4u":["application/vnd.mpegurl"],
"pdf":["application/pdf"],
"pptx":["application/vnd.openxmlformats-officedocument.presentationml.presentation"],
"ppt":["application/powerpoint","application/vnd.ms-powerpoint","application/vnd.ms-office",
"application/msword"],"docx":["application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
"xlsx":["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","application/vnd.ms-excel"],
"xl":["application/excel"],"xls":["application/msexcel","application/x-msexcel","application/x-ms-excel",
"application/x-excel","application/x-dos_ms_excel","application/xls","application/x-xls"],
"xsl":["text/xsl"],"mpeg":["video/mpeg"],"mov":["video/quicktime"],"avi":["video/x-msvideo",
"video/msvideo","video/avi","application/x-troff-msvideo"],"movie":["video/x-sgi-movie"],
"log":["text/x-log"],"txt":["text/plain"],"css":["text/css"],"html":["text/html"],
"wav":["audio/x-wav","audio/wave","audio/wav"],"xhtml":["application/xhtml+xml"],
"tar":["application/x-tar"],"tgz":["application/x-gzip-compressed"],"psd":["application/x-photoshop",
"image/vnd.adobe.photoshop"],"exe":["application/x-msdownload"],"js":["application/x-javascript"],
"mp3":["audio/mpeg","audio/mpg","audio/mpeg3","audio/mp3"],"rar":["application/x-rar","application/rar",
"application/x-rar-compressed"],"gzip":["application/x-gzip"],"hqx":["application/mac-binhex40",
"application/mac-binhex","application/x-binhex40","application/x-mac-binhex40"],
"cpt":["application/mac-compactpro"],"bin":["application/macbinary","application/mac-binary",
"application/x-binary","application/x-macbinary"],"oda":["application/oda"],
"ai":["application/postscript"],"smil":["application/smil"],"mif":["application/vnd.mif"],
"wbxml":["application/wbxml"],"wmlc":["application/wmlc"],"dcr":["application/x-director"],
"dvi":["application/x-dvi"],"gtar":["application/x-gtar"],"php":["application/x-httpd-php",
"application/php","application/x-php","text/php","text/x-php","application/x-httpd-php-source"],
"swf":["application/x-shockwave-flash"],"sit":["application/x-stuffit"],"z":["application/x-compress"],
"mid":["audio/midi"],"aif":["audio/x-aiff","audio/aiff"],"ram":["audio/x-pn-realaudio"],
"rpm":["audio/x-pn-realaudio-plugin"],"ra":["audio/x-realaudio"],"rv":["video/vnd.rn-realvideo"],
"jp2":["image/jp2","video/mj2","image/jpx","image/jpm"],"tiff":["image/tiff"],
"eml":["message/rfc822"],"pem":["application/x-x509-user-cert","application/x-pem-file"],
"p10":["application/x-pkcs10","application/pkcs10"],"p12":["application/x-pkcs12"],
"p7a":["application/x-pkcs7-signature"],"p7c":["application/pkcs7-mime","application/x-pkcs7-mime"],"p7r":["application/x-pkcs7-certreqresp"],"p7s":["application/pkcs7-signature"],"crt":["application/x-x509-ca-cert","application/pkix-cert"],"crl":["application/pkix-crl","application/pkcs-crl"],"pgp":["application/pgp"],"gpg":["application/gpg-keys"],"rsa":["application/x-pkcs7"],"ics":["text/calendar"],"zsh":["text/x-scriptzsh"],"cdr":["application/cdr","application/coreldraw","application/x-cdr","application/x-coreldraw","image/cdr","image/x-cdr","zz-application/zz-winassoc-cdr"],"wma":["audio/x-ms-wma"],"vcf":["text/x-vcard"],"srt":["text/srt"],"vtt":["text/vtt"],"ico":["image/x-icon","image/x-ico","image/vnd.microsoft.icon"],"csv":["text/x-comma-separated-values","text/comma-separated-values","application/vnd.msexcel"],"json":["application/json","text/json"]}';
$all_mimes = json_decode($all_mimes,true);
foreach ($all_mimes as $key => $value) {
if(array_search($mime,$value) !== false) return $key;
}
return false;
}
file_put_contents creates the file if it doesn't exist, but it fails if you try to put the file in a directory that doesn't exist. So you should try the following:
check if the images directory exists
check the write permissions of the directory
try with an absolute path, so in your case probably $target_dir = '/home/ragiththomas/Sites/asco-forum/images/';
With symfony4, another way is to use vichuploadbundle. We can take an example with image.
according to this documentation integrating vichuploadbundle , install vichupload and configure it
composer require vich/uploader-bundle
In the config/bundles.php file don't forget to check if this line appear :
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
In the config/services.yaml, add this configuration (it's just an example)
# config/services.yaml
parameters:
app.path.images: /uploads/images
don't forget to create uploads directory inside public directory and images directory inside uploads directory.
And you configure the file config/packages/vich_uploader.yaml like :
# config/packages/vich_uploader.yaml
vich_uploader:
db_driver: orm
mappings:
object_image:
uri_prefix: '%app.path.images%'
upload_destination: '%kernel.project_dir%/public%app.path.images%'
namer: Vich\UploaderBundle\Naming\UniqidNamer
delete_on_remove: true
delete_on_update: true
create an image entity (with validation assert) like :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass="App\Repository\ImageRepository")
* #Vich\Uploadable()
* #ORM\Table(name="image")
*/
class Image
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var File|null
* #Assert\NotBlank(message="Please upload a file.")
* #Assert\File(
* maxSize = "1M",
* maxSizeMessage = "Maximum size allowed : {{ limit }} {{ suffix }}.",
* mimeTypes = {"image/png", "image/jpg", "image/jpeg"},
* mimeTypesMessage = "Allowed formats : png, jpg, jpeg."
* )
* #Vich\UploadableField(mapping="object_image", fileNameProperty="imageName")
*/
private $imageFile;
/**
* #var string|null
*
* #ORM\Column(type="string", length=255)
*/
private $imageName;
/**
* #var \DateTime
* #ORM\Column(type="datetime")
*
*/
private $createdAt;
/**
* #var \DateTime
* #ORM\Column(type="datetime")
*/
private $updatedAt;
public function __construct()
{
$this->createdAt = new \DateTime('now');
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return \DateTime
*/
public function getCreatedAt(): \DateTime
{
return $this->createdAt;
}
/**
* #param \DateTime $createdAt
* #return Image
*/
public function setCreatedAt(\DateTime $createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* #return \DateTime
*/
public function getUpdatedAt(): \DateTime
{
return $this->updatedAt;
}
/**
* #param \DateTime $updatedAt
* #return Image
*/
public function setUpdatedAt(\DateTime $updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* #return null|File
*/
public function getImageFile(): ?File
{
return $this->imageFile;
}
/**
* #param null|File $imageFile
* #return Image
*/
public function setImageFile(?File $imageFile): Image
{
$this->imageFile = $imageFile;
if($this->imageFile instanceof UploadedFile) {
$this->updatedAt = new \DateTime('now');
}
return $this;
}
/**
* #return null|string
*/
public function getImageName(): ?string
{
return $this->imageName;
}
/**
* #param null|string $imageName
* #return Image
*/
public function setImageName(?string $imageName): Image
{
$this->imageName = $imageName;
return $this;
}
}
create a form image entity like :
<?php
namespace App\Form;
use App\Entity\Image;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ImageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageFile', FileType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Image::class
]);
}
}
you create an UploadedBase64File service (in Utils directory that you will create ) like
<?php
namespace App\Utils;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class UploadedBase64File extends UploadedFile
{
public function __construct(string $base64Content, string $originalName)
{
$filePath = tempnam(sys_get_temp_dir(), 'UploadedFile');
$data = base64_decode($this->getBase64String($base64Content));
file_put_contents($filePath, $data);
$error = null;
$mimeType = null;
$test = true;
parent::__construct($filePath, $originalName, $mimeType, $error, $test);
}
private function getBase64String(string $base64Content)
{
$data = explode(';base64,', $base64Content);
return $data[1];
}
}
Finally in a controller, you can do something like :
<?php
namespace App\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Utils\UploadedBase64File;
class CoreController extends AbstractController
{
/**
* #Route("/api/images", methods={"POST"}, name="api_add_image")
*/
public function addImage(Request $request)
{
$data = json_decode($request->getContent(), true);
if($data === null
|| !is_array($data)
|| count($data) !== 1
|| !isset($data['image']['name'], $data['image']['value'])
|| count($data['image']) !== 2
) {
// Throw invalid format request for image
}
$imageFile = new UploadedBase64File($data['image']['value'], $data['image']['name']);
$image = new Image();
$form = $this->createForm(ImageType::class, $image, ['csrf_protection' => false]);
$form->submit(['imageFile' => $imageFile]);
if(!($form->isSubmitted() && $form->isValid())) {
// Send json form error
}
// Persist, do thing you want to do and send json response
}
}
And if you want to test in postman, you can do something like :
{
"image": {
"name": "originalfilename.png",
"value": "base64 content"
}
}
This is an attempt of other solution.

File not found exception on Symfony upload

I'm using Symfony 3.4 to work on a simple REST API microservice. There are not much resources to be found when working with HTTP APIs and file uploads. I'm following some of the instructions from the documentation but I found a wall.
What I want to do is to store the relative path to an uploaded file on an entity field, but it seems like the validation expects the field to be a full path.
Here's some of my code:
<?php
// BusinessClient.php
namespace DemoBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use ApiBundle\Entity\BaseEntity;
use ApiBundle\Entity\Client;
use JMS\Serializer\Annotation as Serializer;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints;
/**
* Class BusinessClient
* #package DemoBundle\Entity
* #ORM\Entity(repositoryClass="DemoBundle\Repository\ClientRepository")
* #ORM\Table(name="business_client")
* #Serializer\ExclusionPolicy("all")
* #Serializer\AccessorOrder("alphabetical")
*/
class BusinessClient extends BaseEntity
{
/**
* #Constraints\NotBlank()
* #ORM\ManyToOne(targetEntity="ApiBundle\Entity\Client", fetch="EAGER")
* #ORM\JoinColumn(name="client_id", referencedColumnName="oauth2_client_id", nullable=false)
*/
public $client;
/**
* #Constraints\NotBlank()
* #ORM\Column(type="string", length=255, nullable=false)
* #Serializer\Expose
*/
protected $name;
/**
* #Constraints\Image(minWidth=100, minHeight=100)
* #ORM\Column(type="text", nullable=true)
*/
protected $logo;
/**
* One Business may have many brands
* #ORM\OneToMany(targetEntity="DemoBundle\Entity\Brand", mappedBy="business")
* #Serializer\Expose
*/
protected $brands;
/**
* BusinessClient constructor.
*/
public function __construct()
{
$this->brands = new ArrayCollection();
}
/**
* Set the links property for the resource response
*
* #Serializer\VirtualProperty(name="_links")
* #Serializer\SerializedName("_links")
*/
public function getLinks()
{
return [
"self" => "/clients/{$this->getId()}",
"brands" => "/clients/{$this->getId()}/brands"
];
}
/**
* Get the name of the business client
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the name of the business client
*
* #param string $name
*/
public function setName($name): void
{
$this->name = $name;
}
/**
* Get the logo
*
* #Serializer\Expose
* #Serializer\VirtualProperty(name="logo")
* #Serializer\SerializedName("logo")
*/
public function getLogo()
{
return $this->logo;
}
/**
* Set the logo field
*
* #param string|File|UploadedFile $logo
*/
public function setLogo($logo): void
{
$this->logo = $logo;
}
/**
* Get the client property
*
* #return Client
*/
public function getClient()
{
return $this->client;
}
/**
* Set the client property
*
* #param Client $client
*/
public function setClient($client): void
{
$this->client = $client;
}
}
Uploader Service:
<?php
namespace DemoBundle\Services;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Class FileUploader
* #package DemoBundle\Services
*/
class FileUploader
{
/** #var string $uploadDir The directory where the files will be uploaded */
private $uploadDir;
/**
* FileUploader constructor.
* #param $uploadDir
*/
public function __construct($uploadDir)
{
$this->uploadDir = $uploadDir;
}
/**
* Upload a file to the specified upload dir
* #param UploadedFile $file File to be uploaded
* #return string The unique filename generated
*/
public function upload(UploadedFile $file)
{
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move($this->getTargetDirectory(), $fileName);
return $fileName;
}
/**
* Get the base dir for the upload files
* #return string
*/
public function getTargetDirectory()
{
return $this->uploadDir;
}
}
I've registered the service:
services:
# ...
public: false
DemoBundle\Services\FileUploader:
arguments:
$uploadDir: '%logo_upload_dir%'
And last the controller:
<?php
namespace DemoBundle\Controller;
use ApiBundle\Exception\HttpException;
use DemoBundle\Entity\BusinessClient;
use DemoBundle\Services\FileUploader;
use FOS\RestBundle\Controller\Annotations as REST;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Swagger\Annotations as SWG;
use Symfony\Component\Validator\Constraints\ImageValidator;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Class BusinessClientController
* #package DemoBundle\Controller
*/
class BusinessClientController extends BaseController
{
/**
* Create a new business entity and persist it in database
*
* #REST\Post("/clients")
* #SWG\Tag(name="business_clients")
* #SWG\Response(
* response="201",
* description="Create a business client and return it's data"
* )
* #param Request $request
* #param FileUploader $uploader
* #return Response
* #throws HttpException
*/
public function createAction(Request $request, FileUploader $uploader, LoggerInterface $logger)
{
$entityManager = $this->getDoctrine()->getManager();
$oauthClient = $this->getOauthClient();
$data = $request->request->all();
$client = new BusinessClient();
$client->setName($data["name"]);
$client->setClient($oauthClient);
$file = $request->files->get('logo');
if (!is_null($file)) {
$fileName = $uploader->upload($file);
$client->setLogo($fileName);
}
$errors = $this->validate($client);
if (count($errors) > 0) {
$err = [];
/** #var ConstraintViolationInterface $error */
foreach ($errors as $error) {
$err[] = [
"message" => $error->getMessage(),
"value" => $error->getInvalidValue(),
"params" => $error->getParameters()
];
}
throw HttpException::badRequest($err);
}
$entityManager->persist($client);
$entityManager->flush();
$r = new Response();
$r->setContent($this->serialize($client));
$r->setStatusCode(201);
$r->headers->set('Content-type', 'application/json');
return $r;
}
/**
* Get data for a single business client
*
* #REST\Get("/clients/{id}", requirements={"id" = "\d+"})
* #param $id
* #return Response
* #SWG\Tag(name="business_clients")
* #SWG\Response(
* response="200",
* description="Get data for a single business client"
* )
*/
public function getClientAction($id)
{
$object = $this->getDoctrine()->getRepository(BusinessClient::class)
->find($id);
$j = new Response($this->serialize($object));
return $j;
}
}
When I try to set the logo as a file basename string the request will fail. with error that the file (basename) is not found. This makes sense in a way.
If otherwise I try to set not a string but a File with valid path to the newly uploaded file the request will succeed, but the field in the table will be replaced with a full system path. The same happens when I put a valid system path instead of a file.
<?php
// Controller
.....
// This works
if (!is_null($file)) {
$fileName = $uploader->upload($file);
$client->setLogo($this->getParameter("logo_upload_dir")."/$fileName");
}
Parameter for the upload dir:
parameters:
logo_upload_dir: '%kernel.project_dir%/web/uploads/logos'
I'm not using any forms as this is a third party API and I'm mainly using the request objects directly to handle the data. Most of the documentations used Forms to handle this. Also all my responses are in JSON.
I'd appreciate any help on this. Otherwise I'll have to store the full path and that in not a good idea and very impractical.
Thanks in advance.
Here is a thought on this: Instead of validating the property which your plan to be a relative path to an image, validate the method. Something like this maybe:
class BusinessClient extends BaseEntity
{
public static $basePath;
// ....
/**
* Get the logo
*
* #Constraints\Image(minWidth=100, minHeight=100)
*/
public function getAbsolutePathLogo()
{
return self::$basePath . '/' . $this->logo;
}
So, remove the validation from your logo member, add a new method (I named it getAbsolutePathLogo buy you can choose anything) and set up validation on top of it.
This way, your logo will be persisted as relative path and validation should work. However, the challenge now is to determine the right moment to set that static $basePath. In reality, this one does not even need to be a class static member, but could be something global:
return MyGlobalPath::IMAGE_PATH . '/' . $this->logo;
Does this make sense?
Hope it helps a bit...

Reading annotations with Symfony4

I'm trying to read annotations with Symfony4 but looks like something is not working!
The class I'm trying to read from:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\OAuthClientRepository")
*/
class OAuthClient {
{
/**
* #Assert\NotBlank()
* #ORM\Column(type="string")
*/
protected $name;
}
The code I'm using to read the annotations:
<?php
namespace App\Test;
use Doctrine\Common\Annotations\SimpleAnnotationReader as DocReader;
/**
* Helper AnnotationReader
*/
class AnnotationReader
{
/**
* #param $class
* #return null|\StdClass
*/
public static function getClass($class)
{
$reader = new DocReader;
$reflector = new \ReflectionClass($class);
return $reader->getClassAnnotations($reflector);
}
/**
* #param $class
* #param $property
* #return array
*/
public static function getProperty($class, $property)
{
$reader = new DocReader;
$reflector = new \ReflectionProperty($class, $property);
return $reader->getPropertyAnnotations($reflector);
}
/**
* #param $class
* #param $method
* #return array
*/
public static function getMethod($class, $method)
{
$reader = new DocReader;
$reflector = new \ReflectionMethod($class, $method);
return $reader->getMethodAnnotations($reflector);
}
}
I get empty arrays when I call:
App\Test\AnnotationReader::getClass(App\Entity\OAuthClient::class);
App\Test\AnnotationReader::getProperty(App\Entity\OAuthClient::class, 'name');
What am I doing wrong?
What is the best way to read annotation?
I'm looking to read the validations used on a class property.
Thank you for your help!
change
use Doctrine\Common\Annotations\SimpleAnnotationReader as DocReader;
to
use Doctrine\Common\Annotations\AnnotationReader as DocReader;
and it works.
You may have to call the addNamespace() method on the SimpleAnnotationReader instance.
For instance, for ORM annotations:
$reader->addNamespace('Doctrine\ORM\Mapping');
And for validation annotations:
$reader->addNamespace('Symfony\Component\Validator\Constraints');
See:
SimpleAnnotationReader API: https://www.doctrine-project.org/api/annotations/latest/Doctrine/Annotations/SimpleAnnotationReader.html
SimpleAnnotationReader examples: https://github.com/doctrine/doctrine2/blob/462173ad71ae63cd9877e1e642f7968ed1f9971b/lib/Doctrine/ORM/Configuration.php#L140-L141

Repository method not found

I have created a method to query to database for all joining table in repository. I also have read the documentation from How to create custom repository. The method is working properly, but in PhpStorm there is a yellow warning
Method 'findAllDetail' not found.
How do I fix this warning?
Below is my entity:
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\InvoiceRepository")
*/
class Invoice
{
and here is the InvoiceRepository:
namespace App\Repository;
use App\Entity\Invoice;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* #method Invoice|null find($id, $lockMode = null, $lockVersion = null)
* #method Invoice|null findOneBy(array $criteria, array $orderBy = null)
* #method Invoice[] findAll()
* #method Invoice[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class InvoiceRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Invoice::class);
}
/**
* #param $value
* #return Invoice[] Returns an array of Invoice objects
*/
public function findAllDetail($value)
{
$qb = $this->createQueryBuilder('i')
/* .... */
;
return $qb->execute();
}
and here is the controller:
/**
* #Route("/invoice/review/{idInvoice}", name="submitToReview", requirements={"idInvoice"="\d+"})
* #param $idInvoice
* #return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function submitToReview($idInvoice, \Swift_Mailer $mailer)
{
$invoice = $this->getDoctrine()->getRepository(Invoice::class)->findAllDetail($idInvoice);
/* #var $item \App\Entity\Invoice */
For those people who get the same warning, below is how i fix it (based on M. Kebza Comment)
put your repository in controller and pass repository as parameter in the controller
in controller
...
use App\Repository\InvoiceRepository;
/**
* #Route("/invoice/review/{idInvoice}", name="submitToReview", requirements={"idInvoice"="\d+"})
* #param $idInvoice
* #return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function submitToReview($idInvoice, \Swift_Mailer $mailer, InvoiceRepository $repository )
{
$invoice = $repository->findAllDetail($idInvoice);
/* #var $item \App\Entity\Invoice */
Basically when you do
$this->getDoctrine()->getRepository()
It returns a Doctrine EntityRepository. So that's normal if phpstorm is complaining about your findAllDetails method because EntityRepository does not know it.
From the documentation:
By default the EntityManager returns a default implementation of Doctrine\ORM\EntityRepository when you call EntityManager#getRepository($entityClass).
So even if you override this behavior as suggested in the doc, I suppose that phpstorm keeps reference to the default.
Repository class make public in services.yaml file and call in controller
$invoiceRepository = $this->get(InvoiceRepository::class);
or
/** #var InvoiceRepository $invoiceRepository **/
$invoiceRepository = $this->getDoctrine()->getRepository(Invoice::class);
$invoice = $invoiceRepository->findAllDetail($idInvoice);
I would construct the controller with the repository (and the mailer) rather than put it into the action - the autowiring will sort the injection, there's no uri parameter confusion, and it's good to be explicit - your future self will thank you:
# Controller.php
/**
* #var InvoiceRepository
*/
private $invoiceRepo;
/**
* #var \Swift_Mailer
*/
private $mailer;
/**
* #param InvoiceRepository $invoiceRepo
* #param \Swift_Mailer $mailer
*/
public function __construct(InvoiceRepository $invoiceRepo, \Swift_Mailer $mailer)
{
$this->invoiceRepo = $invoiceRepo;
$this->mailer = $mailer;
}
/**
* #Route("/invoice/review/{idInvoice}", name="submitToReview", requirements={"idInvoice"="\d+"})
* #param $idInvoice
* #return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function submitToReview($idInvoice)
{
$invoice = $this->invoiceRepo->findAllDetail($idInvoice);
...
}

Categories