php - Is php caching classes before creation? - symfony

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);
}
}
}

Related

Virtual Filesystem for PHPUnit tests in Laravel 5.4

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...

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!

Is is possible to have symfony2 log missing translation strings so that I know what needs adding to my xilff files?

I have a symfony project in which I've been through my twig templates and added {% trans %}...{% endtrans %} or adding translations like {{ title|trans }} where appropriate. I've also added a messages.de.xliff file and that is working perfectly for the few translations I have tried.
Is there a way I can get a list of strings missing from my xliff file? It's quite hard to keep track of every translation as I add it. It seems like it should log a failure to get a translation in a log file somewhere, but I've been googling a while and can't find anything.
Hi Try following May Be helpful.
https://github.com/schmittjoh/JMSTranslationBundle/blob/master/Resources/doc/index.rst
Very powerful tool and definitely takes care of you problem.
This is a very crappy patch to apply in vendor/symfony that does what I need. Probably not to be run on a production server!
diff --git a/src/Symfony/Component/Translation/MessageCatalogue.php b/src/Symfony/Component/Translation/MessageCatalogue.php
index b55676f..98a5cba 100644
--- a/src/Symfony/Component/Translation/MessageCatalogue.php
+++ b/src/Symfony/Component/Translation/MessageCatalogue.php
## -128,6 +128,8 ## class MessageCatalogue implements MessageCatalogueInterface
return $this->fallbackCatalogue->get($id, $domain);
}
+ error_log('Translation not found: "' . $id . '"');
+
return $id;
}
My solution was to overwrite the Translator and MessageCatalogue classes.
Translator:
class RegisteringTranslator extends \Symfony\Component\Translation\Translator
{
protected function loadCatalogue($locale)
{
parent::loadCatalogue($locale);
if ( ! $this->catalogues[$locale] instanceof RegisteringMessageCatalogue) {
$registeringCatalogue = new RegisteringMessageCatalogue($locale);
$registeringCatalogue->addCatalogue($this->catalogues[$locale]);
$this->catalogues[$locale] = $registeringCatalogue;
}
}
}
Catalogue:
class RegisteringMessageCatalogue extends \Symfony\Component\Translation\MessageCatalogue
{
public function get($id, $domain = 'messages')
{
if ( ! $this->has($id, $domain)) {
error_log('Translation not found: "' . $id . '"');
}
return parent::get($id, $domain);
}
}
Of course you need to use the new Translator class.
Also not very nice because it uses the protected methods and properties of Translator class. But better than changing the Symfony code directly.
I know this is an old question, but I'm posting here just in case somebody still has the same problem.
Starting from Symfony 2.6, you'll find a very nice addition to the web debug toolbar that shows how many translations you're missing.
By clicking it, the profiler will display a detailed list of missing translation.
Works out of the box, without any configuration.
Normally you should be able to use the Symfony command debug:translation via app/console.
Something like this:
$ php app/console debug:translation --only-missing <locale> <Bundle Name>
A concrete example would be:
$ php app/console debug:translation --only-missing nl AppBundle
That would output:
----------- ---------- ------------------------------------------------------------------------------------------------------- ------------------------------------------
State Domain Id Message Preview (nl)
----------- ---------- ------------------------------------------------------------------------------------------------------- ------------------------------------------
missing messages Create a clean selection Create a clean selection
missing messages New Selection New Selection
missing messages login.labels.geoserver_url login.labels.geoserver_url

Implementing GeocodableBehavior in Symfony 1.4 (using Propel)

I'm trying to implement the GeocodableBehavior on a Symfony 1.4 (with Propel 1.6) project i'm working on, but until now it's a complete failure. I've tried to search if other people but I didn't found anything, like if I was the only one having troubles with this.
So, maybe I'm missing something very very easy, but following the instructions given on the GeocodableBehavior leads to nothing but errors, and I can't figure out where's the problem.
I followed instructions for the GeocodableBehavior (here -> http://www.propelorm.org/cookbook/geocodable-behavior.html)
This seems to work as i'm getting the latitude/longitude columns created on my model. Until then, it works fine.
Where things get a little more complicated is when trying to save an object with the GeocodableBehavior, there's problems with the Geocoder class.
(Documentation here -> https://github.com/willdurand/Geocoder)
My class is Point, referring to a geolocated point, an address. When creating a Point using sf admin generator, the behavior which is supposed to use some fields (street, postal_code, country, etc) to query the GoogleMaps api, just fails to use the Geocoder class.
Fatal error: Class 'Geocoder\Geocoder' not found in /var/www/vhosts/www._________.local/lib/model/om/BasePoint.php on line 3717
I put the Geocoder class in a lib/vendor/geocoder folder, I tried to use the autoload.yml file to load it, but nothing changes...
autoload:
geocoder:
name: geocoder
path: %SF_LIB_DIR%/vendor/geocoder
recursive: on
There's something i'm missing in how to load those classes in my sf project, and i can't find what. Geocoder package has an autoload.php file but i didn't manage to "load" it successfully...
Thanks in advance.
I know it's kinda giving up on the autoloader, but you could establish a register function in /config/ProjectConfiguration.class.php. The only downside is that you will need to add a call to the function before any block that uses Geocoder.
class ProjectConfiguration extends sfProjectConfiguration
{
static protected $geocoderLoaded = false;
static public function registerGeocoder()
{
if (self::$geocoderLoaded) {
return;
}
require_once sfConfig::get('sf_lib_dir') . '/vendor/geocoder/autoload.php';
self::$geocoderLoaded = true;
}
...
}
Then just execute ProjectConfiguration::registerGeocoder(); anywhere you'd need the class. It's more annoying than getting the autoloader to work, but it's at least dependable.
Did you check your autoload cache to see it there is something related to Geocoder?
/cache/[apps_name]/dev/config/config_autoload.yml.php
/cache/project_autoload.cache
Maybe, manually add the autoload in the /config/ProjectConfiguration.class.php:
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
require_once sfConfig::get('sf_lib_dir').'/vendor/geocoder/src/autoload.php';
Using the built-in autoloader should be a working option, but you can also combine symfony's autoloader with a "PSR-0 enabled" one. Basically, this boils down to the following implementation:
public function setup()
{
// plugin stuff here
// register the new autoloader
spl_autoload_register(array($this, 'autoloadNamespace'));
}
public function autoloadNamespace($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strripos($className, '\\'))
{
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
// make sure that the path to Geocoder is correct
foreach(array(
sfConfig::get('sf_lib_dir').'/vendor/Geocoder/src' . DIRECTORY_SEPARATOR . $fileName . $className . '.php',
) as $fileName)
{
if (file_exists($fileName))
{
require $fileName;
return true;
}
}
return false;
}
With this additional autoloader, your application should be able to use Geocoder.

Can I include an optional config file in Symfony2?

I want to make a local config file, config_local.yml, that allows each development environment to be configured correctly without screwing up other people's dev environments. I want it to be a separate file so that I can "gitignore" it and know that nothing essential is missing from the project, while simultaneously not having the issue of git constantly telling me that config_dev.yml has new changes (and running the risk of someone committing those changes).
Right now, I have config_dev.yml doing
imports:
- { resource: config_local.yml }
which is great, unless the file doesn't exist (i.e. for a new clone of the repository).
My question is: Is there any way to make this include optional? I.e., If the file exists then import it, otherwise ignore it.
Edit: I was hoping for a syntax like:
imports:
- { resource: config.yml }
? { resource: config_local.yml }
I know this is a really old question, and I do think the approved solution is better I thought I would give a simpler solution which has the benefit of not changing any code
You can use the ignore_errors option, which won't display any errors if the file doesn't exist
imports:
- { resource: config_local.yml, ignore_errors: true }
Warning, if you DO have a syntax error in the file, it will also be ignored, so if you have unexpected results, check to make sure there is no syntax error or other error in the file.
There is another option.
on app/appKernel.php change the registerContainerConfiguration method to this :
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
$extrafiles = array (
__DIR__.'/config/config_local.yml',
);
foreach ($extrafiles as $filename) {
if (file_exists($filename) && is_readable($filename)) {
$loader->load($filename);
}
}
}
this way you have a global config_local.yml file that overwrites the config_env.yml files
A solution is to create a separate environment, which is explained in the Symfony2 cookbook. If you do not wish to create one, there is another way involving the creation of an extension.
// src/Acme/Bundle/AcmeDemo/DepencendyInjection/AcmeDemoExtension.php
namespace Acme\DemoBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
// All following files will be loaded from the configuration directory
// of your bundle. You may change the location to /app/ of course.
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
try
{
$loader->load('config_local.yml');
}
catch(\InvalidArgumentException $e)
{
// File was not found
}
}
}
Some digging in the Symfony code revealed me that YamlFileLoader::load() FileLocator::locate() will throw \InvalidArgumentException, if a file is not found. It is invoked by YamlFileLoader::load().
If you use the naming conventions, the extension will be automatically executed. For a more thorough explanation, visit this blog.
I tried both above answers but none did work for me.
i made a new environment: "local" that imports "dev", but as you can read here: There is no extension able to load the configuration for "web_profiler" you also had to hack the AppKernel class.
Further you couldnt set config_local.yml to .gitignore because the file is necessary in local env.
Since i had to hack the AppKernel anyway i tried the approach with the $extrafiles but that resulted in "ForbiddenOverwriteException"
So now what worked for me was a modification of the $extrafiles approach:
replace in app/AppKernel.php
$loader->load(__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml');
with
if ($this->getEnvironment() == 'dev') {
$extrafiles = array(
__DIR__ . '/config/config_local.yml',
);
foreach ($extrafiles as $filename) {
if (file_exists($filename) && is_readable($filename)) {
$loader->load($filename);
}
}
} else {
$loader->load(__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml');
}

Resources