Virtual Filesystem for PHPUnit tests in Laravel 5.4 - phpunit

i'm having a bit of a problem with my PHPUnit integration tests, i have a method which handles a form upload for a video file as well as a preview image for that video.
public function store($request)
{
/** #var Video $resource */
$resource = new $this->model;
// Create a new Content before creating the related Photo
$contentRepo = new ContentRepository();
$content = $contentRepo->store($request);
if($content->isValid()) {
$resource->content_id = $content->id;
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
\File::makeDirectory($directory, 0755, true);
$request->video->move($directory.'/', $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
$request->preview_image->move($directory.'/', $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
$resource->highlighted = intval($request->input('highlighted') == 'on');
$resource->save();
return $resource;
}
else {
return $content;
}
}
The important part to keep is the $request->video->move() call which i probably need to replace in order to use Virtual Filesystem.
and then the test
public function testVideoUpload(){
File::put(__DIR__.'/frontend/videos/assets/image.mp4', 'test');
$file = new UploadedFile(__DIR__.'/frontend/videos/assets/image.mp4', 'foofile.mp4', 'video/mp4', 100023, null, $test=true);
File::put(__DIR__.'/frontend/images/assets/image.jpg', 'test');
$preview = new UploadedFile(__DIR__.'/frontend/images/assets/image.jpg', 'foofile.jpg', 'image/jpeg', 100023, null, $test=true);
$this->post('/admin/videos', [
'title' => 'My Video #12',
'description' => 'This is a description',
'actors' => [$this->actor->id, $this->actor2->id],
'scenes' => [$this->scene->id, $this->scene2->id],
'payment_methods' => [$this->paymentMethod->id],
'video' => $file,
'preview_image' => $preview
])->seeInDatabase('contents', [
'title' => 'My Video #12',
'description' => 'This is a description'
]);
}
As you can see, i need to create a dummy file in some local directory and then use that in the HTTP request to the form's endpoint, then after that, that file would be moved and i need to delete the created folder and the new moved file... it's an authentic mess.
As such i want to use Virtual Filesystem instead, but i have no idea how to set it up in this particular case, i've already downloaded a package and set it up, but the questions are, first, which package have you used/recommend and how would you tweak the class and the test to support the Virtual Filesystem? Would i need to switch over to using the Storage facade instead of the $request->video->move() call? If so how would that be done exactly?
Thank you in advance for your help

I couldn't figure out the VFS system, however i do have somewhat of an alternative that's still kinda messy but gets the job done.
Basically i set up two methods on my PHPUnit base class to setup and teardown the temp folders i need on any test that requires them, because i'm using Database Transactions the files get deleted on every test run and i need to create new dummy files every time i run the test.
So i have two methods setupTempDirectories and teardownTempDirectories which i will call at the beginning and at the end of each test that requires those temporary directories.
I put my temp files in the Storage directory because sometimes i run my tests individually through PHPStorm and the __DIR__ command gets messed up and points to different directories when i do that, i also tried __FILE__ with the same result, so i just resorted to using Laravel's storage_path instead and that works fine.
Then that leaves the problem of my concrete class which tries to move files around and create directories in the public folder for them... so in order to fix that i changed the code to use the Storage facade, then i Mock the Storage facade in my tests
So in my concrete class
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
Storage::makeDirectory($directory, 0755, true);
Storage::move($request->video, $directory . '/' . $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
Storage::move($request->preview_image, $directory . '/' . $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
And then in my test i mock both the makeDirectory and the move methods like such
// Override the Storage facade with a Mock version so that we don't actually try to move files around...
Storage::shouldReceive('makeDirectory')->once()->andReturn(true);
Storage::shouldReceive('move')->twice()->andReturn(true);
That makes my tests work and does not actually leave files behind after it's done...i hope someone has a better solution but for the time being this is what i came up with.
I was actually trying to use VFS but it never worked out... i keep getting errors that the original file in the storage directory is not found even though it's right there...
I'm not even sure the Storage facade was using VFS in the background to begin with even though it should...

Related

Laravel Dusk testing with sqlite drops file busy exception

T try to write a laravel dusk login (with sqlite) test but I still kepp getting
Tests\Browser\LoginTest::employee_can_login
ErrorException: unlink(/var/www/laravel/database/database.sqlite): Text file busy
my test looks like
/** #test research analyst login **/
public function employee_can_login()
{
$user = factory(User::class)->create([
'email' => 'login#gmail.com',
'password' => bcrypt('password'),
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit(new Login())
->type('email', 'login#gmail.com')
->type('password', 'password')
->press('Login')
->assertSee('Dashboard')
->assertSee('You are logged in!')
->assertSee($user->name);
});
}
on migration the sqlite tables were created,
What does that mean how do I fix this error?
As answered in similar thread, this is common problem (at least for me) with virtual machines. You can try not to share this file (so probably better move this file to not shared directory) or to solve this temporary you need to restart your virtual machine to make it work.

How to integrate Dropzonejs with wordpress media handler in frontend?

How can I integrate Dropzonejs file uploader library in wordpress front end just like the built in one and have the uploaded one available in my media library?
Dropzonejs is a very extensive javascript library that provides a lot of options to handle media uploading.
To integrate dropzonejs with wordpress the process is pretty straight forward. Assume the following piece of code is where you want to appear your uploader.
<div id="media-uploader" class="dropzone"></div>
<input type="hidden" name="media-ids" value="">
Having a class dropzone will automatically attach the dropzone event with the element. That will stop us from overriding default parameters. So we would like to disable the auto discover feature of the library.
// Disabling autoDiscover, otherwise Dropzone will try to attach twice.
Dropzone.autoDiscover = false;
Now we will use jQuery to bind our configuration with the element.
jQuery("#media-uploader").dropzone({
url: dropParam.upload,
acceptedFiles: 'image/*',
success: function (file, response) {
file.previewElement.classList.add("dz-success");
file['attachment_id'] = response; // push the id for future reference
var ids = jQuery('#media-ids').val() + ',' + response;
jQuery('#media-ids').val(ids);
},
error: function (file, response) {
file.previewElement.classList.add("dz-error");
},
// update the following section is for removing image from library
addRemoveLinks: true,
removedfile: function(file) {
var attachment_id = file.attachment_id;
jQuery.ajax({
type: 'POST',
url: dropParam.delete,
data: {
media_id : attachment_id
}
});
var _ref;
return (_ref = file.previewElement) != null ? _ref.parentNode.removeChild(file.previewElement) : void 0;
}
});
In the code above what we have done is we attached dropzone with our element with some parameters-
url - location where we want to send our files to upload. I'll initialize the variable later.
acceptedFiles - since we are only interested in uploading images, we will limit the files to be attached only to images. You can find about more in the website of this library.
success - a callback that is fired when the file/image is uploaded successfully. It accepts two parameter the reference of the uploaded file itself and the response from the server. This is very important, here we stored the attachment id in our form. You can perform a validation here prior to store the id.
error - if the file failed to upload then you can perform any task here.
addRemoveLinks - add the remove file link below the preview panel, you can style it with your css.
removedfile - handles the operation while you click on the remove file link for an image in the preview panel. In this function we sent an ajax call to our server to remove the image from the library
Of course there are a lot of option available, but I found these are the most basic parameters I required to setup my drag-n-drop media uploader.
Now the most important thing is to decide about the file uploader url. You can have a custom file where you would want to process the operation. But I found another way.
From this question and the answer I found using admin-post.php file is pretty amazing.
Many people complained about this admin-post.php, so think sticking to the wp_ajax.php is the best option.
So I initialized the drophandler variable prior to my dropzone initialization as follows-
wp_enqueue_script('dropzone','path/to/dropzone', array('jquery'));
wp_enqueue_script('my-script','path/to/script',array('jquery','dropzone'));
$drop_param = array(
'upload'=>admin_url( 'admin-ajax.php?action=handle_dropped_media' ),
'delete'=>admin_url( 'admin-ajax.php?action=handle_deleted_media' ),
)
wp_localize_script('my-script','dropParam', $drop_param);
Now we are ready to send our images to the server. Here we will add some php code whether in the theme's function.php file or in our plugin file, but we need to be assured that it is loaded.
The following function will take care of the uploading the image and saving as an attachment in the library.
add_action( 'wp_ajax_handle_dropped_media', 'handle_dropped_media' );
// if you want to allow your visitors of your website to upload files, be cautious.
add_action( 'wp_ajax_nopriv_handle_dropped_media', 'handle_dropped_media' );
function handle_dropped_media() {
status_header(200);
$upload_dir = wp_upload_dir();
$upload_path = $upload_dir['path'] . DIRECTORY_SEPARATOR;
$num_files = count($_FILES['file']['tmp_name']);
$newupload = 0;
if ( !empty($_FILES) ) {
$files = $_FILES;
foreach($files as $file) {
$newfile = array (
'name' => $file['name'],
'type' => $file['type'],
'tmp_name' => $file['tmp_name'],
'error' => $file['error'],
'size' => $file['size']
);
$_FILES = array('upload'=>$newfile);
foreach($_FILES as $file => $array) {
$newupload = media_handle_upload( $file, 0 );
}
}
}
echo $newupload;
die();
}
The following action take care of the deletion of the media element. Second parameter of wp_delete_attachment() function allows us to decide whether we want to trash the image or completely delete it. I wanted to delete it completely so passed true.
add_action( 'wp_ajax_handle_deleted_media', 'handle_deleted_media' );
function handle_deleted_media(){
if( isset($_REQUEST['media_id']) ){
$post_id = absint( $_REQUEST['media_id'] );
$status = wp_delete_attachment($post_id, true);
if( $status )
echo json_encode(array('status' => 'OK'));
else
echo json_encode(array('status' => 'FAILED'));
}
die();
}
This will return the attachment_id in the response and we'll get it in the success function. In the media_handle_upload( $file, 0 ); I passed the reference of the file and a 0 because I didn't wanted to assign the media with any post yet (0 for no post, but if you want to assign then pass the post ID here. More reference in the codex.)
This is all for uploading media in wordpress.
Note: I haven't completed the removing uploaded file part. I'll complete this in a moment.
UPDATE
The post is updated. Now we can remove uploaded media elements from the uploader container. Thanks to this question and the answer I could figure out the actual process.
Those who are having problems getting this to work for non-admin users; please use admin-ajax.php instead of admin-post.php.
I had faced a strange issue that admin-post.php would work for non-admin users on my local server; but my live server refused to let non-admins upload files. php would echo entire page instead of the echoed value.
I replaced admin-post.php with admin-ajax.php and uploads work super cool.
I hope this helps.
The solution added to this post is incorrect unless I've misunderstood the question. Basically the solution won't work for anyone who isn't logged in as an admin. It took me 30 minutes to work it out plus the solution for removing images doesn't delete it from the media library.

Running more than one console commands in a controller Symfony 2

First of all I would like to thank you all for looking at my question. Here's my question.
I want to run three existing console commands in Symfony 2 from a controller. So I wrote three separate functions for that (see below). I managed to run 'doctrine:mapping:import' (find the code below) without any issue inside a controller. Next thing I wanted to do is generate entities based on the imported mapping files. I could not run the 'doctrine:generate:entities' command without shutting down and booting the kernel (which I think a bad idea, look at the code below). Without shutting down and booting the kernel it won't generate the entities for me. But after shutting down and booting the kernel it creates the entities for me (I am somewhat happy now). The next problem I am having is when I run the 'doctrine:generate:form' command (find the code below). When I run this code just after generating entities it say's 'Class 'THE NAME OF MY CLASS' does not exist'. This can't happen. Because I am running the form build command after generating the entities. Even I try searching for the class whether it actually there by accessing the file physically. And it is there. So I am totally stuck in here, I'd say.
Well, I know it's a lengthy question. If someone can tell what's causing generate entities to not to create entities without shutting down and booting the kernal and form builder command not to work, even the entity files are there, that would be really really appreciated. One thing I noticed though, this commands (3 functions) works fine when I run one at a time. But I want to call these 3 functions one after another. So mainly the problem occurred when I sequentially call these 3 functions.
Code to 'doctrine:mapping:import'
public function executeImportCommandAction($filter)
{
$kernel = $this->container->get('kernel');
$app = new Application($kernel);
$app->setAutoExit(false);
$input = new \Symfony\Component\Console\Input\ArrayInput(
array('command' => 'doctrine:mapping:import', 'bundle' => 'TESTClientBundle', '--filter'
=> $filter, 'mapping-type' => 'yml'));
$app->doRun($input, new \Symfony\Component\Console\Output\ConsoleOutput());
}
Code to 'doctrine:generate:entities'
public function executeBuildFormCommandActions($entity)
{
$kernel = $this->container->get('kernel');
$kernel->shutdown();
$kernel->boot();
$app = new Application($kernel);
$app->setAutoExit(false);
$input = new \Symfony\Component\Console\Input\ArrayInput(
array('command' => 'doctrine:generate:entities', 'name' => 'TESTClientBundle',
'--no-backup' => 'true'));
$app->doRun($input, new \Symfony\Component\Console\Output\ConsoleOutput());
}
Code to 'doctrine:generate:form'
public function executeBuildFormCommandActions($entity)
{
#$kernel = $this->container->get('kernel');
$app = new Application($kernel);
$app->setAutoExit(false);
$input = new \Symfony\Component\Console\Input\ArrayInput(
array('command' => 'doctrine:generate:form', 'entity' => 'TESTVClientBundle:'.$entity.''));
$app->doRun($input, new \Symfony\Component\Console\Output\ConsoleOutput());
}
Thanks a lot in advance.
Cheers!
Do you know there is an Process Component? http://symfony.com/doc/current/components/process.html
You can easily run a symfony command with it.
First of all I need to thank #vincecore to giving me the heads up regarding Symfony Process Component. I managed to workaround with Process component to achieve what I wanted to achieve. I assume kernel boot shutdown is not a proper approach even though it works well. However generate form did not work even after dealing with the kernel. This is the piece of code I found working when running all 'doctrine:mapping:import', 'doctrine:generate:entities' and 'doctrine:generate:form' console commands inside the controller. For the sake of clarity I'd like to illustrate code related to generating form.
public function executeBuildFormCommandActions($form_file)
{
$move_to_project = 'C:/xampp5.5.11/htdocs/proj_test/';
$commandline = "php app/console doctrine:generate:form TESTClientBundle:$form_file";
$form_type_file = $this->get('kernel')->getRootDir() . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'TEST'
. DIRECTORY_SEPARATOR . 'ClientBundle' . DIRECTORY_SEPARATOR .
'Form' . DIRECTORY_SEPARATOR . $form_file.'Type.php';
if(is_file($form_type_file)){
unlink($form_type_file);
}
$process = new \Symfony\Component\Process\Process($commandline);
$process->setWorkingDirectory($move_to_project);
$process->run();
try {
if (!$process->isSuccessful()) {
throw new \RuntimeException($process->getErrorOutput());
}
echo $process->getOutput().'<hr/>';
} catch (\RuntimeException $r) {
echo $r->getMessage();
}
}
The good thing about this approach (Process component) is you can directly execute the command as you are executing using the command console. However without setting the working directory to project folder, this did not work at first place. That's obvious and reason behind the fact is command can't access 'app/console' anywhere outside the project folder. So I had to move the console commands inside the project folder ($process->setWorkingDirectory($move_to_project)) as executing commands within the project folder. Rest of two functions (import and generate entities) are also same, only the commands and arguments change.
Hope this helps someone who tries and find no luck when it comes to running more than one command consoles inside Symfony 2 Controller.
Cheers!

php - Is php caching classes before creation?

Background Information:
I'm using Symfony Console Component to write a console application that is wrapped into a Shell object. I wrote a command named console:reload that empties the array of commands from the Application object, and re-adds commands classes listed under certain directory.
This command is run when the shell starts, so, the Application is loaded with the available commands. The classes being loaded are located in a special directory and should follow a simple name rule: <CommandName>Command.php:
// Inside ReloadCommand->execute() method...
$pamperoApp = $this->getApplication();
$pamperoApp->clearCommands();
$namespace = "pampero\\cli\\modules";
foreach(glob(MODULES_DIR . "/*/*Command.php") as $command) {
$class = str_replace(".php", "", $namespace . "\\" . basename(dirname($command)) . "\\" . basename($command));
$this->getApplication()->add(new $class);
}
The autoload provided by Symfony (Composer?) ClassLoader is used:
// Main entry point...
loader = require_once __DIR__ . '/../vendor/autoload.php';
$loader->set('pampero', __DIR__ . '/../..');
I read the code from ClassLoader class and what it does is to store file name path, so no object caching there.
Here's the problem:
I launch the app: php packages.php. The shell appears after ReloadCommand command being executed. A list of available and loaded commands are ready to be used.
If I create a new file, let's say: ExampleCommand.php, and then I type: console:reload, the new command will indeed be added. Now, If I modify the code inside ExampleCommand.php and run console:reload again, the changes made to the class won't take effect.
But that's not all. If I remove the example file, call console:reload, create the file again and run: console:reload the command will be added.
Reading:
I have read APC related things, and before creating new classes I have done things like:
// Prior adding commands in ReloadCommand
apc_clear_cache();
apc_clear_cache('user');
apc_clear_cache('opcode');
Without luck. I've also run apc.php and enabled/disabled apc.enable_cli option. None of those things creates the object represented by the modified file.
So my hints and clues about the problems turns to be classes caching when a file/class is found. But how to avoid that for this special case? I don't want to restart the shell if some extra funcionality is added through classes.
Any ideas?
I will answer my own question as I found a solution.
Ok, clues were fine. The problem was that PHP including a file binds the symbols for later use while parsing the file.
So, I needed some kind of instrospection. After reading/googling/searching for all night, finally I've ended up finding Runkit.
Runkit documentation can be found here. I know that is not the best thing you can do with your code design. But for my project needs, the truth is that Reflection was needed.
Here is the modified code using Runkit:
protected function execute(InputInterface $input, OutputInterface $output)
{
// Gets a reference to the console application and removes all commands
$pamperoApp = $this->getApplication();
$pamperoApp->clearCommands();
// Adds default commands and add this command
$pamperoApp->addCommands($pamperoApp->getDefaultCommands());
$pamperoApp->add($this);
$namespace = "pampero\\cli\\modules";
foreach(glob(MODULES_DIR . "/*/*Command.php") as $filename) {
$className = str_replace(".php", "", $namespace . "\\" . basename(dirname($filename)) . "\\" . basename($filename));
// Do not add this command again. This command shouldn't be modified on-the-fly
if (get_class($this) !== $className) {
$class = new $className();
// Redefines the class definition
runkit_import($filename, RUNKIT_IMPORT_CLASSES | RUNKIT_OVERRIDE_OBJECTS | RUNKIT_IMPORT_OVERRIDE);
$pamperoApp->add($class);
}
}
}

Must manually load PHP ActiveRecord models

I'm drawing a blank. I have code working locally (which is MAMP). When moving to a nginx ubuntu box (running php-fpm), for some reason, phpactiverecord is acting up.
I finally traced it down to this - All of my model classes, I have to load manually. If I add a require_once() underneath my code, then it works fine. If I don't, then I get errors like:
PHP Fatal Error: Class not found ... on the models I've created..
Does anyone have ANY idea what direction I could troubleshoot this in? I checked permissions to the models folder (which is not in the public root), echo'd out the path that is sent over to cfg->set_model_directory is correct, etc..
This sound like a nginx or php thing? I'm guessing nginx since this works on my MAMP?
Doesn't work:
ActiveRecord\Config::initialize(
function ($cfg) {
$cfg->set_model_directory(BASE_PATH . '/models');
$cfg->set_connections(
array(
'development' => 'mysql://blah:removed#localhost/com_dbname'
)
);
}
);
Works:
ActiveRecord\Config::initialize(
function ($cfg) {
$cfg->set_model_directory(BASE_PATH . '/models');
$cfg->set_connections(
array(
'development' => 'mysql://blah:removed#localhost/com_dbname'
)
);
}
);
require_once(BASE_PATH . '/models/model1.php');
require_once(BASE_PATH . '/models/model2.php');
Update
Adding in actual code to help identify issue:
require_once ('../lib/php-activerecord/ActiveRecord.php');
ActiveRecord\Config::initialize(
function ($cfg) {
$cfg->set_model_directory('/var/www/uc1/models');
$cfg->set_connections(
array(
'development' => 'mysql://test_usr:test_pwd#localhost/test_db'
)
);
}
);
require_once ('/var/www/uc1/models/ucurls.php'); //Name of model file. Must manually include to get this to work on my nginx server.
$_record = UCUrls::find_by_urlkey('example.com/123');
echo "urlkey=" . $_record->urlkey;
I solved this issue in windows adding a line in the file ActiveRecord.php,
in the function activerecord_autoload($class_name)
at the line 39 or 40
$file = "$root/$class_name.php";
//add this line
$file = strtolower($file);
Set trace in ActiveRecord.php too look where are ActiveRecord is searching for models.
But I think your issue is in filesystem - Mac OS X by default uses Case Insensitive filesystem, while Ubuntu's Case Sensitive filesystem.
So your model UCUrls should be in file /var/www/uc1/models/UCUrls.php, not in /var/www/uc1/models/ucurls.php

Resources