I am trying to build a file management system in Laravel based on league/flysystem: https://github.com/thephpleague/flysystem
I am using the S3 adapter and I have it working to save the uploaded files using:
$filesystem->write('filename.txt', 'contents');
Now I am stuck on generating the download file URL when using the S3 adapter.
The files are saved correctly in the S3 bucket, I have permissions to access them, I just don't know how to get to the S3 getObjectUrl method through the league/flysystem package.
I have tried:
$contents = $filesystem->read('filename.txt');
but that returns the content of the file.
$contents = $filemanager->listContents();
or
$paths = $filemanager->listPaths();
but they give me the relative paths to my files.
What I need is something like "ht...//[s3-region].amazonaws.com/[bucket]/[dir]/[file]..."
I am using Laravel 5.2 and the code below seemed to work fine.
Storage::cloud()->url('filename');
I'm not sure what the correct way of doing this is with Flysystem, but the underlying S3Client object has a method for doing that. You could do $filesystem->getAdapter()->getClient()->getObjectUrl($bucket, $key);. Of course, building the URL is as trivial as you described, so you don't really need a special method to do it.
When updating to Laravel 5.1 this method no longer supported by the adapter. No in your config you must have the S3_REGION set or you will get a invalid hostname error and secondly I had to use the command as input to create the presignedRequest.
public function getFilePathAttribute($value)
{
$disk = Storage::disk('s3');
if ($disk->exists($value)) {
$command = $disk->getDriver()->getAdapter()->getClient()->getCommand('GetObject', [
'Bucket' => Config::get('filesystems.disks.s3.bucket'),
'Key' => $value,
'ResponseContentDisposition' => 'attachment;'
]);
$request = $disk->getDriver()->getAdapter()->getClient()->createPresignedRequest($command, '+5 minutes');
return (string) $request->getUri();
}
return $value;
}
Maybe I'm a little late to this question, but here's a way to use Laravel 5's built-in Filesystem.
I created a Manager class that extends Laravel's FilesystemManager to handle the public url retrieval:
class FilesystemPublicUrlManager extends FilesystemManager
{
public function publicUrl($name = null, $object_path = '')
{
$name = $name ?: $this->getDefaultDriver();
$config = $this->getConfig($name);
return $this->{'get' . ucfirst($config['driver']) . 'PublicUrl'}($config, $object_path);
}
public function getLocalPublicUrl($config, $object_path = '')
{
return URL::to('/public') . $object_path;
}
public function getS3PublicUrl($config, $object_path = '')
{
$config += ['version' => 'latest'];
if ($config['key'] && $config['secret']) {
$config['credentials'] = Arr::only($config, ['key', 'secret']);
}
return (new S3Client($config))->getObjectUrl($config['bucket'], $object_path);
}
}
Then, I added this class to the AppServiceProvider under the register method so it has access to the current app instance:
$this->app->singleton('filesystemPublicUrl', function () {
return new FilesystemPublicUrlManager($this->app);
});
Finally, for easy static access, I created a Facade class:
use Illuminate\Support\Facades\Facade;
class StorageUrl extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'filesystemPublicUrl';
}
}
Now, I can easily get the public url for my public objects on my local and s3 filesystems (note that I didn't add anything for ftp or rackspace in the FilesystemPublicUrlManager):
$s3Url = StorageUrl::publicUrl('s3') //using the s3 driver
$localUrl = StorageUrl::publicUrl('local') //using the local driver
$defaultUrl = StorageUrl::publicUrl() //default driver
$objectUrl = StorageUrl::publicUrl('s3', '/path/to/object');
Another form of Storage::cloud():
/** #var FilesystemAdapter $disk */
$s3 = Storage::disk('s3');
return $s3->url($path);
Using presigned request S3:
public function getFileUrl($key) {
$s3 = Storage::disk('s3');
$client = $s3->getDriver()->getAdapter()->getClient();
$bucket = env('AWS_BUCKET');
$command = $client->getCommand('GetObject', [
'Bucket' => $bucket,
'Key' => $key
]);
$request = $client->createPresignedRequest($command, '+20 minutes');
return (string) $request->getUri();
}
For private cloud use this
Storage::disk('s3')->temporaryUrl($path);
Related
I am trying to build my first no-framework PHP application and I am following this tutorial.
I am relatively new to some concepts described in the tutorial. Despite this, I decided to use, as Dependency Injector, PHP-DI instead of the suggested one (rdlowrey/auryn).
I have created everything according to the tutorial except for the file Bootstrap.php (and the file Dependencies.php:
<?php declare(strict_types = 1);
require(__DIR__ . '/../vendor/autoload.php');
...
$container = include('Dependencies.php');
$request = $container->make('Http\HttpRequest');
$response = $container->make('Http\HttpResponse');
...
switch ($routeInfo[0]) {
...
case \FastRoute\Dispatcher::FOUND:
$className = $routeInfo[1][0];
$method = $routeInfo[1][1];
$vars = $routeInfo[2];
$class = $container->make($className);
$class->$method($vars); // (**)
break;
}
echo $response->getContent(); // (*)
$class can be only an instance of a Homepage class which has only one method (show()), called in (**):
class Homepage
{
private $request;
private $response;
private $renderer;
public function __construct(
Request $request,
Response $response,
Renderer $renderer
) {
$this->request = $request;
$this->response = $response;
$this->renderer = $renderer;
}
public function show() {
$data = [
'name' => $this->request->getParameter('name', 'stranger'),
];
$html = $this->renderer->render('Homepage', $data);
$this->response->setContent($html); // (***)
}
}
With all that said, the application returns a 200 HTTP response with an empty body [here (*)]
but if I try to print the content of the HTTP response after (***) I get the correct response.
This could mean that there are two different instances of an HttpResponse class. (Is that right?)
By using rdlowrey/auryn, the author of the tutorial, used the method share() to share the same HttpReponse instance among classes, as shown in the "original" Dependencies.php file:
<?php declare(strict_types = 1);
use \Auryn\Injector;
...
$injector = new Injector;
$injector->alias('Http\Response', 'Http\HttpResponse');
$injector->share('Http\HttpResponse');
...
return $injector;
Is there a way to get the same behavior using PHP-DI (with PHP definitions)?
Here's my version of Dependencies.php:
<?php declare(strict_types = 1);
$definitions = [
'Http\Request' => DI\create('Http\HttpRequest')->constructor(
$_GET, $_POST, $_COOKIE, $_FILES, $_SERVER),
'Http\HttpRequest' => function () {
$r = new Http\HttpRequest($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
return $r;
},
'Http\Response' => DI\create('Http\HttpResponse'),
'Twig\Environment' => function () {
$loader = new Twig\Loader\FilesystemLoader(
dirname(__DIR__) . '/templates');
$twig = new Twig\Environment($loader);
return $twig;
},
'Example\Template\TwigRenderer' => function (Twig\Environment $renderer) {
return new Example\Template\TwigRenderer($renderer);
},
'Example\Template\Renderer' => DI\create(
'Example\Template\TwigRenderer')->constructor(
DI\get('Twig\Environment')),
];
$containerBuilder = new DI\ContainerBuilder;
$containerBuilder->addDefinitions($definitions);
$container = $containerBuilder->build();
return $container;
In Bootstrap.php, getting (get()) HttpRequest/HttpResponse instances, instead of making (make()) them, solved the problem.
...
$container = include('Dependencies.php');
$request = $container->get('Http\HttpRequest');
$response = $container->get('Http\HttpResponse');
...
As clearly stated in the documentation:
The make() method works like get() except it will resolve the entry
every time it is called. [..] if the entry is an object, an new instance will be created every time [..]
We are using Doctrine 2 in our app, but due to our infrastructure, we do not have a static configuration for database connections. Instead, we have a collection of singletons in a service provider for each database we need to connect to, and we select a random database host for then when we connect.
Unfortunately, we are seeing some performance degradation in Doctrine's getRepository() function. I believe the issue is that Doctrine needs to generate its proxy classes at runtime (even in production) because we cannot figure out how to configure the CLI tools in order to create them at build time.
We are using the Laravel framework for the application.
Here's an example of our Laravel service provider which makes the repositories available for dependency injection.
<?php
use App\Database\Doctrine\Manager as DoctrineManager;
use Proprietary\ConnectionFactory;
use App\Database\Entities;
use App\Database\Repositories;
use App\Database\Constants\EntityConstants;
class DoctrineServiceProvider extends ServiceProvider
{
// Create a singleton for the Doctrine Manager. This class will handle entity manager generation.
$this->app->singleton(DoctrineManager::class, function ($app)
{
return new DoctrineManager(
$app->make(ConnectionFactory::class),
[
EntityConstants::ENTITY_CLASS_DATABASE1 => [app_path('Database/Entities/Database1')],
EntityConstants::ENTITY_CLASS_DATABASE2 => [app_path('Database/Entities/Database2')],
],
config('app.debug'),
$this->app->make(LoggerInterface::class)
);
});
// Register the first repository
$this->app->singleton(Repositories\Database1\RepositoryA1::class, function ($app)
{
return $app[DoctrineManager::class]
->getEntityManager(EntityConstants::ENTITY_CLASS_DATABASE1)
->getRepository(Entities\Database1\RepositoryA1::class);
});
// Register the second repository
$this->app->singleton(Repositories\Database1\RepositoryA2::class, function ($app)
{
return $app[DoctrineManager::class]
->getEntityManager(EntityConstants::ENTITY_CLASS_DATABASE1)
->getRepository(Entities\Database1\RepositoryA2::class);
});
// Register a repository for the second database
$this->app->singleton(Repositories\Database2\RepositoryB1::class, function ($app)
{
return $app[DoctrineManager::class]
->getEntityManager(EntityConstants::ENTITY_CLASS_DATABASE2)
->getRepository(Entities\Database2\RepositoryB1::class);
});
}
Here's the class that generates EntityManagers for Doctrine:
<?php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Connections\MasterSlaveConnection;
use Proprietary\ConnectionFactory;
class Manager
{
private $c_factory;
private $paths;
private $connections = [];
private $entity_managers = [];
public function __construct(
ConnectionFactory $cf,
array $paths
)
{
$this->c_factory = $cf;
$this->paths = $paths;
}
public function getConnection($name, $partition = false, $region = false)
{
// Get a list of servers for this database and format them for use with Doctrine
$servers = self::formatServers($name, $this->c_factory->getServers($name, true, $partition, $region));
// Generate a connection for the entity manager using the servers we have.
$connection = DriverManager::getConnection(
array_merge([
'wrapperClass' => MasterSlaveConnection::class,
'driver' => 'pdo_mysql',
], $servers)
);
return $connection;
}
public function getEntityManager($name, $partition = false, $region = false)
{
// Should these things be cached somehow at build time?
$config = Setup::createAnnotationMetadataConfiguration($this->paths[$name], false);
$config->setAutoGenerateProxyClasses(true);
// Set up the connection
$connection = $this->getConnection($name, $partition, $region);
$entity_manager = EntityManager::create($connection, $config);
return $entity_manager;
}
// Converts servers from a format provided by our proprietary code to a format Doctrine can use.
private static function formatServers($db_name, array $servers)
{
$doctrine_servers = [
'slaves' => [],
];
foreach ($servers as $server)
{
// Format for Doctrine
$server = [
'user' => $server['username'],
'password' => $server['password'],
'host' => $server['hostname'],
'dbname' => $db_name,
'charset' => 'utf8',
];
// Masters can also be used as slaves.
$doctrine_servers['slaves'][] = $server;
// Servers are ordered by which is closest, and Doctrine only allows a
// single master, so if we already set one, don't overwrite it.
if ($server['is_master'] && !isset($doctrine_servers['master']))
{
$doctrine_servers['master'] = $server;
}
}
return $doctrine_servers;
}
}
Our service classes use dependency injection to get the repository singletons defined in the service provider. When we use the singletons for the first time, Doctrine will use the entity class defined in the service provider and get the connection associated with the repository.
Is there any way we can enable the CLI tools with this configuration? Are there any other ways that we can optimize this for use in production?
Thanks.
I was able to solve the problem thanks to a suggestion from the Doctrine IRC channel. Since the CLI tools can only handle a single database, I created a doctrine-cli directory containing a base-config.php file and a subdirectory for each of the databases we use.
Here's an example file structure:
doctrine-cli/
|- database1/
| |- cli-config.php
|- database2/
| |- cli-config.php
|- base-config.php
The base-config.php file looks like this:
<?php
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use App\Database\Doctrine\Manager as DoctrineManager;
use Proprietary\ConnectionFactory;
require __DIR__ . '/../bootstrap/autoload.php';
class DoctrineCLIBaseConfig
{
private $helper_set;
public function __construct($entity_constant, $entity_namespace)
{
$app = require_once __DIR__ . '/../bootstrap/app.php';
// Proprietary factory for getting our databse details
$connection_factory = new ConnectionFactory(...);
// Our class that parses the results from above and handles our Doctrine connection.
$manager = new DoctrineManager(
$connection_factory,
[$entity_constant => [app_path('Database/Entities/' . $entity_namespace)]],
false,
null,
null
);
$em = $manager->getEntityManager($entity_constant);
$this->helper_set = new HelperSet([
'db' => new ConnectionHelper($em->getConnection()),
'em' => new EntityManagerHelper($em),
]);
}
public function getHelperSet()
{
return $this->helper_set;
}
}
Here's an example cli-config.php from the database directory:
<?php
use App\Database\Constants\EntityConstants;
require __DIR__ . "/../base-config.php";
$config = new DoctrineCLIBaseConfig(
EntityConstants::ENTITY_CLASS_DATABASE1,
"database1"
);
return $config->getHelperSet();
Now, I'm able to cycle through each of the directories and run commands like so:
php ../../vendor/bin/doctrine orm:generate-proxies
For our build process, I wrote a simple shell script that cycles through the directories and runs the orm:generate-proxies command.
When I try to get the current directory with :
$this->container->getParameter('kernel.root_dir').'/../web/
I've got this error : Fatal error: Using $this when not in object context in C:\XXX on line 124
Code :
class AdminController {
/**
* Add event controller.
*
* #param Request $request Incoming request
* #param Application $app Silex application
*/
public function addEventAction(Request $request, Application $app) {
$event = new Event();
$types= $app['dao.type']->findAllSelectList();
$eventForm = $app['form.factory']->create(new EventType($types), $event);
$eventForm->handleRequest($request);
if ($eventForm->isSubmitted() && $eventForm->isValid()) {
var_dump($event->getCoverImageLink());
$file = $event->getCoverImageLink();
$fileName = md5(uniqid()).'.'.$file->guessExtension();
var_dump($fileName);
//$path = $this->container->getParameter('kernel.root_dir').'/../web';//$this->get('kernel')->getRootDir() . '/../web';
var_dump($this);
$app['dao.event']->save($event);
$app['session']->getFlashBag()->add('success', 'The event was successfully created.');
}
return $app['twig']->render('event_form.html.twig', array(
'title' => 'New event',
'eventForm' => $eventForm->createView()));
}
How to fix this error please? What is the correct function to use?
It appears that you are using Silex, not Symfony 2. Being a very minimalistic framework, silex doesn't give you all the configuration and dependency injection goodies that Symfony does.
The easiest approach to be able to retrieve the application root, would be to define it yourself in bootstrap.php. Simply add something like this at the top:
define('APP_ROOT', __DIR__ . '/../');
Now you can just use the constant in your controller:
public function addEventAction(Request $request, Application $app) {
...
$path = APP_ROOT . '/../web';
...
}
I'm trying to run this functional test on my laravel controller. I would like to test image processing, but to do so I want to fake image uploading. How do I do this? I found a few examples online but none seem to work for me. Here's what I have:
public function testResizeMethod()
{
$this->prepareCleanDB();
$this->_createAccessableCompany();
$local_file = __DIR__ . '/test-files/large-avatar.jpg';
$uploadedFile = new Symfony\Component\HttpFoundation\File\UploadedFile(
$local_file,
'large-avatar.jpg',
'image/jpeg',
null,
null,
true
);
$values = array(
'company_id' => $this->company->id
);
$response = $this->action(
'POST',
'FileStorageController#store',
$values,
['file' => $uploadedFile]
);
$readable_response = $this->getReadableResponseObject($response);
}
But the controller doesn't get passed this check:
elseif (!Input::hasFile('file'))
{
return Response::error('No file uploaded');
}
So somehow the file isn't passed correctly. How do I go about this?
For anyone else stumbling upon this question, you can nowadays do this:
$response = $this->postJson('/product-import', [
'file' => new \Illuminate\Http\UploadedFile(resource_path('test-files/large-avatar.jpg'), 'large-avatar.jpg', null, null, null, true),
]);
UPDATE
In Laravel 6 the constructor of \Illuminate\Http\UploadedFile Class has 5 parameters instead of 6. This is the new constructor:
/**
* #param string $path The full temporary path to the file
* #param string $originalName The original file name of the uploaded file
* #param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream
* #param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
* #param bool $test Whether the test mode is active
* Local files are used in test mode hence the code should not enforce HTTP uploads
*
* #throws FileException If file_uploads is disabled
* #throws FileNotFoundException If the file does not exist
*/
public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, $test = false)
{
// ...
}
So the above solution becomes simply:
$response = $this->postJson('/product-import', [
'file' => new \Illuminate\Http\UploadedFile(resource_path('test-files/large-avatar.jpg'), 'large-avatar.jpg', null, null, true),
]);
It works for me.
Docs for CrawlerTrait.html#method_action reads:
Parameters
string $method
string $action
array $wildcards
array $parameters
array $cookies
array $files
array $server
string $content
So I assume the correct call should be
$response = $this->action(
'POST',
'FileStorageController#store',
[],
$values,
[],
['file' => $uploadedFile]
);
unless it requires non-empty wildcards and cookies.
The best and Easiest way : First Import the Necessary things
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
Then make a fake file to upload.
Storage::fake('local');
$file = UploadedFile::fake()->create('file.pdf');
Then make a JSON Data to pass the file. Example
$parameters =[
'institute'=>'Allen Peter Institute',
'total_marks'=>'100',
'aggregate_marks'=>'78',
'percentage'=>'78',
'year'=>'2002',
'qualification_document'=>$file,
];
Then send the Data to your API.
$user = User::where('email','candidate#fakemail.com')->first();
$response = $this->json('post', 'api/user', $parameters, $this->headers($user));
$response->assertStatus(200);
I hope it will work.
With phpunit you can attach a file to a form by using attach() method.
Example from lumen docs:
public function testPhotoCanBeUploaded()
{
$this->visit('/upload')
->name('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}
Here is a full example how to test with custom files. I needed this for parsing CSV files with known format so my files had to had exact formatting and contents. If you need just images or random sized files use $file->fake->image() or create() methods. Those come bundled with Laravel.
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
class PanelistImportTest extends TestCase
{
/** #test */
public function user_should_be_able_to_upload_csv_file()
{
// If your route requires authenticated user
$user = Factory('App\User')->create();
$this->actingAs($user);
// Fake any disk here
Storage::fake('local');
$filePath='/tmp/randomstring.csv';
// Create file
file_put_contents($filePath, "HeaderA,HeaderB,HeaderC\n");
$this->postJson('/upload', [
'file' => new UploadedFile($filePath,'test.csv', null, null, null, true),
])->assertStatus(200);
Storage::disk('local')->assertExists('test.csv');
}
}
Here is the controller to go with it:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class UploadController extends Controller
{
public function save(Request $request)
{
$file = $request->file('file');
Storage::disk('local')->putFileAs('', $file, $file->getClientOriginalName());
return response([
'message' => 'uploaded'
], 200);
}
}
Add similar setUp() method into your testcase:
protected function setUp()
{
parent::setUp();
$_FILES = array(
'image' => array(
'name' => 'test.jpg',
'tmp_name' => __DIR__ . '/_files/phpunit-test.jpg',
'type' => 'image/jpeg',
'size' => 499,
'error' => 0
)
);
}
This will spoof your $_FILES global and let Laravel think that there is something uploaded.
Using Laravel 4 to create a "Read-it-Later" application just for testing purposes.
I'm able to successfully store a URL and Description into my application using the following curl command:
curl -d 'url=http://testsite.com&description=For Testing' readitlater.local/api/v1/url
I'm interested in using GET to accomplish the same thing but by passing my variables in a URL (e.g. readitlater.local/api/v1/url?url=testsite.com?description=For%20Testing)
Here is my UrlController segment:
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$url = new Url;
$url->url = Request::get('url');
$url->description = Request::get('description');
$url->save();
return Response::json(array(
'error' => false,
'urls' => $urls->toArray()),
200
);
}
Here is my Url model:
<?php
class Url extends Eloquent {
protected $table = 'urls';
}
I read through the Laravel docs on input types but I'm not certain how to apply that to my current controller: http://laravel.com/docs/requests#basic-input
Any tips?
You didn't apply what you correctly linked to...Use Input::get() to fetch anything from GET or POST, and the Request class to get info on the current request. Are you looking for something like this?
public function store()
{
$url = new Url; // I guess this is your Model
$url->url = Request::url();
$url->description = Input::get('description');
$url->save();
return Response::json(array(
'error' => false,
'urls' => Url::find($url->id)->toArray(),
/* Not sure about this. You want info for the current url?
(you already have them...no need to query the DB) or you want ALL the urls?
In this case, use Url::all()->toArray()
*/
200
);
}