Output progress for console and web applications - php

I'm writing an yii2 app that is mainly used as an console application. I have components or (micro)services that fetches some data from a server, handle them and save the information I need to db. Of course there are several for loops and in these loops I output my current progress with the use of yii\helpers\Console::updateProgress or just echo to watch the progress and testing output (like starting with xxx products). On some events I log important informations with Yii::info() or Yii::error() and so on. Normally a cron handling tasks like pullProductUpdates or something else and i.
However in some cases I need the method (i.e. pullProductUpdates) in my webapplication too. But then there must not be any echo command active or Console::updateProgress commands.
Of course I don't have problems with the logging methods from Yii because I configured the log targets and they will not echoing something. But I'm uncertain how to handle the echo commands...
One way is to check wether $_SERER['REMOTE_ADDR'] is set or not. In console it will evaluate to null so I can warp an if {} else {} around. A probably better solution is to write a log() or progress() method. A trait could be useful?
So how should I design a solution? Is there any pattern for this? Should my services implement an interface like loggable or proressable? Or use an Logger/ Progress objects and use some kind of DI (dependency injection)? Because I don't want to write those log() or progress() methods functions more than one time. Besides I can't use a progress function in a webapplication. One reason is I don't know how to to that (if its possible with php here), but this would be another question.
Thanks in advance.

As a PHP programmer you should be aware of and use the PSR. In this case you should use dependency injection and the LoggerInterfaces.
For web application you should configure your composition root to use a logger implementation that logs to a file. For console application you should log to the terminal.
The composition root is the place where you configure your Dependency Injection Container (DIC). See more about Yii DIC here.
In order to do that you should be able to switch between these two composition roots by an environment variable or by php_sapi_name.

Related

Decoupling output in Symfony command line

Note: I refer to the Symfony Console component quite a lot in my question, but I think this question could be considered broader if thought about in the context of any user interface.
I am using the Symfony Console component to create a console application. I am trying to keep class coupling to a minimum, as it makes unit testing easier and is generally better practice.
My app has some processes which may take some time to complete, so I want to keep the user informed as to what is happening using progress bars and general text output as it runs. Symfony requires an instance of the Symfony Console OutputInterface to be passed to any command's execute method. So far so good; I can create progress bars and output text as I please. However, all of the heavy lifting of my app doesn't happen in the commands' execute methods and is instead within the core classes of my application. These classes shouldn't and don't know they are being used in a console application.
I am struggling to keep it this way because I don't know how to provide feedback to the console (or whatever user interface) without injecting the output class into my core. Doing so would result in tight coupling between the console output class and my application core. I have thought about using an event dispatcher (Symfony has one), but that too means my core will be coupled with the dispatcher (maybe that's fine). Ideally, I need to sort of "bubble" my application state back up to the execute method of the invoked command, where I can then perform output.
Could someone point me in the right direction, please? I feel like this must actually be quite a common case but can't find much about it.
Thanks for your time in advance!
I have succesfully used the event dispatcher approach before. You can trigger events at the start, progress, and end of processing for example, and have an event listener update the progress bar based on that.
<?php
$progress = $this->getHelperSet()->get('progress');
$dispatcher = $this->getContainer()->get('event_dispatcher');
$dispatcher->addListener('my_start_event', function (GenericEvent $event) use ($progress, $output) {
$progress->start($output, $event->getArgument('total'));
});
$dispatcher->addListener('my_progress_event', function () use ($progress) {
$progress->advance();
});
$dispatcher->addListener('my_finish_event', function () use ($progress) {
$progress->finish();
});
If you really want to avoid coupling of the event dispatcher in your service, you could extend or decorate your class (probably implementing a shared interface), and only use the event dispatcher there. You would need an extension point (public or protected method) in the base class however to be able to notify of any progress.

how to get application instance in symfony2

I can't find how to make something like ::getInstance() like in other php frameworks to get Application (singleton) instance in symfony2
I found only sfContext::getInstance() buy it doesn't work and looks like its method from symfony1
add more info.
I have some class - abstract class Post { }. I want to get some application config, environment variables inside this class. i don't want to put all this stuff to constructor method, instead i want to call application instance and get all i want
If you take a look at web/app.php you will see that your application instance is just a global variable called kernel
$kernel = $GLOBALS['kernel'];
die(get_class($kernel));
However, the reason #san40 told you to read the documents is that you probably never actually need it for anything. SF2 is very different than SF1. So if you explained why your wanted the instance then someone might be able to suggest alternative approaches.
===================================
Looking at web/console shows that same principle for console commands except the instance is called application.
die(get_class($GLOBALS['application']))
However, using the application instance to pull environment variables and such is not recommended. If nothing else, it makes your objects completely dependent in the instance. Dependency injection is what you want. Read through the stuff in the s2 documentation.

Testable Code and global constants

Here I am writing a small app with the sole intent of acquiring better OOP/testable code habits. And loving it, btw!
I am striving to assimilate the methodology behind developping 'testable code', mostly by reading posts from unit testing evangelists such as Sebastien Bergmann, Misko Hevery and Giorgio Sironi.
Among the hardships I assimilated is the misuse of static methods, objects that depend on objects that depend on objects. Currently, I am stuck on global wide constants. At the start of my application I load one single CONSTANT that simply sets the application mode in debug or prod:
/**
* APP_MODE values:
*
* PROD Production, no errors displayed email sent on error, logs to
* logs/app-<date-time>.log.
*
* DEBUG: All warnings and errors displayed, emails disabled and log messages
* sent to console. Whether in-memory modifications to configuration
* data are allowed
*/
define("APPMODE", "DEBUG");
How can one test app classes for proper error handling depending on the state of this constant?
At first my thought was to simply move the global constant to a class constant instead in my init class and that solves the case for this particular class, but I am not satisfied with this procedure. I mean, should one simply avoid sitewide constants that are not "truly" constants in the strict sense of one possible value always?
I can't imagine testers have to write 2 test suites for every class, ie initClassDebugTest.php and initClassProdTest.php unless phpUnit can somehow reset global state? Should global constants used this way be avoided? I have a weird gut feeling I should not use a constant here at all. I would be very curious to know how test savy coders out there would handle global defines with 2 possible values at runtime.
It mainly depends on how you create your objects and how many classes access this APPMODE.
Let's see what APPMODE does:
* DEBUG: All warnings and errors displayed, emails disabled and log messages
* sent to console. Whether in-memory modifications to configuration
* data are allowed
Something like this usually gets solved by passing in "DebugLogger" and "DontSendEmailMailer" to the classes that need to send mail.
By doing this you only need a few factories (or whatever you use to create your object graph) that need to know about "production" vs "development".
The classes that do your actual business logic should not know if it's run in production or not. That would mean the developer of each class would have to care about that and every class would need to be changed if you .. say.. have a "staging" environment. It introduces lots of global state that, like you discovered, is hard to test.
If errors should be displayed or not to be decided in your models in your php.ini or in your application bootstrap and should not concern the rest of your application.
I'd start of moving that "debug" functionality out of classes that need your APPMODE setting and move that into to dedicated (logging, mailing, ...) classes. The real thing (that actually sends mail) and the debug thing (that maybe writes mails to disk?). Both of those classes can be tested properly (testing a null logger is pretty easy ;) ) and you need to make that switch only a few times.
if($config->evironment() == "debug") {
$logger = new DisplayEverythingLogger();
} else {
$logger = new OnlyLogErrorsToTextfileLogger();
}
$neededModel = new ClassThatDoesActualWork($logger);
$controllerOrSomething = new ControllerOrWhatEveryDoesYourWorkflow($neededModel);
$controllerOrSomething->dispatch();
and so on. You can reduce the amount of global state step by step until you get rid of the define and only have a configuration setting.
When it comes to testing the class that does work you now won ether way because the logging is injectable and you can pass in a mock for testing.
So far for a first idea.. if you think that doesn't work for you maybe provide an example where APPMODE is used
Use phpunit's --bootstrap option. It runs the given file prior to any tests being done.

Symfony: multiple applications in one test

I am writings functional tests for my projects' backend application modules. To test some features, I need to simulate user actions from frontend application. So, I created 2
sfTestFunctional instances:
$frontendBrowser = new frontendTestFunctional();
$backendBrowser = new backendTestFunctional();
These classes basically inherit from sfTestFunctional. Now, as we know, each application in symfony has its own context instance, so we have to switch to it first:
sfContext::switchTo('frontend');
//this works fine
$frontendBrowser->
get('/home');
sfContext::switchTo('backend');
//the following fails with a misirable error: Cannot redeclare class homeComponents
$backendBrowser->
get('/home');
So, the problem is that both pages have their own classes with the same name (homeComponents) that obviously cannot be included in one script. Is there anything I can do about it?
P.S the question is not necessarily Symfony related, so I also tag it 'php'
update: Seems like the only solution is to rename all the modules in one application, so that action and components classes have different names. But this is very involved.
You might consider breaking the test up into two distinct tests.
The first one runs the frontend application and then checks to make sure that the state of the database, session, etc. is correct.
The second test will set up the test environment to mimic the results of the frontend application completing successfully and then running the backend application and checking the result.
This will also help down the road in case regressions surface in either application; if you were to keep the tests for both applications consolidated, you would get less information from a test failure. With a separate test for each application, you will be able to find the regression more easily (since you will at least know which application is affected).

My logger keeps getting destructed while I am trying to log errors and exceptions

I am just now switching back to PHP after enterprise open-source Java development for three years. Now I am tasked with updating our platform for better logging.
I now understand better how the PHP object lifecycle regarding when objects are garbage collected and have trapped my problem. I am trying to invoke the logger after its already been destructed, when a fatal error occurs. My question is, how do I fix this? How can I stop an object from being destroyed until the end of the request?
Ideally I would like to keep this logger around in memory like I would in Java but is that even possible with PHP? Is there anything shared between two different threads or requests?
With PHP, each request is processed by a different process -- which means you quite cannot keep some object arround between requests (you could serialize it and store it in a file or something like that, and un-serialize it when another requests comes ; but that's not really the way things are generally done)
This means each time your PHP script receives a request, you have to re-instanciate your logger.
Now, if you want to use your logger from several different classes/methods/functions in the same script, you have to know that variables are not global "by default" in PHP : a variable declared outside of a function is not accessible from inside a function, unless you said so using the global keyword.
In this kind of situation, when you want one and only one instance of a specific class (your logger) available from anywhere in your application, people often use the Singleton Design Pattern.
It'll allow to use something like this :
My_Logger_Class::log('blah');
From any portion of your code, and the log method will deal with :
instanciating the class if there was not already one existing instance
the actual logging
And, yes, the first time this method is called for one HTTP request, it'll have to re-open the log file (if logging to a file).
As a sidenote : there are already some existing great logging components, like PEAR::Log or Zend_Log.
Maybe using one of those might help you spend less time re-inventing some wheel ?

Categories