I would like to separate Tests and Data Providers. Using PHP 8 attributes, I cannot get the following test to run when referencing an external Data Provider:
#[Test]
#[DataProviderExternal(RouterDataProvider::class, 'registerGetRouteData')]
public function itRegistersGetRoute(Route $route, array $expectedResult)
{
$this->router->get($route);
$this->assertEquals($expectedResult, $this->router->getRoutes());
}
My data provider class:
class RouterDataProvider
{
public static function registerGetRouteData(): array
{
return [
$route = new Route('/', ['IndexController', 'index']),
[
'GET' => [
'/' => $route,
],
'POST' => []
]
];
}
}
How could I get this test to run with the desired provider method?
By running PHPUnit with the following flags, I was able to see exactly what my issue was:
./vendor/bin/phpunit --display-deprecations --display-warnings --diplay-errors --display-notices
The data set was invalid. Changing the return to yield and updating the return type for the registerGetRouteData method from array to \Generator resolved this.
I was running phpunit with the --testdox flag, so I'm not sure if this is what stopped me seeing any errors initially and assume the test was being skipped.
I'm writting tests and task for a new app for an application and I need to access to the "app config parameters" of this new app defined in /apps/mynewapp/config/app.yml. I thought it will be as easy as written in the Symfony doc, but it seems I've forgotten something.
When I get my config: $actions = sfConfig::get("app_actions") it is NULL. I thought the config name is wrong, but when I get all the config parameters available with sfConfig::getAll(), I don't have my app config parameters.
Maybe I've forgotten to include my /apps/mynewapp/config/app.yml?
There is the content of my file:
all:
.array:
actions:
bind_destroy: BindDestroyAction
bind_subscribe: BindSubscriptionAction
messages:
bind_destroy: BindDestroyMessage
bind_subscribe: BindSubscriptionMessage
And there is how I try to access to my parameters in /apps/mynewapp/lib/GRM/GRMSender.class.php:
class GRMSender
{
private $actionClassNames;
private $messageClassNames;
public function __construct()
{
$this->actionClassNames = sfConfig::get("app_actions");
$this->messageClassNames = sfConfig::get("app_messages");
}
}
The class has already been autoloaded and I'm able to instantiate the class in my unit test scripts.
Thank you for your help.
EDIT
The problem is about my tests (in /test/unit) and my tasks (in /lib/task). I have to use what I did in my application "mynewapp". I did some things :
For the tasks, I defined the application in my task options :
class mynewappActionTask extends sfBaseTask
{
protected function configure()
{
// Do some configuration...
try {
$this->addOptions(array(
new sfCommandOption(
'application',
"app",
sfCommandOption::PARAMETER_REQUIRED,
'The application name',
"mynewapp" // There
),
));
} catch (sfCommandException $e) {}
}
}
For the tests, I wrote a file which loads my mynewapp config. IMHO it's a hack and there is a better way to do it :
$configMynewapp = ProjectConfiguration::getApplicationConfiguration("mynewapp", sfConfig::get("sf_environment"), true);
There must be better ways to get mynewapp config parameters in tasks and in tests. In mynewapp files (controller, lib, etc.) it's ok.
Try to do this:
/apps/mynewapp/config/app.yml
all:
actions:
bind_destroy: BindDestroyAction
bind_subscribe: BindSubscriptionAction
messages:
bind_destroy: BindDestroyMessage
bind_subscribe: BindSubscriptionMessage
Then you can get:
$actions = sfConfig::get('app_actions');
It will return:
$actions => array(
'bind_destroy' => 'BindDestroyAction',
'bind_subscribe' => 'BindSubscriptionAction'
)
Anyway, you can access one of them directly:
$action = sfConfig::get('app_actions_bind_destroy')
$action => 'BindDestroyAction'
I am trying to store some values on cache the first time I load a page. This is the code I am using:
$cached_items = [
'main_nav' => $main_nav,
'sub_nav' => $sub_nav,
'footer_nav' => $footer_nav,
'view_as' => $view_as,
];
$redisConnection = new Client('tcp://redis:6379');
$cache = new RedisAdapter($redisConnection);
$menu = $cache->getItem('mmi_menus');
if ($menu->isHit()) {
return $menu->get();
} else {
$menu->set($cached_items);
$cache->save($menu);
}
This caching is being done from a non Symfony controller - let's say it's a standalone file.
First problem with the code above,
the else condition is reach out all the time and I think it should not be since values are stored. (check here)
Second problem, having this function in a Symfony controller:
public function GenerateMenuItemsAction()
{
$redisConnection = new Client('tcp://redis:6379');
$cache = new RedisAdapter($redisConnection);
$menu = $cache->getItem('mmi_menus');
if ($menu->isHit()) {
return $this->render(
'CommonBundle:Layout:menu.html.twig',
['menu' => $menu->get()]
);
}
}
$menu->isHit() is null so all the time I am getting this exception from Symfony:
An exception has been thrown during the rendering of a template ("The
controller must return a response (null given). Did you forget to add
a return statement somewhere in your controller?").
Update
I am not using any TTL afaik maybe somehow a default one is setup but this is how the section looks like on the config.yml:
framework:
cache:
app: cache.adapter.redis
default_redis_provider: "redis://%redis_host%"
pools:
cache.pool1:
public: true
What I am missing here? Any ideas?
my config.yml looks like that:
framework:
cache:
system: cache.adapter.apcu
default_redis_provider: redis://%redis_password%#%redis_host%:%redis_port%
pools:
redis_pool:
adapter: cache.adapter.redis
public: true
default_lifetime: 0
provider: cache.default_redis_provider
So I can easily (in my Controller) do something like:
$this->get('redis_pool')->getItem('myitem');
Or you can inject 'redis_pool' as an argument to a Service.
I don't need any 'new' or extra Connection information/configuration - anything is done in config.yml and available as a Service across the application.
So I have a Laravel controller:
class YeahMyController extends BaseController {
public function getSomething() {
Console::info('mymessage'); // <-- what do I put here?
return 'yeahoutputthistotheresponse';
}
}
Currently, I'm running the application using artisan (which runs PHP's built-in development web server under the hood):
php artisan serve
I would like to log console messages to the STDOUT pipe for the artisan process.
Aha!
This can be done with the following PHP function:
error_log('Some message here.');
Found the answer here: Print something in PHP built-in web server
The question relates to serving via artisan and so Jrop's answer is ideal in that case. I.e, error_log logging to the apache log.
However, if your serving via a standard web server then simply use the Laravel specific logging functions:
\Log::info('This is some useful information.');
\Log::warning('Something could be going wrong.');
\Log::error('Something is really going wrong.');
Or with current version of Laravel, like this:
info('This is some useful information.');
This logs to Laravel's log file located at /laravel/storage/logs/laravel-<date>.log (laravel 5.0). Monitor the log - linux/osx: tail -f /laravel/storage/logs/laravel-<date>.log
Laravel 5.0 http://laravel.com/docs/5.0/errors
Laravel 4.2: http://laravel.com/docs/4.2/errors
I haven't tried this myself, but a quick dig through the library suggests you can do this:
$output = new Symfony\Component\Console\Output\ConsoleOutput();
$output->writeln("<info>my message</info>");
I couldn't find a shortcut for this, so you would probably want to create a facade to avoid duplication.
It's very simple.
You can call it from anywhere in APP.
$out = new \Symfony\Component\Console\Output\ConsoleOutput();
$out->writeln("Hello from Terminal");
In Laravel 6 there is a channel called 'stderr'. See config/logging.php:
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
In your controller:
use Illuminate\Support\Facades\Log;
Log::channel('stderr')->info('Something happened!');
For better explain Dave Morrissey's answer I have made these steps for wrap with Console Output class in a laravel facade.
1) Create a Facade in your prefer folder (in my case app\Facades):
class ConsoleOutput extends Facade {
protected static function getFacadeAccessor() {
return 'consoleOutput';
}
}
2) Register a new Service Provider in app\Providers as follow:
class ConsoleOutputServiceProvider extends ServiceProvider
{
public function register(){
App::bind('consoleOutput', function(){
return new \Symfony\Component\Console\Output\ConsoleOutput();
});
}
}
3) Add all this stuffs in config\app.php file, registering the provider and alias.
'providers' => [
//other providers
App\Providers\ConsoleOutputServiceProvider::class
],
'aliases' => [
//other aliases
'ConsoleOutput' => App\Facades\ConsoleOutput::class,
],
That's it, now in any place of your Laravel application, just call your method in this way:
ConsoleOutput::writeln('hello');
Hope this help you.
If you want the fancy command IO from Laravel (like styling, asking and table) then I created this class below
Instructions
I have not fully verified everywhere that it is THE cleanest solution etc, but it works nice (but I only tested it from within a unit test case, under Laravel 5.5).
So most probably you can use it however you like:
$cmd = new ConsoleCommand;
$cmd->error("Aw snap!");
$cmd->table($headers, $rows);
$answer = $cmd->ask("Tell me, what do you need?");
//even Symfony's progress bar
$cmd->outputStyle->progressStart(5); //set n = 100% (here 100% is 5 steps)
$cmd->outputStyle->progressAdvance(); //you can call it n times
$cmd->outputStyle->progressFinish(); //set to 100%
Or course you can also wrap in your own facade, or some static singleton etc, or anyway you wish.
The class itself
class ConsoleCommand extends \Illuminate\Console\Command
{
protected $name = 'NONEXISTENT';
protected $hidden = true;
public $outputSymfony;
public $outputStyle;
public function __construct($argInput = null)
{
parent::__construct();
$this->input = new \Symfony\Component\Console\Input\StringInput($argInput);
$this->outputSymfony = new \Symfony\Component\Console\Output\ConsoleOutput();
$this->outputStyle = new \Illuminate\Console\OutputStyle($this->input, $this->outputSymfony);
$this->output = $this->outputStyle;
}
}
I wanted my logging information to be sent to stdout because it's easy to tell Amazon's Container service (ECS) to collect stdout and send it to CloudWatch Logs. So to get this working, I added a new stdout entry to my config/logging.php file like so:
'stdout' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stdout',
],
'level' => 'info',
],
Then I simply added 'stdout' as one of the channels in the stack log channel:
'default' => env('LOG_CHANNEL', 'stack'),
'stack' => [
'driver' => 'stack',
'channels' => ['stdout', 'daily'],
],
This way, I still get logs in a file for local development (or even on the instance if you can access it), but more importantly they get sent to the stdout which is saved in CloudWatch Logs.
If you want to log to STDOUT you can use any of the ways Laravel provides; for example (from wired00's answer):
Log::info('This is some useful information.');
The STDOUT magic can be done with the following (you are setting the file where info messages go):
Log::useFiles('php://stdout', 'info');
Word of caution: this is strictly for debugging. Do no use anything in production you don't fully understand.
Bit late to this...I'm surprised that no one mentioned Symfony's VarDumper component that Laravel includes, in part, for its dd() (and lesser-known, dump()) utility functions.
$dumpMe = new App\User([ 'name' => 'Cy Rossignol' ]);
(new Symfony\Component\VarDumper\Dumper\CliDumper())->dump(
(new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($dumpMe)
);
There's a bit more code needed, but, in return, we get nice formatted, readable output in the console—especially useful for debugging complex objects or arrays:
App\User {#17
#attributes: array:1 [
"name" => "Cy Rossignol"
]
#fillable: array:3 [
0 => "name"
1 => "email"
2 => "password"
]
#guarded: array:1 [
0 => "*"
]
#primaryKey: "id"
#casts: []
#dates: []
#relations: []
... etc ...
}
To take this a step further, we can even colorize the output! Add this helper function to the project to save some typing:
function toConsole($var)
{
$dumper = new Symfony\Component\VarDumper\Dumper\CliDumper();
$dumper->setColors(true);
$dumper->dump((new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($var));
}
If we're running the app behind a full webserver (like Apache or Nginx—not artisan serve), we can modify this function slightly to send the dumper's prettified output to the log (typically storage/logs/laravel.log):
function toLog($var)
{
$lines = [ 'Dump:' ];
$dumper = new Symfony\Component\VarDumper\Dumper\CliDumper();
$dumper->setColors(true);
$dumper->setOutput(function ($line) use (&$lines) {
$lines[] = $line;
});
$dumper->dump((new Symfony\Component\VarDumper\Cloner\VarCloner())->cloneVar($var));
Log::debug(implode(PHP_EOL, $lines));
}
...and, of course, watch the log using:
$ tail -f storage/logs/laravel.log
PHP's error_log() works fine for quick, one-off inspection of simple values, but the functions shown above take the hard work out of debugging some of Laravel's more complicated classes.
Here's another way to go about it:
$stdout = fopen('php://stdout', 'w');
fwrite($stdout, 'Hello, World!' . PHP_EOL);
The PHP_EOL adds new line.
In command class
before class
use Symfony\Component\Console\Output\ConsoleOutput;
Inside the class methods
$output = new ConsoleOutput();
$output->writeln('my text that appears in command line ');
You can use echo and prefix "\033", simple:
Artisan::command('mycommand', function () {
echo "\033======== Start ========\n";
});
And change color text:
if (App::environment() === 'production') {
echo "\033[0;33m======== WARNING ========\033[0m\n";
}
From Larave 6.0+
$this->info('This will appear in console');
$this->error('This error will appear in console');
$this->line('This line will appear in console);
Documentation https://laravel.com/docs/6.x/artisan#writing-output
In Symfony2, there is some Twig module disabled by default. One of them is the debug extension, that adds {% debug %} tag (useful on a development environment).
To enable it, nothing really difficult, you add this service to your configuration :
debug.twig.extension:
class: Twig_Extensions_Extension_Debug
tags:
- { name: 'twig.extension' }
But how to enable the {% sandbox %} tag?
My issue is that the extension's constructor takes security policies :
public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false)
{
$this->policy = $policy;
$this->sandboxedGlobally = $sandboxed;
}
By reading the twig documentation, I seen the way to do it natively (without Symfony2) :
$tags = array('if');
$filters = array('upper');
$methods = array(
'Article' => array('getTitle', 'getBody'),
);
$properties = array(
'Article' => array('title', 'body'),
);
$functions = array('range');
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
$sandbox = new Twig_Extension_Sandbox($policy);
$twig->addExtension($sandbox);
I can do something like that inside a service just before using the sandbox, but that's not as clear as the dependancy injection we're used to.
Is there a better / proper way to enable the twig's sandbox extension in Symfony2?
Why not create a private service of the security policy:
parameters:
twig.sandbox.tags:
- if
twig.sandbox.filters:
- upper
twig.sandbox.methods:
Article: [getTitle, getBody]
twig.sandbox.properties:
Article: [title, body]
twig.sandbox.functions:
- range
twig.sandbox.policy:
class: Twig_Sandbox_SecurityPolicy
arguments:
- %twig.sandbox.tags%
- %twig.sandbox.filters%
- %twig.sandbox.methods%
- %twig.sandbox.properties%
- %twig.sandbox.functions%
public: false
You can then inject this service into the twig.sandbox.extension service:
twig.sandbox.extension:
class: Twig_Extension_Sandbox
arguments:
- #twig.sandbox.policy
tags:
- { name: twig.extension }
Done. Marking the twig.sandbox.policy private ensures it won't be accessible using the container (it can still be injected into other services, but I think that's not an issue).
Disclaimer: I haven't tested this and it probably needs some tweaking before it actually works so don't copy paste!