What I want to archive:
I want to write a feature test using phpunit in Laravel.
What does the controller I want to test do:
It accepts uploads of test records to store it into a database. Each record consists of two files (xml, yml) with the same file name. Both files have to be read and stored in the database. The uploaded files are never stored on the server, they are directly processes.
What I want to test:
Upload a record and check if the correct data is in the database and available over the API
Check if I get the correct error if one file (xml or yml) is missing
Check if I get the right error if the files are not a valid record
and so on ...
What is my current problem?
I would like to use a template for the xml and yml files and use Faker to generate fake data for this test. The reason: Why not? My understanding of testing is, that you should test as many cases as possible and if static data is enough why do we use Faker and Factory in the Unit tests for the database and so on?
When I look at Laravel: Testing File Uploads, they generate there testing files with UploadedFile::fake(). My understanding of those files is, that they are empty and you can't use a template or something like that, to fill it with useful data. Most solutions I found just kept real files in their project. Like this.
I could use blade for this, as shown here, but I'm not really sure if I should abuse blade like this.
I could fully generate the xml and yml files using Yaml and XMLReader/XMLWriter, but there is a lot of static text in those files and I only need to fill data into some specific points.
Questions:
So what is the best way to create such a fake file? Should I use blade or twig or some other templating engine? A small solution would be appreciated.
Or should I generate the full file by myself and why is this better?
Or is there no point in generating fake data and I should use static data instead?
Here is my current test function:
public function testFullRecordUpload() {
// Generate record files to upload
// TODO Use template files with faker, or something like that
$xml_file = UploadedFile::fake()->create('test_file.xml', $sizeInKilobytes = 566);
$yml_file = UploadedFile::fake()->create('test_file.yml', $sizeInKilobytes = 20);
// Send Files
$response = $this->json('POST', '/upload', [
'xml' => $xml_file,
'yml' => $yml_file
]);
// Check response
$response->assertOk();
// Check if the uploaded data is available over API
// TODO
}
Related
I have been stuck on this for hours. Maybe my mind is tired. I hope someone can assist me. I am developing a Laravel application which connects to an external application that creates and edits invoices. One of my routes is supposed to allow a user to download a PDF invoice from this external application. My controller resembles the code below:
public function download(Invoice $invoice)
{
// Creates an instance of the remote invoice
$remoteInvoiceFile = RemoteInvoiceSoftware::find($invoice->id);
return response()->streamDownload(function () use ($remoteInvoiceFile ) {
// This calls the remote server and the server responds with the PDF file contents
$file = $remoteInvoiceFile->download();
echo $file;
}, 'file.pdf');
}
I read about the streamDownoad function from the Laravel documentation and implemented it the same way it was displayed. But I am getting an error where I am able to download a PDF file, however, not only is the file less than 5KB (the original invoice file is about 60KB), I also get an error when I try to open it. Something about the file being corrupt or not parsed well.
When I echo $remoteInvoiceFile->download() without using the streamDownload I get something like this:
Please help me figure what's going on and how I can fix this. Thank you!!
I want to use PHPOffice/PHPWord to generate a word file in Laravel 5.2. This is working great! Only thing is, I would like to stream the file. Or, when the user chooses to generate a word document, it automatically opens.
I looked inside the WriterInterface.php and the only function possible is the save function. Does anyone know how to implicate a stream or open function?
$objWriter->open('../public/uploads/helloWorld.docx');
Save the file to a temporary folder, then use the download function #laravel Docs
I have to create a photo online store for my organization with some additional features. But the main idea is to allow users to upload their digital photos (JPEG format only) and sell them in the store. This project is much related to the websites like iStockPhotos, Fotolia etc...
Is there any standard I should follow for the JPEG images like minimum and maximum sizes and quality?
I am able to use libraries like such as Imagine to make watermark and thumbnail images from the original. But my main concern here, is how may I store the original and duplicate files safely in a proper folder structure securely and how do I create download links to the original files when someone purchased it?
I am planning to build this from scratch using PHP Yii2 framework. So please give me any guidelines to make it successful. Thanks in advance.
I'll split my answer in three parts. The upload-, saving- and the download-process. I expect you have experience with PHP and Yii2 so I won't code the whole thing out but give you the gist.
1. Uploading photos
This one is simple. Simply create a form where you can upload your image-files. Make sure you set the correct enctype (multipart/form-data). The basics of file-uploading with Yii2 are explained here.
When saving your file you need to perform two steps:
save the original
create a resized and watermarked copy
For the second task I can recommend you the library yurkinx/yii2-image which I made really good experience with. It has resizing and watermarking built in.
2. Saving the photos
I usually put them in a dedicated folder within #runtime. The runtime folder is within your project-root and therefore not accessible from the web. Your folder could have the following route, which you put in your params.php for later reference.
#runtime/shop_images/
For the actual file-names you could use the id of the model which I'll explain next. Your file-names could be:
1.jpg (the original)
1_thumb.jpg (the resized and watermarked copy)
You should have a model for the photos. Each uploaded photo would be represented by a row in this models db-table. You also need a table to connect the users and their purchased images (m:n). Let me know if you need the exact schema.
Watch out...you need to create the model-instance first and save it. Otherwise you won't have the id to name the actual files! The easiest way is to overwrite the save()-method od your photo-model as beforeSave() is too early (no id yet!) and afterSave() is too late (no more rollback possibility).
3. Downloading the photos
This one is way easier than it sounds. You create an action within your photo-conroller which lets the user download the pictures.
In the action you first check if the user has purchased the picture. You can do this easily as you simply need to check if there is a row in your user_photo-table linking the currently logged in user to the requested photo.
If the connection is there you are ready to output the photo. This involves two steps:
Configure the response for outputting a photo
The actual output
Response configuration
You can do this completely manual or use my extension. This is the manual way:
public function actionPicture($id) {
//find the model (will throw excetion if not)
$model = $this->findModel($id);
//check if user is allowed
if (!UserPhoto::find()->user(Yii::$app->user->id)->photo($model)->exists()) {
//throw not allowed exception here!
}
//get the path to your photo
$photoPath= Yii::getAlias('#runtime/shop_images') . DIRECTORY_SEPARATOR . $model->id . '.jpg');
$photoPath= FileHelper::normalizaPath($imagePath);
//prepare the response
$response = Yii::$app->response;
$response->headers->set('Content-type', 'image/jpg');
$response->headers->set('Content-length', filesize($photoPath));
$response->format = \yii\web\Response::FORMAT_RAW;
//return the contents
return file_get_contents($photoPath);
}
To give you a little insight on this. Whatever you return from an action will be put in the data-attribute of your response. This will then be formatted into the content attribute of the response. How this will happen depends on the response format you set. In our case we chose FORMAT_RAW which leaves the data as is. Now we can simply return the contents of our photo-file and we are done.
You can further specify how your file gets processed by specifying additional headers. This way for example you can force a download with a certain filename:
$headers->set('Content-disposition', 'attachment; filename="' . $myCustomFileName . '"');
The same way you create an action for your thumbs or extend this one with a second parameter which tells you what size of the image to return.
I hope I could help. If you need any more information on one of the steps, don't hesitate to ask!
DB-Schema
Here is a possible migration to create the two tables needed. The user-table is omitted as you probably created this one already.
public function up() {
//the photo table and the relation to the owning user
$this->createTable('{{%photo}}', [
'id'=>$this->primaryKey(),
'owner_user_id'=>$this->integer(),
'original_filename'=>$this->string()->notNull(),
'filesize'=>$this->integer()->notNull(),
'extension'=>$this->string(4)->notNull(),
'meta_data'=>$this->text(),
]);
$this->addForeignKey('FK_photo_user', '{{%photo}}', 'owner_user_id', '{{%user}}', 'id', 'SET NULL', 'CASCADE');
//the m:n-table mapping users to their purchased photos
$this->createTable('{{%user_photo}}', [
'user_id'=>$this->integer()->notNull(),
'photo_id'=>$this->integer()->notNull(),
'PRIMARY KEY (user_id, photo_id)',
]);
$this->addForeignKey('FK_user_photo_user', '{{%user_photo}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('FK_user_photo_photo', '{{%user_photo}}', 'photo_id', '{{%photo}}', 'id', 'CASCADE', 'CASCADE');
return true;
}
If you look at the schema you will get the gist.
Photo-metadata (EXIF)
If you wish to save meta-data for the photos, usually the EXIF-data, you can do this as well of course. I have to warn you though...there really is no real standard existing where the manufacturers of cameras store their values as I currently make the experience in one of our projects. In our example the most important information would have been the "date taken"-field. This alone can be in several places, depending the manufacturer. Making it even more complicated is the type of data within the EXIF. We had big trouble with illegal characters in there and about every charset-encoding you can imagine.
Nonetheless I'll give you a snipped how we save the data in JSON-format in text column. The big advantage is that you have all the in its original structure. I also added the column to the migration above.
$exif = exif_read_data($model->localFilePath, 'FILE,EXIF');
if ($exif !== false) {
$model->meta_Data = Json::encode($exif);
}
Make sure you read the documentation about exif_read_data() as it definitely has its kinks!
I'm new to Zend's Apigility and I have problem with file upload.
I've created a new rest service and configured fields in the admin UI as described in Apigility documentation: https://apigility.org/documentation/recipes/upload-files-to-api
When trying to obtain any data from InputFilter i get only null values.
Resource controller
public function create($data)
{
$inputFilter = $this->getInputFilter();
$data = $inputFilter->getValues();
var_dump($data);
//return $this->attachments->create($data);
}
var_dump result
array(1) {
["filedata"]=>
NULL
}
For testing purposes I'm using Postman extension for Chrome with Content-Type header set to 'multipart/form-data', and attached file to key: filedata.
I'm pretty sure, I can send files using json and base64_encode, but I would rather hold with it until it would be absolutely necessary.
For those who aren't aware, Apigility is a Zend Framework 2 based framework specifically made for Rest/Rpc API's.
To do file uploads, please refer to their documentation on the recent updates as noted by Jon Day.
Credit : https://apigility.org/documentation/recipes/upload-files-to-api
How can you allow uploading files via your API?
Answer
Zend Framework 2 provides a variety of classes surrounding file upload
functionality, including a set of validators (used to validate whether
the file was uploaded, as well as whether it meets specific criteria
such as file size, extension, MIME type, etc.), a set of filters (used
to allow renaming an uploaded file, as well as, more rarely, to
manipulate the contents of the file), and file-upload-specific inputs
for input filters (because validation of files needs to follow
different rules than regular data).
Currently the limitation is that Apigility will only accept multipart/form-data
Using Xdebug I am getting the following out :
$data_array = $inputFilter->getValues();
$image = $data_array['images_data'];
The $image array looks like this :
name = MemeCenter_1400658513231_337.jpg
type = image/jpeg
tmp_name = /tmp/phpzV3mWA
error = 0
size = 379580
Try this
Update apigility with composer. File upload is working in version 1.0.3
Use Postman to send files but with no headers.Just select form-data.It worked for me.
To move uploaded file use rename instead of move_uploaded_file.
You can use the option ('magicFile' => false) for the MimeType validator which fixes the problem without any modification at the zf library.
I am running Symfony 1.4 on Linux. My application creates pdf files and saves the files in the following directory:
/srv/www/vhosts/myapp/htdocs/stmts
Here is an example of the path to a particular pdf file:
/srv/www/vhosts/myapp/htdocs/stmts/example_001.pdf
My symfony is installed in the following path:
/srv/www/vhosts/myapp/htdocs
How can I create a route from my Symfony application to the example_001.pdf file? I want to be able to create a link in my symfony application to the pdf file. When a user clicks on the link the pdf will be opened.
Thank You
In order for using a route to make sense you would need to be doing something like this:
public function executeDownload(sfWebRequest $request)
{
// assume this method holds the logic for generating or getting a path to the pdf
$pdfPath = $this->getOrCreatePdf();
// disbale the layout
$this->setLayout(false);
$response = $this->getResponse();
// return the binary pdf dat directly int he response as if serving a static pdf file
$response->setHttpHeader('Content-Disposition', 'attachment; filename="'. basename($pdfPath));
$response->setContentType('application/pdf');
$response->setContent(file_get_contents($pdfPath));
return sfView::NONE;
}
That action would actually read the file and send the content. But unless you have a good reason for doing this its not advisable because youre going to incur unnecessary overhead from php.
If you do have a good reason for doin this (restricted access, dynamic file names, etc.) then you would simply determine what paramters you need to use in that action to determine the path to the pdf on the file system and set up a normal route. For example lets say your using a human recognizeable slug to refernece the file. Then you have a db record that holds a mapping of slug to file path. In that case the preceding action might look like this:
public function executeDownload(sfWebRequest $request)
{
$q = Doctrine_Core::getTable('PdfAsset')
->createQuery('p')
->where('slug = ?', $request->getSlug());
$this->forward404Unless($asset = $q->fetchOne());
$pdfPath = $asset->getPath();
// disbale the layout
$this->setLayout(false);
$response = $this->getResponse();
// return the binary pdf dat directly in the response as if serving a static pdf file
$response->setHttpHeader('Content-Disposition', 'attachment; filename="'. basename($pdfPath));
$response->setContentType('application/pdf');
$response->setContent(file_get_contents($pdfPath));
return sfView::NONE;
}
With the corresponding route looking something like:
pdf_asset:
url: /download/pdf/:slug
params: {module: yourModule, action: 'download'}
Note if the files are large you might want to use fopen instead of file_get_contents and then read the data out as a stream so that you dont have to put it all in memory. This would require you to use a view though (but you would still set layout to false to prevent the layout from wrapping your streamed data).