Yii2 Set component value dynamically - php

I am using this library https://github.com/yiioverflow/yii2-imap
'imap' => [
'class' => 'roopz\imap\Imap',
'connection' => [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => 'abc#gmail.com',//set this value dynamically
'imapPassword' => '123',//set this value dynamically
'serverEncoding' => 'encoding', // utf-8 default.
'attachmentsDir' => 'uploads/attachments'
],
],
//Create imap class object
$mailbox = yii::$app->imap->connection;
// Read all messaged into an array:
$mailsIds = $mailbox->searchMailbox('ALL');
in controller. want to set this value with help of session in yii2.

I found alternative php-imap library here [PHP IMAP][1]
[1]: https://github.com/barbushin/php-imap. which can be easily install with composer in yii2. and can pass a dynamic value
$mailbox = new PhpImap\Mailbox('{imap.gmail.com:993/imap/ssl}INBOX', 'some#gmail.com', '*********', __DIR__);
// Read all messaged into an array:
$mailsIds = $mailbox->searchMailbox('ALL');

Passing dynamic values that depends on Yii::$app right in config will not work because you are referring to application, and it's constructed using that config (components is a part of application too) and doesn't exist at this moment. It needs to be set later, when application is initialized and Yii::$app object exists. For example, in controller or some custom component.
Using library yiioverflow/yii2-imap it can be done like this:
use Yii;
...
Yii:$app->imap->connection = [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => $imapLogin, // Set this value dynamically
'imapPassword' => $imapPassword, // Set this value dynamically
'serverEncoding' => 'encoding', // utf-8 default
'attachmentsDir' => 'uploads/attachments',
],
Then you need to call:
Yii:$app->imap->createConnection();
to properly update the config.
There is no way to set separately imapLogin or imapPassword because of the way this component is written (these properties are protected and filled from connection array). If you want to do that, you have to subclass this component and write these setters by yourself and replace used component with your custom one.
More info about application components can be found in official docs.

You can use own "service layer" (that works similar to global Yii::$app). Just create \yii\di\ServiceLocator instance:
// Init service layer.
$services = new ServiceLocator();
$services->setComponents([
'imap' => [
'class' => 'roopz\imap\Imap',
'connection' => [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => 'abc#gmail.com',//set this value dynamically
'imapPassword' => '123',//set this value dynamically
'serverEncoding' => 'encoding', // utf-8 default.
'attachmentsDir' => 'uploads/attachments'
],
],
// ...
]);
// Retrieving the defined components:
$imap = $services->get('imap');
$imap = $services->imap;
If imap component will use only your controller, you can store $services as protected/private property of this controller.
Described approach works completely similarly to usual components in Yii::$app, because application class is also ServiceLocator.
Alternatively, you can define or redefine your component using imap-instance:
// Preparing components
$defaultImapConfig = [
'connection' => [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => null,
'imapPassword' => null,
'serverEncoding' => 'encoding', // utf-8 default.
'attachmentsDir' => 'uploads/attachments'
],
];
// Init service layer.
$services = new ServiceLocator();
// Define component
$imap = new \roopz\imap\Imap(ArrayHelper::merge($defaultImapConfig, ['connection' => [
'imapLogin' => 'abc#gmail.com',
'imapPassword' => '123',
]]));
$services->set('imap', $imap);
// Redefine component with new config
$imap = new \roopz\imap\Imap(ArrayHelper::merge($defaultImapConfig, ['connection' => [
'imapLogin' => 'dfg#gmail.com',
'imapPassword' => '456',
]]));
$services->set('imap', $imap); // If component definition with the same name already exist, it will be ovewritten.
Of course, you can use similar way to redefine global components in Yii::$app, but it is bad practice. I recommend to create separate (local) service layer, that can be accessed from your controllers, models etc.
More details about work with service locators you can found here.

Related

Using Native Enum in Api-Platform / OpenApi

I am learning OpenApi/Swagger API with Api-Platform. I created a new endpoint, that accepts values of an enum as a parameter:
#[ApiResource(
itemOperations: [
'get_by_name' => [
'openapi_context' => [
....
'parameters' => [
[
'in' => 'header',
'name' => 'X-Server-Region',
'schema' => [
'type' => 'string',
'enum' => ['server1', 'server2'],
'example' => 'server1',
],
'description' => 'Server to select',
'required' => true
],
...
)]
However, this is a rather common param and values can be updated frequently (as more servers are added), I'd like to use some kind of template.
So I tried:
<?php
namespace App\Enum;
enum Server: string
{
case SERVER1 = 'server1';
case SERVER2 = 'server2';
...
}
with
'enum' => [...Server::cases()],
or
'enum' => [Server::class],
and many other forms of that, to no avail.
I tried to understand the concept of components, but could not find a way to use them in Symfony/Api Platform.
How could I reuse an enum at different endpoints?
Enums being fairly new to PHP, they are not yet directly supported by Api-Platform.
Support will come, but for the time being you'll have to explicitly list each of the cases manually on the configuration.
While you could also store the list of 'cases' in a constant in a class (you could even do it in the enum itself):
enum Server : string {
const CASES = ['server1', 'server2']
case SERVER1 = 'server1';
case SERVER2 = 'server2';
}
And then use that constant directly in annotations or attributes:
parameters' => [
[
'in' => 'header',
'name' => 'X-Server-Region',
'schema' => [
'type' => 'string',
'enum' => Server::CASES,
'example' => 'server1',
],
'description' => 'Server to select',
'required' => true
],
... this wouldn't be really using the enum advantages, as you would still need to edit the cases in two places instead of only just the one; and would only be convenient for annotations or attributes. If you used XML or YAML configuration for your API resources, it wouldn't be that great.
To temporarily solve the problem until the Enum support is available, you can create a class with a prototype of your data to reuse it in your API. This way you can reuse the same data set without duplicating it. This will make things easier for you in the future.
And so you create a prototype of your data like this:
class Server
{
public const PROTOTYPE = [ 'select1', 'select2', 'select3'];
}
Import calss and then call it in your annotations like this:
'enum' => Server::PROTOTYPE,

In Laravel can I set a default context for the Log facade

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.

TYPO3 v10 - Getting feUser Object using context API in eID_include

Code that i used and need to update for V10
$this->feUser = EidUtility::initFeUser();
When using the following code (a random) controller, the context gives me the correct login feUser object.
$context = GeneralUtility::makeInstance(Context::class);
$user = $context->getAspect('frontend.user');
DebuggerUtility::var_dump($user);
When using the same code in an eID_include class No userObject is given.
Specificly in the following class
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['FileDumpEID.php']['checkFileAccess']['xxx'] = My\Class\Hooks\FileDumpHook:class
Is there a need of bootstrapping context?
Since the TYPO3\CMS\Frontend\Middleware\EidHandler middleware is executed before the TYPO3\CMS\Frontend\Middleware\FrontendUserAuthenticator middleware in the middlewares order i dont think, that this is possible.
If you need parts of the frontend handling you either can add an own middleware with depend of TYPO3\CMS\Frontend\Middleware\FrontendUserAuthenticator or use an page Object in typoscript.
I had the same problem. You may change the order of Middlewares: https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/RequestHandling/Index.html
I've created a new file RequestMiddlewares.php inside the "Configuration" directory of my extension:
<?php
return [
'frontend' => [
'typo3/cms-frontend/eid' => [
'disabled' => true
],
'typo3/cms-frontend/eid-new' => [
'target' => \TYPO3\CMS\Frontend\Middleware\EidHandler::class,
'after' => [
'typo3/cms-frontend/tsfe',
],
'before' => [
'typo3/cms-frontend/prepare-tsfe-rendering',
]
]
]
];
You have to flush TYPO3 and PHP Cache and check the ordering in "Configuration" backend module (select "HTTP Middlewares (PSR-15)").
With this setup it is possible to get the context property 'frontent.user'
$context = GeneralUtility::makeInstance(Context::class);
if($context->getPropertyFromAspect('frontend.user', 'isLoggedIn')) {

Class properties being overwritten by other instance setters

Pulling my hair out over some weird behaviour.
Essentially I've got a Class that constructs a GuzzleHttp\Client and a custom object of organization data, like so:
// Config set-up
$config = [
'ex1' => [
'api_key' => getenv('EX1_API_KEY'),
'org_id' => getenv('EX1_ORG_ID'),
],
'ex2' => [
'api_key' => getenv('EX2_API_KEY'),
'org_id' => getenv('EX2_ORG_ID'),
],
'ex3' => [
'api_key' => getenv('EX3_API_KEY'),
'org_id' => getenv('EX3_ORG_ID'),
],
];
// Initialize adapters
$ex1 = new Adapter($config['ex1']);
$ex2 = new Adapter($config['ex2']);
$ex3 = new Adapter($config['ex3']);
Which is all a-okay, until they finish their construction with $this->org = $org, which overwrites all of them with the same $org, in this line inside the constructor:
// Construct connected org
$org = Organization::get($this, $args['org_id']);
$this->org = $org;
The frustrating part in all of this is if I assign a property of that org instead of the whole thing, each item comes through unique (e.g. $this->org = $org->name).
I have a feeling this has to do with my Organization class, but I don't know where to start debugging this. Can provide more code/context on request, but the entire code-base is on GitHub.
I structured my abstract Resource class as a Singleton pattern. Because of this, I could not have more than one instance at a time (thus, Singleton) and as such was just changing the properties of the same instance each time.

Setting/retrieving mode in Slim Framework V3

I am new to Slim Framework (PHP). I was going through tutorials and testing some code and tried the following:
$app=new App([
'mode'=>file_get_contents(INC_ROOT . '/mode.php')
]);
echo $app->config('mode');
The output was blank.
It seems the function config() works with V2, however I am using V3. What am I doing wrong?
Slim 3 no longer has a config method. Instead, you must add configuration settings through the dependency injection container:
$app = new \Slim\App([
'settings' => [
'mode' => true
]
]);
$container = $app->getContainer();
echo $container->get('settings')['mode'];
A few things worth noting:
Slim 3 no longer handles managing different versions of configuration settings through a mode setting. So, you can set a value for a setting variable called mode as I've demonstrated here, but it won't actually do anything (i.e., Slim won't use it to determine your environment).
As an alternative, you can check out userfrosting/Config, a library we've been working on that can search multiple directories and different environment configuration files, merging together their contents:
/path/to/config/default.php
return [
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#cleansthetoilet.com'
]
]
];
/path/to/config/production.php
return [
'contacts' => [
'housekeeper' => [
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
];
index.php
$app = new \Slim\App();
$container = $app->getContainer();
// Site config object (separate from Slim settings)
$container['config'] = function ($c) {
// Create and inject new config item
$config = new \UserFrosting\Config\Config();
$config->setPaths([
'/path/to/config'
]);
$config->loadConfigurationFiles('production');
return $config;
};
This will recursively merge in the settings from development.php with those in default.php, updating settings with the same name and scope as necessary:
Running print_r($container['config']); returns:
[
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
]
Notice that the value of contacts.housekeeper.email has been updated to 'alex#istheboss.com', and that the database config info has been merged in. Incidentally, you can also access config settings using the more convenient "dot syntax":
$config = $container->get('config');
echo $config['contacts.housekeeper.email'];
// Easier to type instead of $config['contacts']['housekeeper']['email'];
We recommend injecting this as a separate config service in Slim, rather than using their settings array.
You can combine this with phpdotenv to load settings from your system environment, or any .env files you create:
/path/to/config/production.php
return [
'database' => [
'password' => getenv('DB_PASSWORD')
]
];

Categories