Enqueue a function (like wordpress add_action) - php

How do I queue functions in PHP? I need something that works just like Wordpress's add_action system. I want enqueue function which then runs when the time is right.
Edit
This seems to work perfectly. Anyone got any tips to improve my code?
$enqueued_actions = array();
/**
* Enqueue an action to run at a later time.
* #param string $hook The hook name.
* #param obj $func The function object.
* #param integer $imp The level of importance from 0-9
*/
function add_action($hook, $func, $imp = 0) {
global $enqueued_actions;
$enqueued_actions[$hook][] = array('func' => $func, 'imp' => $imp);
}
/**
* Run the enqueued actions with the correct hook.
* #param string $hook Hook name.
*/
function run_action($hook) {
global $enqueued_actions;
$actions = $enqueued_actions[$hook];
for($i = 0; $i < 9; $i++) {
foreach($enqueued_actions[$hook] as $action) {
if($action['imp'] == $i) {
call_user_func($action['func']);
}
}
}
}

You are on the right track here. You might want to make the hooks persistent, though, i.e. saving the hooks into a database, a CSV file, an XML object model, etc.
Also, you might want to introduce a function for the sake of calling all the actions that have been latched to a certain hook, something like call_actions($hook);

Related

Is there a quicker way to check is a LDAP user is in a LDAP Group? This is slow as it checks each one each time?

Using adldap-laravel and Laravel 5.8.
I'm getting permissions based on LDAP groups. I can check if a user is part of a group using: $user->ldap->inGroup('Accounts'); (that returns a bool)
However that method also accepts an array, but seems to be an "AND" search, rather than "ANY".
So I've written this:
/**
* LDAP helper, to see if the user is in an AD group.
* Iterate through and break when a match is found.
*
* #param mixed $input
* #return bool
*/
public function isInGroup($input)
{
if (is_string($input)) {
$input[] = $input;
}
foreach ($input as $group)
if ($this->ldap->inGroup($group)) {
return true;
}
return false;
}
Implemented like this:
$user->isInGroup(['Accounts', 'Sales', 'Marketing']);
However it takes a long time to check.
Does anyone know of an improved way to solve my problem?
Yes can do it via query builder of Adldap.
/**
* LDAP helper, to see if the user is in an AD group.
* Iterate through and break when a match is found.
*
* #param mixed $input
* #return bool
*/
public function isInGroup($input){
if (is_string($input)) {
$input[] = $input;
}
$counter = 0;
$groupQuery = $this->ldap->search()->groups();
foreach ($input as $group) {
if ($counter == 0) {
$groupQuery->where('samaccountname', '=', $group);
} else {
$groupQuery->orWhere('samaccountname', '=', $group);
}
$counter++;
}
return $groupQuery->get()->count();
}
This samaccountname may be different field name for your LDAP provider. I couldn't tested it if has any syntax or method name error anyway you will find this same methods from your Adldap Provider class. The algorithm/process is same.

Unit test: using the proper terminology for mocking/stubbing

After fundamental changes on my project system architecture, I find myself in a situation where I would need to create "fake" implementation in order to test some functionality that used to be public like the following:
/**
* Display the template linked to the page.
*
* #param $newSmarty Smarty object to use to display the template.
*
* #param $parameters associative Array containing the values to pass to the template.
* The key is the name of the variable in the template and the value is the value of the variable.
*
* #param $account child class in the AccountManager hierarchy
*
* #param $partialview String name of the partial view we are working on
*/
protected function displayPageTemplateSmarty(Smarty &$newSmarty, array $parameters = array(), AccountManager $account = NULL, string $partialview = "")
{
$this->smarty = $newSmarty;
if (is_file(
realpath(dirname(__FILE__)) . "/../../" .
Session::getInstance()->getCurrentDomain() . "/view/" . (
!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
)
)) {
$this->smarty->assign(
'activeLanguage',
Session::getInstance()->getActiveLanguage()
);
$this->smarty->assign('domain', Session::getInstance()->getCurrentDomain());
$this->smarty->assign(
'languages',
Languagecontroller::$supportedLanguages
);
$this->smarty->assign(
'title',
Languagecontroller::getFieldTranslation('PAGE_TITLE', '')
);
$this->smarty->assign_by_ref('PageController', $this);
$htmlTagBuilder = HTMLTagBuilder::getInstance();
$languageController = LanguageController::getInstance();
$this->smarty->assign_by_ref('htmlTagBuilder', $htmlTagBuilder);
$this->smarty->assign_by_ref('languageController', $languageController);
if (!is_null($account)) {
$this->smarty->assign_by_ref('userAccount', $account);
}
if (!is_null($this->menuGenerator)) {
$this->smarty->assign_by_ref('menuGenerator', $this->menuGenerator);
}
foreach ($parameters as $key => $value) {
$this->smarty->assign($key, $value);
}
$this->smarty->display((!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
));
}
}
In this case, the PageController class used to be called directly in controllers, but is now an abstract class extended by the controllers and my unit tests can no longer access the method.
I also have methods like this one in my new session wrapper class that can only be used in very specific context and for which I really need to create fake page implementation to test them.
/**
* Add or update an entry to the page session array.
*
* Note: can only be updated by the PageController.
*
* #param $key String Key in the session array.
* Will not be added if the key is not a string.
*
* #param $value The value to be added to the session array.
*
* #return Boolean
*/
public function updatePageSession(string $key, $value)
{
$trace = debug_backtrace();
$updated = false;
if (isset($trace[1]) and
isset($trace[1]['class']) and
$trace[1]['class'] === 'PageController'
) {
$this->pageSession[$key] = $value;
$updated = true;
}
return $updated;
}
Even though I read a few article, it is still quite unclear in my mind if those fake classes should be considered as "stub" or a "mock" (or even "fake", "dummy" and so on).
I really need to use the proper terminology since my boss is expecting me (in a close future) to delegate most of my workload with oversea developers.
How would you call those fake class implementation created solely for testing purpose in order to be self-explanatory?
Gerard Meszaros explains the terminology of dummies, stubs, spies, mocks, and fakes here.
You can find examples from the PHP world here.

PhpStorm: extract() identifying variables

Does anyone know whether there is a setting in PhpStorm that can trigger identifying variables generated using extract() function?
Example would be something like the following:
/**
* #return array
*/
protected function orderSet() : array
{
//...
return [
'colour' => $colour,
'green' => $green,
'orange' => $orange
];
}
/**
* #test
*/
public function returns_correct_attribute_names()
{
$params = $this->orderSet();
extract($params);
$this->assertEquals(
'Colour',
$colour->name
);
}
At the moment any variable that's been extracted in the test is highlighted (unrecognised), but perhaps there is a setting that can change this behaviour?
The solution that LazyOne offered actually works. However there is a bit more context you need in order to implement it.
To accurately inform PHPSTORM about the variables you want to declare the comment must be placed directly above extract() and not the parent function.
public function db(){
$db = new SQLite3('db/mysqlitedb.db');
$payments = $db->query('SELECT * FROM payments');
while ($pay = $payments->fetchArray()){
/**
* #var string $to_user
* #var string $from_user
* #var number $amount
*/
extract($pay);
if (isset($to_user, $from_user, $amount))
echo "TO: {$to_user}| FROM: {$from_user}| $ {$amount} \n";
};
}
This is a working sample from my code ( couldn't copy yours for some reason ).
You can see just before I use the extract() function I declare in the comment block above it the hidden variables and data types.
Bonus: if you intend to use extract, I highly recommend you use an isset to ensure the array you're parsing contains the fields you are expecting.
example in code above

Is there a way to create a "custom" loop or control structure in PHP?

I'm creating a very large page that at various parts needs to loop through a data array like this:
$processed = array();
foreach( $data as $program )
{
if( $program['f_enabled'] == 'N' ) continue;
$pid = $program['f_programId'];
if( !array_key_exists($pid, $processed) )
{
/**
* Do things with $pid and $program['f_foobar'], including
* ?>
* <span>HTML with <?=$inlined_vars?></span>
* <?php
*/
$processed[] = $pid;
}
}
This is reminiscent to me of the WordPress Loop. I know I can compact all the loops into one, storing HTML output in variables and piecing them together at the end, but I strongly desire for code to appear in alignment with the HTML that will surround it.
How can I factor out everything on the outside? Even if it's as hacky as:
MY_HORRIFYING_MACRO
{
/**
* Do things with $pid and $program['f_foobar'], including
* ?>
* <span>HTML with <?=$inlined_vars?></span>
* <?php
*/
}
I'm not concerned about correctness here—I just need this demo to work, and for the code to be readable in a presentation top-to-bottom with a synchronized sense of what else is on the page. Preferably PHP 5.3-compatible—not positive the demo server will be running PHP 5.4+—but if a solution exists using PHP 5.4+ constructs, please share anyway. Thank you.
Instead of making a new control structure (as you would in Ruby), you can do something with more boilerplate but more idiomatically PHP like this:
$processed = array();
function preloop($program) {
if( $program['f_enabled'] == 'N' ) return true;
$pid = $program['f_programId'];
if( !array_key_exists($pid, $processed) )
{
$processed[] = $pid;
return false;
}
return true;
}
foreach( $data as $program ) {
if (preloop($program))
continue;
// do things with program
}
// somewhere else
foreach( $data as $program ) {
if (preloop($program))
continue;
// do things with program
}
You can't do something like that in PHP, but for the processing of datas to be shown in HTML use something like Twig or similar.
I was able to get what I wanted by using callbacks.
First, define the wrapper ("macro") function, like so:
function my_macro($data, $fn)
{
foreach( $data as $program )
{
if( $program['f_enabled'] == 'N' ) continue;
$pid = $program['f_programId'];
if( !array_key_exists($pid, $processed) )
{
call_user_func($fn, $program, $pid);
}
}
}
So in the above, I'm passing down $program and $pid to a not-yet-defined callback function, as these two variables are always needed, and needed frequently.
To actually use this construct, one would simply do this:
my_macro(function($program, $pid)
{
/**
* Do things here, including
* ?>
* <span>HTML with <?=$pid?> and <?=$program['title']?></span>
* <?php
*/
});
And you can disperse this wherever you like in your page. You can of course have as many frequently-used variables as you like in addition to $program and $pid.
I still recommend reading #Marty's advice in the question's comments for a proper, non-hacky approach. But yeah, that's what I did.

PHP memory references

I am wondering this question for a long time, how does PHP handle references are they a good idea to use and I can't explain better than using an example, lets look at the following class and then # the comment of the setResult method.
Lets imagine we are using a model view controller framework and we are building a basic AjaxController, we only got 1 action method (getUsers) so far. Read the comments, and I hope my question is clear, how does PHP handle these kind of situations and is it true what I wrote about the x times in the memory # the setResult docblock.
class AjaxController{
private $json = array(
'result' => array(),
'errors' => array(),
'debug' => array()
);
/**
* Adds an error, always displayed to users if any errors.
*
* #param type $description
*/
private function addError($description){
$this->json['errors'][] = $description;
}
/**
* Adds an debug message, these are displayed only with DEBUG_MODE.
*
* #param type $description
*/
private function addDebug($description){
$this->json['debug'][] = $description;
}
/**
* QUESTION: How does this go in memory? Cause if I use no references,
* the array would be 3 times in the memory, if the array is big (5000+)
* its pretty much a waste of resources.
*
* 1st time in memory # model result.
* 2th time in memory # setResult ($resultSet variable)
* 3th time in memory # $this->json
*
* #param array $resultSet
*/
private function setResult($resultSet){
$this->json['result'] = $resultSet;
}
/**
* Gets all the users
*/
public function _getUsers(){
$users = new Users();
$this->setResult($users->getUsers());
}
public function __construct(){
if(!DEBUG_MODE && count($this->json['debug']) > 0){
unset($this->json['debug']);
}
if(count($this->json['errors']) > 0){
unset($this->json['errors']);
}
echo json_encode($this->json);
}
}
Another simple example: What would be better to use technique A:
function example(){
$latestRequest = $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$_SESSION['abc']['test']['abc'] = 'test';
}
}
Or technique B:
function example(){
$latestRequest =& $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$latestRequest = 'test';
}
}
Thanks for reading and advise :)
In short: don't use references.
PHP copies on write. Consider:
$foo = "a large string";
$bar = $foo; // no copy
$zed = $foo; // no copy
$bar .= 'test'; // $foo is duplicated at this point.
// $zed and $foo still point to the same string
You should only use references when you need the functionality that they provide. i.e., You need to modify the original array or scalar via a reference to it.

Categories