Skip to content
This repository has been archived by the owner on Apr 20, 2021. It is now read-only.

Handle file uploads correct #275

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ env:
- DEPS=low PROFILE=default
- DEPS=high PROFILE=symfony2
- DEPS=low PROFILE=symfony2
- DEPS=high PROFILE=browserKit
- DEPS=low PROFILE=browserKit

php:
- 5.5
Expand Down
20 changes: 20 additions & 0 deletions behat.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ default:
suites:
default:
paths: [ '%paths.base%/tests/features' ]
filters:
tags: '~@symfony'
contexts:
- Behat\MinkExtension\Context\MinkContext
- behatch:context:browser:
Expand Down Expand Up @@ -33,3 +35,21 @@ symfony2:
extensions:
Behat\MinkExtension:
default_session: symfony2

browserKit:
suites:
default:
filters:
tags: '@symfony'
extensions:
Behat\Symfony2Extension:
kernel:
bootstrap: 'tests/features/bootstrap/Bootstrap.php'
class: App\Kernel
env: test
debug: false
Behat\MinkExtension:
default_session: browserKit
sessions:
browserKit:
symfony2: ~
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
},

"require-dev": {
"symfony/templating": "^2.3|^3.0|^4.0",
"behat/symfony2-extension": "^2.1",
"behat/mink-goutte-driver": "^1.1",
"guzzlehttp/guzzle": "^6.3",
"behat/mink-selenium2-driver": "^1.3",
Expand All @@ -25,7 +27,8 @@

"autoload": {
"psr-4": {
"Behatch\\": "src/"
"Behatch\\": "src/",
"App\\": "tests/fixtures/www/app"
}
},

Expand Down
48 changes: 42 additions & 6 deletions src/HttpCall/Request/BrowserKit.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,22 @@ public function getContent()

public function send($method, $url, $parameters = [], $files = [], $content = null, $headers = [])
{
foreach ($files as $originalName => &$file) {
if (is_string($file)) {
$file = new UploadedFile($file, $originalName);
}
}

$client = $this->mink->getSession()->getDriver()->getClient();

$tmpFiles = [];
if (!$client instanceof GoutteClient) {
$tmpFiles = $this->convertFilesToSymfonyUploadedFiles($files);
}

$client->followRedirects(false);
$client->request($method, $url, $parameters, $files, $headers, $content);
$client->followRedirects(true);
$this->resetHttpHeaders();

foreach ($tmpFiles as $tmpName) {
@unlink($tmpName);
}

return $this->mink->getSession()->getPage();
}

Expand Down Expand Up @@ -141,4 +144,37 @@ protected function resetHttpHeaders()
$client->restart();
}
}

private function convertFilesToSymfonyUploadedFiles(& $files)
{
$tmpFiles = [];
foreach ($files as $key => &$file) {
$tmpName = false;
if (is_string($file)) {
$tmpName = tempnam(sys_get_temp_dir(), 'upload');
copy($file, $tmpName);
$tmpFiles[] = $tmpName;
$originalName = $file;
} elseif (is_array($file)) {
// This mirrors Goutte\Client::addPostFiles() called from Goutte\Client::doRequest()
// so that a Symfony\Component\HttpKernel\Client can have the same behaviour
if (isset($file['tmp_name'])) {
$tmpName = $file['tmp_name'];
if (isset($file['name'])) {
$originalName = $file['name'];
} else {
$originalName = $tmpName;
}
} else {
$subTmpFiles = $this->convertFilesToSymfonyUploadedFiles($file);
$tmpFiles = array_merge($tmpFiles, $subTmpFiles);
}
}
if ($tmpName) {
$file = new UploadedFile($tmpName, $originalName, null, null, true);
}
}

return $tmpFiles;
}
}
1 change: 1 addition & 0 deletions tests/features/bootstrap/Bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<?php

putenv('APP_ENV=' . $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = 'test');
require_once __DIR__ . '/../../../vendor/autoload.php';
19 changes: 19 additions & 0 deletions tests/features/symfony.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Feature: Testing RESTContext

@symfony
Scenario: Testing post method
When I send a POST request to "/symfony/rest" with parameters:
| key | value |
| foo | bar |
| foofile1 | @lorem.txt |
| foofile2 | @lorem.txt |
Then I should see "You have sent a POST request. "
And I should see "1 parameter(s)"
And I should see "2 file(s)"
And I should see "foo : bar"
And I should see "foofile1 - name : lorem.txt"
And I should see "foofile1 - error : 0"
And I should see "foofile1 - size : 39"
And I should see "foofile2 - name : lorem.txt"
And I should see "foofile2 - error : 0"
And I should see "foofile2 - size : 39"
40 changes: 40 additions & 0 deletions tests/fixtures/www/app/Controller/RestController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace App\Controller;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RestController implements ContainerAwareInterface
{
use ContainerAwareTrait;

public function action(Request $request)
{
$files = $request->files->all();
$displayFiles = [];
/** @var UploadedFile $file */
foreach ($files as $key => $file) {
$displayFiles[$key] = [
'name' => $file->getClientOriginalName(),
'error' => $file->getError(),
'size' => $file->getSize(),
];
$file->move('/tmp/moved');
}

$view = 'templates/rest.php';
$parameters = [
'server' => $request->server->all(),
'method' => $request->getMethod(),
'request' => $request->request->all(),
'files' => $displayFiles,
'body' => $request->getContent(),
];
$content = $this->container->get('templating')->render($view, $parameters);
return new Response($content);
}
}
97 changes: 97 additions & 0 deletions tests/fixtures/www/app/Kernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace App;

use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;

class Kernel extends BaseKernel
{
const CONFIG_EXTS = '.yaml';

private $projectDir;

public function getProjectDir()
{
if (null === $this->projectDir) {
$this->projectDir = realpath(__DIR__ . '/../framework');
}

return $this->projectDir;
}

public function getCacheDir()
{
return $this->getProjectDir().'/var/cache/'.$this->environment;
}

public function getLogDir()
{
return $this->getProjectDir().'/var/log';
}

public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(function (ContainerBuilder $container) use ($loader) {
$container->loadFromExtension('framework', [
'router' => [
'resource' => 'kernel::loadRoutes',
'type' => 'service',
],
]);

if ($this instanceof EventSubscriberInterface) {
$container->register('kernel', static::class)
->setSynthetic(true)
->setPublic(true)
->addTag('kernel.event_subscriber');
}

$this->configureContainer($container, $loader);

$container->addObjectResource($this);
});
}

public function loadRoutes(LoaderInterface $loader)
{
$routes = new RouteCollectionBuilder($loader);
$this->configureRoutes($routes);

return $routes->build();
}

public function registerBundles()
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if (isset($envs['all']) || isset($envs[$this->environment])) {
yield new $class();
}
}
}

protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
// Feel free to remove the "container.autowiring.strict_mode" parameter
// if you are using symfony/dependency-injection 4.0+ as it's the default behavior
$container->setParameter('container.autowiring.strict_mode', true);
$container->setParameter('container.dumper.inline_class_loader', true);
$confDir = $this->getProjectDir().'/config';

$loader->load($confDir.'/packages/framework'. self::CONFIG_EXTS);
$loader->load($confDir.'/services'.self::CONFIG_EXTS);
}

protected function configureRoutes(RouteCollectionBuilder $routes)
{
$confDir = $this->getProjectDir().'/config';

$routes->import($confDir.'/routes'.self::CONFIG_EXTS, '/');
}
}
32 changes: 32 additions & 0 deletions tests/fixtures/www/app/templates/rest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
You have sent a <?php print $method; ?> request.

<?php print sizeof($server); ?> header(s) received.
<?php foreach ($server as $key => $value): ?>
<br/><?php print $key ?> : <?php print $value; ?>
<?php endforeach; ?>

<?php if (sizeof($request) == 0): ?>
<br/>No parameter received.
<?php else: ?>
<br/><?php print sizeof($request); ?> parameter(s) received.
<?php foreach ($request as $key => $value): ?>
<br/><?php print $key ?> : <?php print $value; ?>
<?php endforeach; ?>
<?php endif; ?>

<?php if (sizeof($files) == 0): ?>
<br/>No files received.
<?php else: ?>
<br/><?php print sizeof($files); ?> file(s) received.
<?php foreach ($files as $key => $value): ?>
<br/><?php print $key ?> - name : <?php print $value['name']; ?>
<br/><?php print $key ?> - error : <?php print $value['error']; ?>
<br/><?php print $key ?> - size : <?php print $value['size']; ?>
<?php endforeach; ?>
<?php endif; ?>

<?php if ($body == null): ?>
<br/>No body received.
<?php else: ?>
<br/>Body : <?php print $body; ?>
<?php endif; ?>
5 changes: 5 additions & 0 deletions tests/fixtures/www/framework/config/bundles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
];
9 changes: 9 additions & 0 deletions tests/fixtures/www/framework/config/packages/framework.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
framework:
test: true
secret: 'xx-top-secret-yy'

router:
strict_requirements: ~

templating:
engines: ['php']
3 changes: 3 additions & 0 deletions tests/fixtures/www/framework/config/routes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
rest:
path: /symfony/rest
controller: 'App\Controller\RestController:action'
32 changes: 32 additions & 0 deletions tests/fixtures/www/framework/config/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'

services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.

# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
# App\:
# resource: '../../app/*'
# exclude: '../../app/Kernel.php}'
# public: true

# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../../app/Controller'
tags: ['controller.service_arguments']

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones