I'd like to assign anonymous functions to arrays in PHP inside of a class, but I keep stumbling over syntax errors.
class Stuff {
private $_preference_defaults = array(
'cookie' => true,
'session' => true,
'database' => true,
'filter' => function($input) { return true; },
'sanitizer' => function($input) { return $input; },
);
};
Will throw an unexpected T_FUNCTION syntax error, and it doesn't matter whether or not the array is static. I got desperate and tried the "old" way of doing it:
class Stuff {
private $_preference_defaults = array(
'cookie' => true,
'session' => true,
'database' => true,
'filter' => create_function('$input', 'return true;'),
'sanitizer' => create_function('$input', 'return $input;'),
);
};
And this led to an unexpected '(', expecting ')' syntax error. However, if I define the functions ahead of time, it does work:
class Stuff {
function _preference_default_filter($input) {
return true;
}
function _preference_default_sanitizer($input) {
return true;
}
private $_preference_defaults = array(
'cookie' => true,
'session' => true,
'database' => true,
'filter' => _preference_default_filter,
'sanitizer' => _preference_default_sanitizer,
);
};
And then I'm able to call those functions within a class method:
function do_stuff($foo) {
return $this->{$this->_preference_defaults['filter']}($foo);
}
Not only is this syntactically sour, in my head it wreaks of poor style and I can imagine this sort of trickery causing headaches in the future.
Am I not able to create an anonymous function at class (static) scope? (I'm thinking maybe because a valid Closure object cannot be created in that context?)
Is there a more... PHP... means to the same end? That is to say, is there a "better" way of assigning trivial default callbacks to array values in class scope? The final snippet I provided gets the job done for sure, but I'd prefer a solution that won't leave me asking the optometrist for a stronger prescription.
Am I not able to create an anonymous function at class (static) scope? (I'm thinking maybe because a valid Closure object cannot be created in that context?)
Yes, because the initial values of class attributes should be known in parsing step, while function call and anonymous function require runtime
Also see here to get the additional details: https://stackoverflow.com/a/9029556/251311
And cannot get your second question :-S
This indeed will not work. During the, lets say, declaration phase you can generally not do much fancy things.
However.. because your properties are just private, and not static.. simply declare everything in the constructor. Problem solved!
This syntax:
'sanitizer' => _preference_default_sanitizer,
Is really bad.. With E_NOTICE on you will actually find it coverts _preference_default_sanitizer to a string first, which is why this may work.
Just set the method name as string.
class Stuff {
private $_preference_defaults = array(
'cookie' => true,
'session' => true,
'database' => true,
'filter' => '_preference_default_filter',
'sanitizer' => '_preference_default_sanitizer',
);
function _preference_default_filter($input) {
return true;
}
function _preference_default_sanitizer($input) {
return true;
}
};
And then call by:
function do_stuff($foo) {
return $this->{$this->_preference_defaults['filter']}($foo);
}
Or
function do_stuff($foo) {
return call_user_func(array($this, $this->_preference_defaults['filter']), $foo);
}
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 have a private function to return an array of options, those options indicate a callback and other options such as template, form, etc. Here the code:
/**
* #return array
*/
private function options()
{
$options = [
'general' => [
'form' => GeneralConfigType::class,
'template' => 'general.html.twig',
'title' => 'Configuración General',
'ignoreFields' => ['slider', 'social'],
'uploadedFields' => [],
'callbacks' => ['generalData']
],
'business' => [
'form' => ConfigurationType::class,
'template' => 'business.html.twig',
'title' => 'Configuración de Empresa',
'ignoreFields' => [],
'uploadedFields' => ['image','favicon','login_icon','sidebar_icon'],
'callbacks' => ['businessImage']
],
];
return $options;
}
Now here is my doubt, in addition to indicate the function you have to execute in the key callback, Can I pass on the variables I'm going to need in that callback? I've tried several ways and they haven't worked.
Example:
Before:
'callbacks' => ['generalData']
After:
In this example I'm assigning the '$', but I could do it if the only string, I'm just looking for a way to pass to the callback the variables it needs and no more.
'callbacks' => ['generalData' => '$configurationData, $configuration, $form, $request']
And this code would be where everything would be executed in other method:
if (!empty($options[ 'callbacks' ])) {
foreach ($options[ 'callbacks' ] as $callback => $variables) {
$this->$callback($variables);
}
}
If I understand you correctly, you want to store the name of the variable in the array of options and then use that variable in the callback function.
When I've done this type of thing, I find it easier to just store the variable name as text and leave out the $ from the name stored in the array. I then use a variable variable when retrieving it.
Either way, I think you need a little more code on the execution side. One more loop:
if (!empty($options[ 'callbacks' ])) {
foreach ($options[ 'callbacks' ] as $callback => $variables) {
foreach($variables as $variable){ // extra loop to get the variables
$this->$callback[$$variable];
// This is where it gets tricky, and depends on how you wish to format.
// The variables are currently part of an array, thus the array notation
// above. By using the stored name only, and a variable variable, you
// should be able to get to the var you need
}
}
}
#jcarlosweb, what you need to do is very simple. The short answer is that it can be done using the [call_user_func_array()][1] method.
In the context of your example, the callbacks could be rearranges in the following way ...
'callbacks' => ['generalData' => [$configurationData, $configuration, $form, $request]
Basically, the array keys will be the name of the function to call, and the corresponding array values will be a array of the values of each parameter that is accepted but the callback function. Doing it this way is important because you need to capture the value of the parameters while they are in scope. And this will avoid using eval().
Using the callbacks can be as simple as ...
$options = options();
foreach ($options['callbacks'] as $callback => $params) {
$result = call_user_func_array($callback, $params);
// Do something with $result if necessary
}
I finally got it with the function compact http://php.net/manual/en/function.compact.php
Here's the code:
First I select the variables I need in my options:
'callbacks' => ['businessImage' => ['configurationData', 'configuration', 'form', 'request']]
Second I call the variables with compact, but I had to use extract here because if I didn't configurationData variable wasn't modified, which I don't understand since I had previously referenced it.
if (!empty($options[ 'callbacks' ])) {
foreach ($options[ 'callbacks' ] as $callback => $variables) {
$variables = compact($variables);
$this->$callback($variables);
extract($variables);
}
}
Third callback applied and referenced:
/**
* #param array $params
* #return array $configurationData
*/
private function businessImage(&$params)
{
extract($params,EXTR_REFS);
// more code here ......
$configurationData[ "image" ] = $originalImageName;
$configurationData[ "favicon" ] = $originalFaviconName;
$configurationData[ "login_icon" ] = $originalLoginIconName;
$configurationData[ "sidebar_icon" ] = $originalSidebarIconName;
return $configurationData;
}
This works correctly in my website, but as I said before I do not understand why I have to call back the function extract, if I have already passed it referenced in the same callback as you see in my last code.
I need to understand the code below, specially how exactly $duplicateChecks and $relationCallbacks work but there is little explanation on the official documentation. Can somebody explain how these work or suggest some other documentation I can look at?
class PlayerCsvBulkLoader extends CsvBulkLoader {
public $columnMap = array(
'Number' => 'PlayerNumber',
'Name' => '->importFirstAndLastName',
'Birthday' => 'Birthday',
'Team' => 'Team.Title',
);
public $duplicateChecks = array(
'Number' => 'PlayerNumber'
);
public $relationCallbacks = array(
'Team.Title' => array(
'relationname' => 'Team',
'callback' => 'getTeamByTitle'
)
);
public static function importFirstAndLastName(&$obj, $val, $record) {
$parts = explode(' ', $val);
if(count($parts) != 2) return false;
$obj->FirstName = $parts[0];
$obj->LastName = $parts[1];
}
public static function getTeamByTitle(&$obj, $val, $record) {
return FootballTeam::get()->filter('Title', $val)->First();
}
}
$duplicateChecks is used by findExistingObject function in the CsvBulkLoader class. It is iterated over to find any object that has a column with the specified value. In that example, it checks the "PlayerNumber" column.
It can also be passed a callback like so:
public $duplicateCheck = array(
'Number' => array(
'callback' => 'checkPlayerNumberFunction'
)
);
The callback specified needs to either exist on an instance of the class specified on the property objectClass or on the CsvBulkLoader itself (which would happen if you extended it). These callbacks are used to do more complex duplicate lookups and return an existing object (if any) found.
$relationCallbacks on the other hand is used by the main processRecord function. The callback works in the same way as the $duplicateCheck callback, it needs to either exist on an instance of the class specified on the proeprty objectClass or on the CsvBulkLoader. These callbacks can return an object that will be related back to a specific object record (new or existing) as a has_one.
There is a little more to it than that though the best way to learn is by a bit of experimentation and jumping through the code of the class itself. I have linked to the various functions etc in my answer.
I want to check if the user has permission to access to the URL (controller/method) combination. It should check before any method called in called controller and method belongs to it.
As far as I understand, the hook should be pre_controller for the logic above but when I use it, I think it clashes with post_controller_constructor shown below. If I use post_controller instead then it works but this time logic is compromised.
How can I solve this problem?
Thanks
CONFIG/HOOKS
//Used to authenticate user session to decide whether to authenticate site or not
$hook['post_controller_constructor'] =
array(
'class' => 'site_authentication',
'function' => 'authenticate',
'filename' => 'site_authentication.php',
'filepath' => 'hooks',
'params' => null
);
//Used to authenticate permitted controllers
$hook['pre_controller'] =
array(
'class' => 'permitted_controllers',
'function' => 'authenticate',
'filename' => 'permitted_controllers.php',
'filepath' => 'hooks',
'params' => null
);
APPLICATION/HOOKS
//This works fine
class site_authentication
{
private $CI;
public function __construct()
{
$this->CI =& get_instance();
}
public function authenticate()
{
if (! $this->CI->session->userdata('site'))
{
redirect('to error page');
}
$user_session = $this->CI->session->userdata('site');
//Some more stuff here
}
}
//This doesn't work with pre_controller
class permitted_controllers
{
private $CI;
public function __construct()
{
$this->CI =& get_instance();
}
public function authenticate()
{
$user_session = $this->CI->session->userdata('site');
//Url is set here, ignore syntax error below
$url = $this->CI->uri->segment(1) . 2 . 3;
if (! in_array($url, $user_session['controllers']))
{
redirect('to error page');
}
}
}
If I combine them two, they work fine under post_controller_constructor but they won't work separately?
$hook['post_controller_constructor'] [] =
array(
'class' => 'site_authentication',
'function' => 'authenticate',
'filename' => 'site_authentication.php',
'filepath' => 'hooks',
'params' => null
);
$hook['post_controller_constructor'] [] =
array(
'class' => 'permitted_controllers',
'function' => 'authenticate',
'filename' => 'permitted_controllers.php',
'filepath' => 'hooks',
'params' => null
);
pre_controller hook is run before the super object has been constructed, so it is not a viable option for hooking into CI's normal syntax (such as $this->db->query()).
I'd suggest creating a base controller (aka MY_Controller or some other name) and adding the permission check to its constructor. Then, each controller that should run the permissions check will then extend MY_Controller instead of CI_Controller. Here's Phil Sturgeon's classic article about base controllers.
Hooks are called on every page load. If you don't need to check permissions somewhere, you need to either add that logic to your hook, or add logic somewhere else to try and disable it. Not very extendable. Using a base controller, adding the permission check is as simple as extending a different class.
The pre_controller hook executes before the super object has been fully constructed read more here
I'm trying to set some default values in my class but can't use gettext as an variable value
Why does this code give me an error?
class Test
{
private $defaultoptions = array('HideOwnPosts' => false,
'HideClickedLinks' => false,
'AutoCommentLinks' => false,
'AutoCommentText' => gettext('exampletext'),
'AutoOpenCount' => 5);
}
I just need the default value dependend on the users language i use gettext für i18n so i yould like to user ist here too.
http://codepad.org/PTlIelQ4
You cannot initiate a class member with a 'dynamic' result (gettext()). Only static values are allowed. If you need to use the result of a function to initialize a member, then you'll have to do it in the constructor, not the class definition.
Try this:
<?php
class Test
{
private $defaultoptions = array('HideOwnPosts' => false,
'HideClickedLinks' => false,
'AutoCommentLinks' => false,
'AutoCommentText' => false,
'AutoOpenCount' => 5);
public function __construct() {
$this->defaultoptions['AutoCommentText'] = gettext('exampletext');
}
}