I want use route groups for FastRoute in Expressive.
Like sample:
$router = $app->getContainer()->get(FastRoute\RouteCollector::class);
$router->get('/', App\Action\HomePageAction::class);
$router->addGroup('/pages', function (FastRoute\RouteCollector $router) {
$router->get('', App\Action\PagesIndexAction::class);
$router->get('/add', App\Action\PagesAddAction::class);
$router->get('/edit/{id}', App\Action\PageEditActionFactory::class);
$router->post('/edit/{id}', App\Action\PageEditActionFactory::class);
$router->get('/another/{section}[/{subsection}]', PagesAnotherActionFactory::class);
});
I created factories as written in docs (https://docs.zendframework.com/zend-expressive/features/router/fast-route/#advanced-configuration)
And register their in router.global.php:
// ...
'factories' => [
FastRoute\RouteCollector::class => App\Container\FastRouteCollectorFactory::class,
FastRoute\DispatcherFactory::class => App\Container\FastRouteDispatcherFactory::class,
Zend\Expressive\Router\RouterInterface::class => App\Container\RouterFactory::class,
],
// ...
Now I can not figure out where to write the configuration and how to activate it.
Can this be done in the file config/router.php?
Help me, please.
you can put them in config.router.php as long as the file gets merged with the rest of your config.
'dependencies' => [
//..
'invokables' => [
/* ... */
// Comment out or remove the following line:
// Zend\Expressive\Router\RouterInterface::class => Zend\Expressive\Router\FastRouteRouter::class,
/* ... */
],
'factories' => [
/* ... */
// Add this line; the specified factory now creates the router instance:
FastRoute\RouteCollector::class => App\Container\FastRouteCollectorFactory::class,
FastRoute\DispatcherFactory::class => App\Container\FastRouteDispatcherFactory::class,
// Zend\Expressive\Router\RouterInterface::class => Zend\Expressive\Router\FastRouteRouterFactory::class, // replaced by following line
Zend\Expressive\Router\RouterInterface::class => App\Container\RouterFactory::class,
/* ... */
],
],
Note the dependencies key and that your own RouterFactory replaces the FastRouteRouterFactory because it shares the same config key.
This is not supported and I am not sure if this can be implemented in FastRoute.
You can check the thread "Zend router - child routes"
Related
I'm using the Log:: facade a lot and have a helper class called LogHelper which provide me with a static method LogHelper::context() which include many key values I need to track the requests. But having to type it every time for each usage make it error prune and fill not so efficient.
I'm looking for a way to inject the values by default, and allow me to overwrite them if needed specifically.
At the moment this is how I use it,
Log::debug('Request Started', LogHelper::context());
what I'm looking for is to inject the context by default
Log::debug('Request Started');
and have the option to overwrite it, if need it:
Log::debug('Request Started', ['more' => 'context'] + LogHelper::context());
PS, the LogHelper::context() return a simple key => value array which include some staff i need to debug requests, and the reason it do not use the values directly in the message is because i log to graylog as structured data, and this way i can filter by any key.
I have solved this issue by using the tap functionality and $logger->withContext() (note: the latter was added in Laravel 8.49).
You want to create a new class which contains your context logic. I've created an extra Logging folder in app/ in which my logging customizations sit.
app/Logging/WithAuthContext.php:
<?php
namespace App\Logging;
use Illuminate\Log\Logger;
class WithAuthContext
{
public function __invoke(Logger $logger)
{
$logger->withContext([
'ip' => request()?->ip(),
'ua' => request()?->userAgent(),
]);
}
}
Depending on which logging channel(s) you use, you will have to add the class to each one you want to add context to. So in app/config/logging.php:
<?php
use App\Logging\WithAuthContext;
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
// ...
'channels' => [
// ...
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'tap' => [WithAuthContext::class],
],
// ...
],
];
There is a way, but it is not pretty. You can create a custom monolog logger driver. The process is described at https://laravel.com/docs/8.x/logging#creating-monolog-handler-channels.
Here's a possible implementation:
class ContextEnrichingLogger extends \Monolog\Handler\AbstractHandler {
private $logger;
public function __construct($level = Monolog\Logger::DEBUG, bool $bubble = true, $underlyingLogger = 'single') {
$this->logger = Log::driver($underlyingLogger);
}
public function handle(array $record) {
$record['context'] += LogHelper::context();
return $this->logger->handle($record);
}
}
Then register this as a custom logger in your config/logging.php:
return [
'default' => 'enriched',
//...
'channels' => [
// ...
'enriched' => [
'driver' => 'monolog',
'handler' => ContextEnrichingLogger::class,
'level' => env('APP_LOG_LEVEL', 'debug'),
"with" => [
"underlyingLogger" => env('LOG_CHANNEL', 'single')
]
]
]
];
I haven't tested this particular one but this is how I've defined other custom loggers.
Note, this is probably also achievable via a custom formatter though I think it's probably the same trouble.
I'm working on a laravel plugin/package.
We want to define our own loggers outside of the main project configuration (config/logger.php)
I have tried the following in the ServiceProvider register() function.
Based on the MonoLogger test code.
$partLogger = new Logger('vendor_part-log');
$partLogHandler = new StreamHandler(storage_path('logs/vendor/part-log.log', Logger::DEBUG));
$partLogger->pushHandler($partLogHandler);
// MonoLog Registry
Registry::addLogger($partLogger, 'vendor_part-log');
Sadly this doesn't work inside Laravel.
I also can't get the other existing loggers from Registry::
So the problem is that the new channel won't register.
Is there a different Registry in use inside Laravel or do I need an entirely different solution to achieve this?
While we'd still like a more automated solution, we've compromised and are using a static function as source of the configuration.
In config/logging.php:
At the top replace return [... with $config = [....
And at the bottom add the following lines:
$config['channels'] = array_merge(
$config['channels'],
\FooVendor\Bar\Classes\Logs::getLogs(),
);
return $config;
And create the mentioned class with the following function:
/**
* Configs to be merged in config/logging.php
* #return array
*/
public static function getLogs()
{
return [
'foo_partlogger' => [
'driver' => 'single',
'path' => storage_path('logs/foo/partlogger.log'),
'level' => 'debug',
],
'foo_second_partlogger' => [
'driver' => 'single',
'path' => storage_path('logs/foo/second_partlogger.log'),
'level' => 'debug',
],
];
}
I have an existing Yii2 application and have been trying to implement a REST API as an additional module (Maybe a module isn't the correct way to go about this?) But I'm having some trouble configuring the route structures. It doesn't quite work and doesn't follow the expected results, based of the following guide.
I've built an additional module that looks like this:
module
api
controllers
UserController.php
Module.php
UserController.php
<?php
namespace app\modules\api\controllers;
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
}
Module.php
<?php
namespace app\modules\api;
/**
* onco module definition class
*/
class Module extends \yii\base\Module
{
public $defaultController = 'user';
/**
* #inheritdoc
*/
public $controllerNamespace = 'app\modules\api\controllers';
/**
* #inheritdoc
*/
public function init()
{
parent::init();
// custom initialization code goes here
}
}
In my config file I have the added following:
'request' => [
...
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
],
...
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'enableStrictParsing' => false, // have tried as true also
'rules' => [
...
['class' => 'yii\rest\UrlRule', 'controller' => '\app\modules\api\controllers\user'],
],
],
...
'modules' => [
...
'api' => [ // module for RESTful API
'class' => 'app\modules\api\Module',
],
]
When I run the following urls through postman I get the following:
http://localhost/site1/web/api/users -> 404
http://localhost/site1/web/api/users/index -> 404
http://localhost/site1/web/api/user/index -> returns json repsonse
http://localhost/site1/web/api/user/2 -> 404
I'm unsure as to why the predicted routes of noted in the docs as:
Trying it Out With the above minimal amount of effort, you have
already finished your task of creating the RESTful APIs for accessing
the user data. The APIs you have created include:
GET /users: list all users page by page;
HEAD /users: show the overview information of user listing;
POST /users: create a new user;
GET /users/123: return the details of the user 123;
HEAD /users/123: show the overview information of user 123;
PATCH /users/123 and PUT /users/123: update the user 123;
DELETE /users/123: delete the user 123;
OPTIONS /users: show the supported verbs regarding endpoint /users;
OPTIONS /users/123: show the supported verbs regarding endpoint /users/123
What have I likely done wrong in this setup? Is there a better way to implement an API into an existing website, whilst maintaining DRY practices?
try this:
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['api/user'],
]
]
],
...
'modules' => [
...
'api' => [
'basePath' => '#app/modules/api',
'class' => 'app\modules\api\Module',
],
]
Also be sure to implement prettyUrl's related server server configs.
I'm using the Zend Logger with following configuration in the module_config.php:
'log' => [
\Base\Log\ConsoleLoggerAwareInterface::class => [
'writers' => [
'standard-output' => [
'name' => 'stream',
'options' => [
'stream' => 'php://output',
],
],
],
],
]
But as it is I can't influence the log to filter or suppress messages.
The goal is to improve the logging to be smarter.
I think I need to add a filter to the writer but I can't find an example how to do that in the module_config.php.
Is there also a way to call a script (e.g. from within a cron) by using a verbosity level?
I hope you know what I'm trying to achive.
Edit:
I have this example in my code:
$this->consoleLogger->emerg('EMERG');
$this->consoleLogger->alert('ALERT');
$this->consoleLogger->crit('CRIT');
$this->consoleLogger->err('ERR');
$this->consoleLogger->warn('WARN');
$this->consoleLogger->notice('NOTICE');
$this->consoleLogger->info('INFO');
$this->consoleLogger->debug('DEBUG');
Should it then not output the filtered ones?
Q: How to filter a Log Writer to a specific error level?
Add a filters key to the particular writer's configuration. Here's direct instantiation to remove any configuration peculiarities: this outputs only "WARN" and "EMERG" messages:
$config = [
'writers' => [
'standard-output' => [
'name' => 'stream',
'options' => [
'stream' => 'php://output',
'filters' => \Zend\Log\Logger::WARN,
],
],
],
];
$logger = new \Zend\Log\Logger($config);
$logger->emerg('EMERG');
$logger->warn('WARN');
$logger->debug('DEBUG');
Adding the filters configuration to your modules_config.php should have a similar effect. If not, check your zend-log version (with eg composer show) and advise.
Q: How to change error level filter with the -v command line parameter?
AFAIK, there is no automatic way to bind the standard verbose flag (-v) with a particular logging level. So you'll have to write your own filter. One thing that's neat to know is that the filters key can take:
an int (as done above, which translates to the built-in log level);
a string (corresponding to a class name implementing \Zend\Log\Filter\FilterInterface);
an object (instance of \Zend\Log\Filter\FilterInterface);
or an array of these.
You can use this flexibility to solve your need of binding a command line parameter to a log value. Here is a custom class that shows emergency events by default, but for every v on the command line increases the shown priority:
class CliLoggingFilter implements \Zend\Log\Filter\FilterInterface
{
public function __construct()
{
$this->level = \Zend\Log\Logger::EMERG;
if (array_key_exists('v', $args = getopt('v'))) {
$this->level += count($args['v']);
}
}
public function filter(array $event)
{
return ($event['priority'] <= $this->level);
}
}
You'd then have a configuration like: 'filters' => CliLoggingFilter::class.
$ php public/index.php
2016-07-25T10:57:28-04:00 EMERG (0): EMERG
$ php public/index.php -vvvv
2016-07-25T10:57:32-04:00 EMERG (0): EMERG
2016-07-25T10:57:32-04:00 WARN (4): WARN
$ php public/index.php -vvvvvvv
2016-07-25T10:57:34-04:00 EMERG (0): EMERG
2016-07-25T10:57:34-04:00 WARN (4): WARN
2016-07-25T10:57:34-04:00 DEBUG (7): DEBUG
Q: How to change all routes to use -v?
AFAIK, there is no way to specify a global command line parameter. You need to either (a) update all your console routes to accept the argument or (b) pass the log level a different way.
Updating all your routes isn't terribly hard. You can define a variable that holds the value and then include that in the configuration, like so:
$globalConsoleRouteParams = '[--verbose|-v]';
return [ 'console' => 'router' => 'routes' => [
'foo' => [ 'options' => [ 'route' => "foo $globalConsoleRouteParams ..." ] ],
'bar' => [ 'options' => [ 'route' => "bar $globalConsoleRouteParams ..." ] ],
// ...
]];
Alternatively, you could use say environment variables to pass your desired log level, plus perhaps any additional configuration you might desire. Modifying our earlier example:
class CliLoggingFilter implements \Zend\Log\Filter\FilterInterface
{
public function __construct()
{
$this->level = \Zend\Log\Logger::EMERG;
if (false !== ($level = getenv('LOG_LEVEL'))) {
$this->level = $level;
}
}
// ...
}
Then it may be invoked like
$ LOG_LEVEL=7 php index.php foo
Working with Yii 2.0.4, I'm trying to use urlManager Rule to preload an object based on a given ID in the URL.
config/web.php
'components' => [
'urlManager' => [
[
'pattern' => 'view/<id:\d+>',
'route' => 'site/view',
'defaults' => ['client' => Client::findOne($id)],
],
[
'pattern' => 'update/<id:\d+>',
'route' => 'site/update',
'defaults' => ['client' => Client::findOne($id)],
],
]
]
If this works, it will not be necessary to manually find and object each time, for some CRUD actions:
class SiteController extends Controller {
public function actionView() {
// Using the $client from the urlManager Rule
// Instead of using $client = Client::findOne($id);
return $this->render('view', ['client' => $client]);
}
public function actionUpdate() {
// Using $client from urlManager Rule
// Instead of using $client = Client::findOne($id);
if ($client->load(Yii::$app->request->post()) && $client->save()) {
return $this->redirect(['view', 'id' => $client->id]);
} else {
return $this->render('edit', ['client' => $client]);
}
}
}
NOTE: The above snippets are not working. They're the idea of what I want to get
Is it possible? Is there any way to achieve this?
If you look closer: nothing actually changes. You still call Client::findOne($id); but now doing it in an unexpected and inappropriate place, and if you look at the comment about default parameter it says:
array the default GET parameters (name => value) that this rule provides.
When this rule is used to parse the incoming request, the values declared in this property will be injected into $_GET.
default parameter is needed when you want to specify some $_GET parameters for your rule. E.g.
[
'pattern' => '/',
'route' => 'article/view',
'defaults' => ['id' => 1],
]
Here we specify article with id = 1 as default article when you open main page of site e.g. http://example.com/ will be handled as http://example.com/article/view?id=1
I can suggest to you add property clientModel in to your controller and then in beforeAction() method check if its update or view action then set
$this->clientModel = Client::findOne($id);
and in your action:
return $this->render('view', ['client' => $this->clientModel]);