Mock file in Storage to download in Laravel - php

Is there a way to mock a file using Laravels Storage::fake() method?
I have used https://laravel.com/docs/5.7/mocking#storage-fake as a base for my tests, which works fine for uploads. But my download tests are ugly as I have to run my upload route first every time with a mock upload UploadedFile::fake()->image('avatar.jpg'). Is there a way to skip that part and mock the file to exist directly in the fake storage system?
public function testAvatarUpload()
{
Storage::fake('avatars');
// This is the call I would like to change into a mocked existing uploaded file
$uploadResponse = $this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')
]);
// Download the first avatar
$response = $this->get('/download/avatar/1');
$response->assertStatus(200);
}

I might be late here. but wanted to help others visiting this question to give an idea of implementing it.
Here is a sample with some assertions.
<?php
namespace Tests\Feature\Upload;
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class SampleDownloadTest extends TestCase
{
/**
* #test
*/
public function uploaded_file_downloads_correctly()
{
//keep a sample file inside projectroot/resources/files folder
//create a file from it
$exampleFile = new File(resource_path('files/test-file.png'))
//copy that file to projectroot/storage/app/uploads folder
Storage::putFileAs('/uploads', $exampleFile, 'test-file.png');
//make request to file download url to get file
$response = $this->get("/files/file/download/url");
//check whethe response was ok
$response->assertOk();
$response->assertHeader('Content-Type', 'image/png')
//check whether file exists in path
Storage::assertExists('/uploads/test-file.png');
//do some more assertions.....
//after test delete the file from storage path
Storage::delete('uploads/test-file.png');
//check whether file was deleted
Storage::assertMissing('/uploads/test-file.png');
}
}

Yes, you can use fake file storage feature of Laravel (mocking):
use Illuminate\Http\UploadedFile;
$file = UploadedFile::fake()->create('filename.ext', $sizeInKb)->store('filename.ext');
If you want to create a text/csv file with a specific content you can use this:
use Illuminate\Http\UploadedFile;
$header = 'a,b,c';
$row1 = 'x,y,z';
$row2 = 's,f,t';
$row3 = 'r,i,o';
$content = implode("\n", [$header, $row1, $row2, $row3]);
$file = UploadedFile::fake()->createWithContent('filename.ext', $content)->store('filename.ext');
You can find this methods definitions in Illuminate\Http\Testing\FileFactory

You could just create a new file directly or copy a specific test file for example:
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;
// for simple text files or if the content doesn't matter
Storage::disk('avatars')->put('avatar.jpg', 'some non-jpg content');
// if you need a specific file for your test
$file = new File(base_path('tests/resources/avatar.jpg'));
Storage::disk('avatars')->putFileAs('/', $file, 'avatar.jpg');
The latter function will take the $file and copy it under the given name avatar.jpg to the given directory / on the disk avatars. You can read more about it in the official documentation.

What you could use to solve that problem is fixtures. Laravel's testing framework is essentially PHPUnit, so I see no reason why it would not work.
define your test like so:
use Tests\TestCase;
class ExampleTest extends TestCase {
protected function setUp() {
parent::setUp();
Storage::fake('avatars');
$uploadResponse = $this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')
]);
}
protected function tearDown() {
parent::tearDown();
}
public function testAvatarUpload() {
// Download the first avatar
$response = $this->get('/download/avatar/1');
$response->assertStatus(200);
}
}
setUp and tearDown get called, respectively, before and after each test in the class. So, before each test method, setUp will wipe the avatars fake disk and run the request. As there is nothing to do after a test (since Storage::fake() replaces the disk if it already exists), the method is empty; I left it here purely to make the example complete.
There's some pretty good documentation on here about this feature of PHPunit.
Regarding putting the file on there, once you have your setUp working correctly, there's nothing stopping you from throwing the file on it.

Related

Retrieving the absolute path of a file saved with Laravel? [duplicate]

I've been experimenting using the new Flysystem integration with Laravel 5. I am storing 'localised' paths to the DB, and getting the Storage facade to complete the path. For example I store screenshots/1.jpg and using
Storage::disk('local')->get('screenshots/1.jpg')
or
Storage::disk('s3')->get('screenshots/1.jpg')
I can retrieve the same file on different disks.
get retrieves the file contents, but I am hoping to use it in my views like this:
<img src="{{ Storage::path('screenshots/1.jpg') }}" />
but path, or anything able to retrieve the full path is not available (as far as I can see). So how can I return the full path? Or, I'm wondering if this is by design? If so, why am I not supposed to be able to get the full path? Or, am I going about this completely the wrong way?
The Path to your Storage disk would be :
$storagePath = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix()
I don't know any shorter solutions to that...
You could share the $storagePath to your Views and then just call
$storagePath."/myImg.jpg";
This method exists since Laravel 5.4, you can get it by:
$path = Storage::disk('public')->path($filename);
Edit: Solution for L5.2+
There's a better and more straightforward solution.
Use Storage::url($filename) to get the full path/URL of a given file. Note that you need to set S3 as your storage filesystem in config/filesystems.php: 'default' => 's3'
Of course, you can also do Storage::disk('s3')->url($filename) in the same way.
As you can see in config/filesystems.php there's also a parameter 'cloud' => 's3' defined, that refers to the Cloud filesystem. In case you want to mantain the storage folder in the local server but retrieve/store some files in the cloud use Storage::cloud(), which also has the same filesystem methods, i.e. Storage::cloud()->url($filename).
The Laravel documentation doesn't mention this method, but if you want to know more about it you can check its source code here.
This is how I got it to work - switching between s3 and local directory paths with an environment variable, passing the path to all views.
In .env:
APP_FILESYSTEM=local or s3
S3_BUCKET=BucketID
In config/filesystems.php:
'default' => env('APP_FILESYSTEM'),
In app/Providers/AppServiceProvider:
public function boot()
{
view()->share('dynamic_storage', $this->storagePath());
}
protected function storagePath()
{
if (Storage::getDefaultDriver() == 's3') {
return Storage::getDriver()
->getAdapter()
->getClient()
->getObjectUrl(env('S3_BUCKET'), '');
}
return URL::to('/');
}
If you just want to display storage (disk) path use this:
Storage::disk('local')->url('screenshots/1.jpg'); // storage/screenshots/1.jpg
Storage::disk('local')->url(''): // storage
Also, if you are interested, I created a package (https://github.com/fsasvari/laravel-uploadify) just for Laravel so you can use all those fields on Eloquent model fields:
$car = Car::first();
$car->upload_cover_image->url();
$car->upload_cover_image->name();
$car->upload_cover_image->basename();
$car->upload_cover_image->extension();
$car->upload_cover_image->filesize();
If you need absolute URL of the file, use below code:
$file_path = \Storage::url($filename);
$url = asset($file_path);
// Output: http://example.com/storage/filename.jpg
First get file url/link then path, as below:
$url = Storage::disk('public')->url($filename);
$path = public_path($url);
Well, weeks ago I made a very similiar question (Get CDN url from uploaded file via Storage): I wanted the CDN url to show the image in my view (as you are requiring ).
However, after review the package API I confirmed that there is no way do this task. So, my solution was avoid using flysystem. In my case, I needed to play with RackSpace. So, finally decide to create my use package and make my own storage package using The PHP SDK for OpenStack.
By this way, you have full access for functions that you need like getPublicUrl() in order to get the public URL from a cdn container:
/** #var DataObject $file */
$file = \OpenCloud::container('cdn')->getObject('screenshots/1.jpg');
// $url: https://d224d291-8316ade.ssl.cf1.rackcdn.com/screenshots/1.jpg
$url = (string) $file->getPublicUrl(UrlType::SSL);
In conclusion, if need to take storage service to another level, then flysystem is not enough. For local purposes, you can try #nXu's solution
this work for me in 2020 at laravel 7
$image_resize = Image::make($image->getRealPath());
$image_resize->resize(800,600);
$image_resize->save(Storage::disk('episodes')->path('') . $imgname);
so you can use it like this
echo Storage::disk('public')->path('');
Store method:
public function upload($img){
$filename = Carbon::now() . '-' . $img->getClientOriginalName();
return Storage::put($filename, File::get($img)) ? $filename : '';
}
Route:
Route::get('image/{filename}', [
'as' => 'product.image',
'uses' => 'ProductController#getImage',
]);
Controller:
public function getImage($filename)
{
$file = Storage::get($filename);
return new Response($file, 200);
}
View:
<img src="{{ route('product.image', ['filename' => $yourImageName]) }}" alt="your image"/>
Another solution I found is this:
Storage::disk('documents')->getDriver()->getConfig()->get('url')
Will return the url with the base path of the documents Storage
Take a look at this: How to use storage_path() to view an image in laravel 4 . The same applies to Laravel 5:
Storage is for the file system, and the most part of it is not accessible to the web server. The recommended solution is to store the images somewhere in the public folder (which is the document root), in the public/screenshots/ for example.
Then when you want to display them, use asset('screenshots/1.jpg').
In my case, i made separate method for local files, in this file:
src/Illuminate/Filesystem/FilesystemAdapter.php
/**
* Get the local path for the given filename.
* #param $path
* #return string
*/
public function localPath($path)
{
$adapter = $this->driver->getAdapter();
if ($adapter instanceof LocalAdapter) {
return $adapter->getPathPrefix().$path;
} else {
throw new RuntimeException('This driver does not support retrieving local path');
}
}
then, i create pull request to framework, but it still not merged into main core yet:
https://github.com/laravel/framework/pull/13605
May be someone merge this one))
$url = $filename->getMedia('media_name');

How to get file URL using Storage facade in laravel 5?

I've been experimenting using the new Flysystem integration with Laravel 5. I am storing 'localised' paths to the DB, and getting the Storage facade to complete the path. For example I store screenshots/1.jpg and using
Storage::disk('local')->get('screenshots/1.jpg')
or
Storage::disk('s3')->get('screenshots/1.jpg')
I can retrieve the same file on different disks.
get retrieves the file contents, but I am hoping to use it in my views like this:
<img src="{{ Storage::path('screenshots/1.jpg') }}" />
but path, or anything able to retrieve the full path is not available (as far as I can see). So how can I return the full path? Or, I'm wondering if this is by design? If so, why am I not supposed to be able to get the full path? Or, am I going about this completely the wrong way?
The Path to your Storage disk would be :
$storagePath = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix()
I don't know any shorter solutions to that...
You could share the $storagePath to your Views and then just call
$storagePath."/myImg.jpg";
This method exists since Laravel 5.4, you can get it by:
$path = Storage::disk('public')->path($filename);
Edit: Solution for L5.2+
There's a better and more straightforward solution.
Use Storage::url($filename) to get the full path/URL of a given file. Note that you need to set S3 as your storage filesystem in config/filesystems.php: 'default' => 's3'
Of course, you can also do Storage::disk('s3')->url($filename) in the same way.
As you can see in config/filesystems.php there's also a parameter 'cloud' => 's3' defined, that refers to the Cloud filesystem. In case you want to mantain the storage folder in the local server but retrieve/store some files in the cloud use Storage::cloud(), which also has the same filesystem methods, i.e. Storage::cloud()->url($filename).
The Laravel documentation doesn't mention this method, but if you want to know more about it you can check its source code here.
This is how I got it to work - switching between s3 and local directory paths with an environment variable, passing the path to all views.
In .env:
APP_FILESYSTEM=local or s3
S3_BUCKET=BucketID
In config/filesystems.php:
'default' => env('APP_FILESYSTEM'),
In app/Providers/AppServiceProvider:
public function boot()
{
view()->share('dynamic_storage', $this->storagePath());
}
protected function storagePath()
{
if (Storage::getDefaultDriver() == 's3') {
return Storage::getDriver()
->getAdapter()
->getClient()
->getObjectUrl(env('S3_BUCKET'), '');
}
return URL::to('/');
}
If you just want to display storage (disk) path use this:
Storage::disk('local')->url('screenshots/1.jpg'); // storage/screenshots/1.jpg
Storage::disk('local')->url(''): // storage
Also, if you are interested, I created a package (https://github.com/fsasvari/laravel-uploadify) just for Laravel so you can use all those fields on Eloquent model fields:
$car = Car::first();
$car->upload_cover_image->url();
$car->upload_cover_image->name();
$car->upload_cover_image->basename();
$car->upload_cover_image->extension();
$car->upload_cover_image->filesize();
If you need absolute URL of the file, use below code:
$file_path = \Storage::url($filename);
$url = asset($file_path);
// Output: http://example.com/storage/filename.jpg
First get file url/link then path, as below:
$url = Storage::disk('public')->url($filename);
$path = public_path($url);
Well, weeks ago I made a very similiar question (Get CDN url from uploaded file via Storage): I wanted the CDN url to show the image in my view (as you are requiring ).
However, after review the package API I confirmed that there is no way do this task. So, my solution was avoid using flysystem. In my case, I needed to play with RackSpace. So, finally decide to create my use package and make my own storage package using The PHP SDK for OpenStack.
By this way, you have full access for functions that you need like getPublicUrl() in order to get the public URL from a cdn container:
/** #var DataObject $file */
$file = \OpenCloud::container('cdn')->getObject('screenshots/1.jpg');
// $url: https://d224d291-8316ade.ssl.cf1.rackcdn.com/screenshots/1.jpg
$url = (string) $file->getPublicUrl(UrlType::SSL);
In conclusion, if need to take storage service to another level, then flysystem is not enough. For local purposes, you can try #nXu's solution
this work for me in 2020 at laravel 7
$image_resize = Image::make($image->getRealPath());
$image_resize->resize(800,600);
$image_resize->save(Storage::disk('episodes')->path('') . $imgname);
so you can use it like this
echo Storage::disk('public')->path('');
Store method:
public function upload($img){
$filename = Carbon::now() . '-' . $img->getClientOriginalName();
return Storage::put($filename, File::get($img)) ? $filename : '';
}
Route:
Route::get('image/{filename}', [
'as' => 'product.image',
'uses' => 'ProductController#getImage',
]);
Controller:
public function getImage($filename)
{
$file = Storage::get($filename);
return new Response($file, 200);
}
View:
<img src="{{ route('product.image', ['filename' => $yourImageName]) }}" alt="your image"/>
Another solution I found is this:
Storage::disk('documents')->getDriver()->getConfig()->get('url')
Will return the url with the base path of the documents Storage
Take a look at this: How to use storage_path() to view an image in laravel 4 . The same applies to Laravel 5:
Storage is for the file system, and the most part of it is not accessible to the web server. The recommended solution is to store the images somewhere in the public folder (which is the document root), in the public/screenshots/ for example.
Then when you want to display them, use asset('screenshots/1.jpg').
In my case, i made separate method for local files, in this file:
src/Illuminate/Filesystem/FilesystemAdapter.php
/**
* Get the local path for the given filename.
* #param $path
* #return string
*/
public function localPath($path)
{
$adapter = $this->driver->getAdapter();
if ($adapter instanceof LocalAdapter) {
return $adapter->getPathPrefix().$path;
} else {
throw new RuntimeException('This driver does not support retrieving local path');
}
}
then, i create pull request to framework, but it still not merged into main core yet:
https://github.com/laravel/framework/pull/13605
May be someone merge this one))
$url = $filename->getMedia('media_name');

Handling plupload file uploads in Symfony 2

I'm trying to write a controller to accept file uploads from the Plupload plugin. As an added bit of fun, the uploads are coming from a different URL so I have to set the Access-Control-Allow-Origin header myself. So far I've done that like so:
/**
* #Route("/frontEnd/file/upload.{_format}")
*/
public function upload(Request $request) {
$response = new Response();
$response->setContent(json_encode(array('hello' => 'world')));
$response->setStatusCode(200);
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->send();
}
which seems to work. When I submit the uploads using plupload I see the XHR requests hit Symfony and the JSON is returned. However, I have no idea how to handle the actual file and move it into a directory.
I did a var_dump() on $_POST and it only returned the following:
array(1) {
["name"]=>
string(21) "wallpaper-2873928.jpg"
}
The upload is definitely being sent as I can see the file's bytes being part of the request payload with developer tools. Do I need to use Symfony's own components to handle the upload? If so, how? The Symfony documentation only seems to cover uploading from a file upload form.
First of all, try to use the Symfony2 way of accessing request parameters. You can get more information in the book.
When uploading a file, Symfony2 automatically creates an instance of UploadedFile for you and puts it in a FileBag in the request object.
You can access the files in your controller like this:
$files = $request->files;
Like said previously, these are temporary files. To upload them in a user defined directory, use the move method on the object.
$directory = //...
foreach ($files as $uploadedFile) {
$name = //...
$file = $uploadedFile->move($directory, $name);
}
The variable $files now contains an instance of File.
On the other hand, you can also use a bundle that supports the Plupload uploader. I'd recommend the OneupUploaderBundle. (Note: I'm the main developer of this bundle, I guess this needs to be added).

autoloader when executing php from linux bash

im currently working on some sort of upload with automatic video conversion. At the moment i am executing a php script via php shell command after the upload is finished so the user doesn't have to wait until the conversion is completed. Like so:
protected function _runConversionScript() {
if (!exec("php -f '" . $this->_conversionScript . "' > /dev/null &"))
return true;
return false;
}
Now in my conversion script file i am using functions from another class "UploadFunctions" to update the status in the database (like started, converted, finished...). The problem there is though that this UploadFunctions class inherits from another class "Controller" where for example the database connection gets established. Currently i am using spl_autoloader to search specific directories for the files needed (for example controller.php), but because the conversion script is out of context with the whole autoloader stuff it doesn't recognize the Controller class and throws an fatal php error.
Here is some code from the conversion script:
require_once('uploadfunctions.php');
$upload_func = new UploadFunctions();
// we want to make sure we only process videos that haven't already
// been or are being processed
$where = array(
'status' => 'queued'
);
$videos = $upload_func->getVideos($where);
foreach ($videos as $video) {
// update database to show that these videos are being processed
$update = array(
'id' => $video['id'],
'status' => 'started'
);
// execute update
$upload_func->updateVideo($update);
.........
Am i doing this completly wrong or is there a better way to accomplish this? If you need more code or information please let me know!
Thanks a lot
Here is my spl_autoload code:
<?php
spl_autoload_register('autoloader');
function autoloader($class_name) {
$class_name = strtolower($class_name);
$pos = strpos($class_name ,'twig');
if($pos !== false){
return false;
}
$possibilities = array(
'..'.DIRECTORY_SEPARATOR.'globals'.DIRECTORY_SEPARATOR.$class_name.'.php',
'controller'.DIRECTORY_SEPARATOR.$class_name.'.php',
'..'.DIRECTORY_SEPARATOR.'libs'.DIRECTORY_SEPARATOR.$class_name.'.php',
'local'.DIRECTORY_SEPARATOR.$class_name.'.php'
);
foreach ($possibilities as $file) {
if(class_exists($class_name) != true) {
if (file_exists($file)) {
include_once($file);
}
}
}
}
?>
I have my project divided into subfolders wich represent the functionality, for example upload, myaccount and gallery.. in every subfolder there are also 2 other folders: controller and local. Controller is the class controlling this part (upload for example) and local is the folder where i am putting the local classes wich are needed. The controller class gets called from the index.php wich is located in the sub-project folder. "libs" and "global" are just projectwide classes, like database, user and so on.
This is an example of my folder structure:
www/index.php // main site
www/upload/index.php // calls the controller for upload and initializes the spl_autoload
www/upload/controller/indexcontroller.php // functionality for the upload
www/upload/local/processVideo.php // this is the conversion script.
I am fairly new to spl_autoload function. In my opinion the spl_autoload is not getting called if my script is calling: "php -f processVideo.php", isn't it?
PHP relative paths are calculated from the path where PHP binary is called.
I suggest you to use __DIR__ constant to avoid that behavior
http://php.net/manual/en/language.constants.predefined.php
I was actually able to resolve the issue. I had to include the spl_autoload_register function inside the conversion script so that it was able to locate the files. This was an issue because the conversion script is not build into my framework an so it isn't able to load the classes from the framework autoloader.

phpunit testcase for file upload

Can anybody help me. How to write file upload phpunit testcase?
I done it for insert, unique data insertion, delete etc functionality.
Following are my code but its not working properly
class FileuploadTest extends PHPUnit_Framework_TestCase
{
public $testFile = array(
'name'=>'2012-04-20 21.13.42.jpg',
'tmp_name'=>'C:\wamp\tmp\php8D20.tmp',
'type'=>'image/jpeg',
'size'=>1472190,
'error'=>0
);
public function testFileupload()
{
$testUpload = new Fileupload;
$testUpload->image = new CUploadedFile($this->testFile['name'],$this->testFile['tmp_name'],$this->testFile['type'],$this->testFile['size'],$this->testFile['error']);
$this->assertFalse($testUpload->validate());
$errors= $testUpload->errors;
$this->assertEmpty($errors);
}
}
According to your comments, that's what testing is, the $testUpload->validate() is returning true, and you are trying to assert if it is false, obviously the test will fail.
If $this->assertFalse($testUpload->validate()); is failing, it means that $testUpload is correctly initialized, and hence validation returns true.
To move on to the next assertion in your test you need to use
$this->assertTrue($testUpload->validate());
You need to read more about unit testing. There are lots of articles on the web, that a simple search will return.

Categories