15/12/2015 - SYMFONY
İçinde dosya eklentisi olmayan istekleri işlemek kolaydır ama eklentisi olanlar biraz karışıktır. Aşağıdaki adımlar, işlemin basit bir akış diyagramıdır.
Uygulamanızda src/Application/FrontendBundle/Resources/upload/.gitkeep
klasörünü yaratın.
parameters:
upload_dir: %kernel.root_dir%/../src/Application/FrontendBundle/Resources/upload/
services:
application_frontend.controller.abstract:
class: Application\BackendBundle\Controller\AbstractController
abstract: true
arguments:
- @serializer
- @validator
- @doctrine_common_inflector
application_frontend.controller.message:
class: Application\FrontendBundle\Controller\MessageController
parent: application_backend.controller.abstract
arguments:
- @application_frontend.service.message
services:
application_frontend.service.message:
class: Application\FrontendBundle\Service\MessageService
arguments:
- @application_frontend.factory.message
- @application_frontend.util.upload_helper
services:
application_frontend.factory.message:
class: Application\FrontendBundle\Factory\MessageFactory
services:
application_frontend.util.upload_helper:
class: Application\FrontendBundle\Util\UploadHelper
arguments:
- %upload_dir%
namespace Application\FrontendBundle\Controller;
use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
abstract class AbstractController
{
private $validContentTypes = ['json' => 'application/json', 'xml' => 'application/xml'];
protected $serializer;
protected $validator;
protected $inflector;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
$this->serializer = $serializer;
$this->validator = $validator;
$this->inflector = $inflector;
}
/**
* @param string $contentType
*
* @return string|Response
*/
protected function validateContentType($contentType)
{
if (!in_array($contentType, $this->validContentTypes)) {
return $this->createFailureResponse(
['content_type' => sprintf('Invalid content type [%s].', $contentType)],
'json',
415
);
}
return array_search($contentType, $this->validContentTypes);
}
/**
* @param string $payload
* @param string $model
* @param string $format
*
* @return object|Response
*/
protected function validatePayload($payload, $model, $format)
{
$payload = $this->serializer->deserialize($payload, $model, $format);
$errors = $this->validator->validate($payload);
if (count($errors)) {
return $this->createFailureResponse($errors, $format);
}
return $payload;
}
/**
* @param array|object $content
* @param string $format
* @param int $status
*
* @return Response
*/
protected function createSuccessResponse($content, $format = 'json', $status = 200)
{
return $this->getResponse($content, $format, $status);
}
/**
* @param array|ConstraintViolationListInterface $content
* @param string $format
* @param int $status
*
* @return Response
*/
protected function createFailureResponse($content, $format = 'json', $status = 400)
{
$errorList = null;
if ($content instanceof ConstraintViolationList) {
foreach ($content as $error) {
$error = $this->getErrorFromValidation($error);
$errorList[$error['key']] = $error['value'];
}
} else {
$errorList = $content;
}
return $this->getResponse(['errors' => $errorList], $format, $status);
}
/**
* @param array|object $content
* @param string $format
* @param int $status
*
* @return Response
*/
private function getResponse($content, $format, $status)
{
$context = new SerializationContext();
$context->setSerializeNull(false);
$response = $this->serializer->serialize($content, $format, $context);
return new Response($response, $status, ['Content-Type' => $this->validContentTypes[$format]]);
}
/**
* @param ConstraintViolationInterface $error
*
* @return array
*/
private function getErrorFromValidation($error)
{
$properties = $this->inflector->tableize($error->getPropertyPath());
return ['key' => $properties, 'value' => $error->getMessage()];
}
}
namespace Application\FrontendBundle\Controller;
use Application\FrontendBundle\Service\MessageServiceInterface;
use Doctrine\Common\Inflector\Inflector;
use Exception;
use JMS\Serializer\SerializerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\File\Exception\UploadException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @Route("message", service="application_frontend.controller.message")
*/
class MessageController extends AbstractController
{
private $messageService;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector,
MessageServiceInterface $messageService
) {
parent::__construct($serializer, $validator, $inflector);
$this->messageService = $messageService;
}
/**
* @param Request $request
*
* @Method({"POST"})
* @Route("")
*
* @return Response
*/
public function messageAction(Request $request)
{
try {
// Get files and the request payload
$files = $request->files->all();
$payload = json_decode($request->getContent(), true);
// Create a model from the files and the request payload
$message = $this->messageService->createMessageModel(
isset($payload['body']) ? $payload['body'] : null,
$files
);
// Validate model
$errors = $this->validator->validate($message);
if (count($errors)) {
return $this->createFailureResponse($errors);
}
$result = $this->messageService->upload($message);
print_r($result);
return $this->createSuccessResponse('Successful');
} catch (Exception $e) {
throw new UploadException($e->getMessage());
}
}
}
namespace Application\FrontendBundle\Service;
use Application\FrontendBundle\Model\Message;
use Application\FrontendBundle\Model\Result;
use Application\FrontendBundle\Model\Upload;
interface MessageServiceInterface
{
/**
* @param null|string $body
* @param array $files
*
* @return Upload
*/
public function createMessageModel($body = null, array $files = []);
/**
* @param Message $message
*
* @return Result
*/
public function upload(Message $message);
}
namespace Application\FrontendBundle\Service;
use Application\FrontendBundle\Factory\MessageFactoryInterface;
use Application\FrontendBundle\Model\Message;
use Application\FrontendBundle\Model\Result;
use Application\FrontendBundle\Util\UploadHelper;
class MessageService implements MessageServiceInterface
{
private $messageFactory;
private $uploadHelper;
public function __construct(
MessageFactoryInterface $messageFactory,
UploadHelper $uploadHelper
) {
$this->messageFactory = $messageFactory;
$this->uploadHelper = $uploadHelper;
}
/**
* @param null|string $body
* @param array $files
*
* @return Message
*/
public function createMessageModel($body = null, array $files = [])
{
return $this->messageFactory->createMessageModel($body, $files);
}
/**
* @param Message $message
*
* @return Result
*/
public function upload(Message $message)
{
return $this->uploadHelper->attachments($message);
}
}
namespace Application\FrontendBundle\Factory;
use Application\FrontendBundle\Model\Message;
interface MessageFactoryInterface
{
/**
* @param null|string $body
* @param array $files
*
* @return Message
*/
public function createMessageModel($body = null, array $files = []);
}
namespace Application\FrontendBundle\Factory;
use Application\FrontendBundle\Model\File;
use Application\FrontendBundle\Model\Message;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class MessageFactory implements MessageFactoryInterface
{
/**
* @param null|string $body
* @param array $files
*
* @return Message
*/
public function createMessageModel($body = null, array $files = [])
{
$message = new Message();
$message->body = $body;
/** @var UploadedFile $uploadedFile */
foreach ($files as $uploadedFile) {
$file = new File();
$file->file = $uploadedFile;
$message->files[] = $file;
}
return $message;
}
}
namespace Application\FrontendBundle\Util;
use Application\FrontendBundle\Model\File;
use Application\FrontendBundle\Model\Message;
use Application\FrontendBundle\Model\Result;
use Application\FrontendBundle\Model\Upload;
use Exception;
use Symfony\Component\HttpFoundation\File\Exception\UploadException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class UploadHelper
{
private $uploadDir;
public function __construct($uploadDir)
{
$this->uploadDir = $uploadDir;
}
/**
* @param Message $message
*
* @return Result
*
* @throws UploadException
*/
public function attachments(Message $message)
{
try {
$result = new Result();
$result->message = $message->body;
/** @var File $file */
foreach ($message->files as $files) {
$filename = sprintf(
'%s.%s',
$this->createFilename($files->file),
pathinfo($files->file->getClientOriginalName(), PATHINFO_EXTENSION)
);
$files->file->move($this->uploadDir, $filename);
$upload = new Upload();
$upload->name = $files->file->getClientOriginalName();
$upload->path = $this->uploadDir.$filename;
$upload->mime = $files->file->getClientMimeType();
$result->uploads[] = $upload;
}
return $result;
} catch (Exception $e) {
throw new UploadException($e->getMessage());
}
}
/**
* @param UploadedFile $uploadedFile
*
* @return string
*/
private function createFilename(UploadedFile $uploadedFile)
{
return sha1($uploadedFile->getFilename().$uploadedFile->getSize().microtime().mt_rand());
}
}
namespace Application\FrontendBundle\Model;
use JMS\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
class Message
{
/**
* @var string
*
* @Assert\NotBlank(message="You must supply a message body.")
*
* @Serializer\Type("string")
*/
public $body;
/**
* @var File[]
*
* @Assert\Valid(traverse="true")
*
* @Serializer\Type("array<Application\FrontendBundle\Model\File>")
*/
public $files = [];
}
namespace Application\FrontendBundle\Model;
use Symfony\Component\HttpFoundation\File\File as UploadedFile;
class File
{
/**
* @var UploadedFile
*/
public $file;
}
Aşağıdaki kodu ekleyip mime validation yapabilirsiniz.
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Assert\File(
* mimeTypes = {
* "image/png",
* "image/gif",
* "image/jpg",
* "image/jpeg"
* }
* )
*/
namespace Application\FrontendBundle\Model;
class Result
{
/**
* @var string
*/
public $message;
/**
* @var Upload[]
*/
public $uploads = [];
}
namespace Application\FrontendBundle\Model;
class Upload
{
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $path;
/**
* @var string
*/
public $mime;
}
Adresimiz POST http://football.local/app_dev.php/message
{
"body": "Hello"
}
Application\FrontendBundle\Model\Result Object
(
[message] => Hello
[uploads] => Array
(
)
)
"Successful"
Bu versiyonu Behat ile taklit ettim o nedenle ekleri FeatureContext dosyası ile yaratıp isteğe ekledim. Bu işlemin nasıl yapıldığına dair örneği, blog içindeki Behat bölümünde bulabilirsiniz.
{
"body": "I also have attachments in this request"
}
Application\FrontendBundle\Model\Result Object
(
[message] => I also have attachments in this request
[uploads] => Array
(
[0] => Application\FrontendBundle\Model\Upload Object
(
[name] => dummy.png
[path] => /Library/WebServer/Documents/football/app/../src/Application/FrontendBundle/Resources/upload/361da8a6da4c7bc23bf36f0ffb26318c5f2cbbf3.png
[mime] => image/png
)
[1] => Application\FrontendBundle\Model\Upload Object
(
[name] => dummy.doc
[path] => /Library/WebServer/Documents/football/app/../src/Application/FrontendBundle/Resources/upload/dc35300bfaf6e5c686c3ac31cb38ee55745c0109.doc
[mime] => application/msword
)
)
)