Determine what namespace the function was called in - php

I was wondering if it was possible to determine what the current namespace was when the function was being called. I have this function declaration:
<?php
namespace Site\Action;
function add ($hook, $function) {
/**
* determine the namespace this was called from because
* __NAMESPACE__ is "site\action" :(
*/
if (is_callable($function))
call_user_func($function);
}
?>
And on another file:
<?php
namespace Foo;
function bar () {
}
?>
And let's say I have this as my procedural code:
<?php
namespace Foo;
Site\Action\add('hookname', 'bar');
?>
It would make sense to assume that Bar in this case was intended to resolve as Foo\bar since that was the namespace it was called from.
That was a long explanation so again, is it possible to determine the active namespace where Site\Action\add() was called from?
Thanks in advance.

What you are looking for is : ReflectionFunctionAbstract::getNamespaceName
If you want to know where you're coming from debug_backtrace() is your friend.
The following should solve your puzzle:
function backtrace_namespace()
{
$trace = array();
$functions = array_map(
function ($v) {
return $v['function'];
},
debug_backtrace()
);
foreach ($functions as $func) {
$f = new ReflectionFunction($func);
$trace[] = array(
'function' => $func,
'namespace' => $f->getNamespaceName()
);
}
return $trace;
}
Just call it from anywhere to see the backtrace.
I modified your "procedural" code file as follows:
namespace Foo;
function bar ()
{
var_export(backtrace_namespace());
}
/** The unasked question: We need to use the fully qualified name currently. */
function go()
{
\Site\Action\add('hookname', 'Foo\\bar');
}
go();
The Result from including this file will be the following on stdout:
array (
0 =>
array (
'function' => 'backtrace_namespace',
'namespace' => '',
),
1 =>
array (
'function' => 'Foo\\bar',
'namespace' => 'Foo',
),
2 =>
array (
'function' => 'call_user_func',
'namespace' => '',
),
3 =>
array (
'function' => 'Site\\Action\\add',
'namespace' => 'Site\\Action',
),
4 =>
array (
'function' => 'Foo\\go',
'namespace' => 'Foo',
),
)
Now for bonus points the answer to the hidden question:
How do I resolve the calling namespace to avoid using fully qualified function name as argument?
The following will allow you to call the function as you intended:
Site\Action\add('hookname', 'bar');
Without getting the dreaded:
Warning: call_user_func() expects parameter 1 to be a valid callback, function 'bar' not found or invalid function name
So before you redesign try this on for size:
namespace Site\Action;
function add($hook, $function)
{
$trace = backtrace_namespace();
$prev = (object) end($trace);
$function = "$prev->namespace\\$function";
if (is_callable($function))
call_user_func($function);
}
I see no reason why debug_backtrace should not be used, this is what it is there for.
nJoy!

If you were using closures instead of static functions you can always ask the reflection API
namespace A;
$closure = function($word = 'hello')
{
return $word;
};
...
$r = new ReflectionFunction($closure);
print $r->getNamespaceName();

i don't know if i'm missing something, but with this:
http://php.net/manual/en/reflectionclass.getnamespacename.php, i think that you can get namespace of the object you are using.

Related

PHP, OOP: Why can't the array property of my class use properties & methods as values like an array outside of a class can?

So like the title says, I am having a hard time making an array property of one of my classes have it's values be declared as properties & methods.
I can successfully do this if the array is not a property of a class, but as soon as the array is dropped into a class, the script doesn't like those values, and throws me this error.
Fatal error: Constant expression contains invalid operations in C:\xampp\htdocs_webdev\repos\mcf\static\inc\classes\class.catalogue.php on line 17
I have both classes being included in a different .php called inc.classes.php. That file is then included in each page. Here is some code to better illustrate my issue,
Master Class File: inc.classes.php
// config
require_once('config/config.php'); // config file
// other tools
require_once(ROOT_DIR . 'inc/parsedown/Parsedown.php'); // tool that I am using for parsing .md files
// my classes
require_once(ROOT_DIR . 'inc/classes/class.vendor.php');
require_once(ROOT_DIR . 'inc/classes/class.catalogue.php');
Class A: class.vendor.php
class Vendor
{
public $vendor = array(
'foo' => array(
'name' => 'Foo Inc.',
'image' => (VENDOR_IMG . 'foo/foo-logo.png'),
),
'bar' => array(
'name' => 'Bar Co.',
'image' => (VENDOR_IMG . 'bar/bar-logo.png'),
),
);
public function get($data) {
if (array_key_exists($data, $this->vendors)) {
return $this->vendors[$data];
} else {
// throw error
}
}
// Class methods...
}
Class B: class.catalogue.php
class Catalogue
{
public $catalogue = array(
'1' => array(
$section = $markdown->text(file_get_contents(ROOT_DIR . catalogue/markdown/section1.md')),
$link = 'catalogue/pdf/section1.pdf,
$pdf = (ROOT_DIR . $link),
'title' => 'Section One',
'content' => mdReplace($section, $pdf, $link),
'theme' => 'purple',
'vendors' => array(
1 => $vendor->get('foo'),
2 => $vendor->get('bar'),
),
),
// '2' ...
);
// Class methods...
}
(mdReplace() is a small function located in a seperate php file called inc.functions.php. It's purpose is to replace a few keywords inside of the .md files that contain the sections' content.)
Apologies in advance if I am just blind as a bat right now and am missing something obvious.
You can't run methods on a class property like that. You'd need to set that up inside your construct:
class Catalogue
{
public $catalogue = array();
public function __construct()
{
$this->catalogue = array(
'1' => array(
$section = $markdown->text(file_get_contents(ROOT_DIR . catalogue/markdown/section1.md')),
$link = 'catalogue/pdf/section1.pdf,
$pdf = (ROOT_DIR . $link),
'title' => 'Section One',
'content' => mdReplace($section, $pdf, $link),
'theme' => 'purple',
'vendors' => array(
1 => $vendor->get('foo'),
2 => $vendor->get('bar'),
),
),
// '2' ...
);
}
// Class methods...
}
If you read php oop manual carefully, here what you will see:
Class member variables are called "properties"... They are defined by using one of the keywords public, protected, or private, followed by a normal variable declaration. This declaration may include an initialization, but this initialization must be a constant value--that is, it must be able to be evaluated at compile time and must not depend on run-time information in order to be evaluated.
See the words
must not depend on run-time information
And your current definition of public $catalogue is dependant of some data that will be evaluated later. That's why you have fatal error.
So, as said the solution is to fill $catalogue data by calling some function - either explicitly or in a __construct for example.
As Farkie said, you can't run method calls on a class property like the way you did.
The reason is, those objects which you are trying to use are not initialised, and in order for them to work they must be initialised first.
Ex variables which can't directly be used in properly as they are not available to be used
$markdown
$pdf
$section
So for Class B you need to have your code written inside the constructor.
However whatever you have done for Class A is perfectly acceptable and it should work. I could see that you have a typo in the variable name. It should be $vendors as you are trying to refer it inside the function get() as $this->vendors[$data];
The following will work
class Vendor
{
public $vendors = array(
'foo' => array(
'name' => 'Foo Inc.',
'image' => (VENDOR_IMG . 'foo/foo-logo.png'),
),
'bar' => array(
'name' => 'Bar Co.',
'image' => (VENDOR_IMG . 'bar/bar-logo.png'),
),
);
public function get($data) {
if (array_key_exists($data, $this->vendors)) {
return $this->vendors[$data];
} else {
// throw error
}
}
// Class methods...
}

Weird array conversion into boolean

I place this question, because I have a weird conversion of an array into boolean and I don't really know what's wrong with that.
I have checked my code very carefully, and I don't find any issue that can modify my code.
Do you see something wrong ? And if so, can you help me please ?
So, as requested I am going to explain in more details my problem.
So, I have this class, and I have a method called load_configuration() that loading some php files, returning an array of values each.
The array, returned by those files, are stored in equivalent property in the class.
Inside the load_configuration() method, I do a var_dump, for my class properties fields and settings, and I get the following result:
array (size=1)
'text' =>
array (size=3)
'type' => string 'text' (length=4)
'label' => string 'Hello world! goes here.' (length=23)
'default' => string 'Hello world!' (length=12)
Also, I have create an array at the end of the method load_configuration() and I return the arrays.
Then when I try to use the either the method properties or the returned values from the method load_configuration() I get the following with var_dump()
// In case of the returned array
array (size=2)
'fields' => boolean true
'settings' => boolean true
// In case of the class property
boolean true
But I don't see the reason. This is a very weird modification.
For better understanding look the comments in methods load_configuration() and get_template_variables()
class SS24_Widget extends \SiteOrigin_Widget {
protected $config_folder = 'Config';
protected $form_settings = 'settings';
protected $form_fields = 'fields';
protected $fields = array();
protected $settings = array();
public function __construct( $unique_widget_id = '', $widget_name = '' ) {
if ( empty( $unique_widget_id ) ) {
$unique_widget_id = uniqid( 'widget-' );
}
$this->load_configuration();
parent::__construct(
$unique_widget_id, // The unique id for your widget.
$widget_name, // The name of the widget for display purposes.
$this->settings, // The widget settings.
array(), // The $control_options array, which is passed through to WP_Widget.
$this->fields, // The widget fields.
$this->get_dir() . '/' // The $base_folder path string.
);
}
protected function load_configuration() {
$config_files = $this->get_dir() . '/' . $this->config_folder . '/*.php';
$fields = array();
$settings = array();
foreach ( glob( $config_files ) as $file ) {
switch( basename( $file, '.php' ) ) {
case $this->form_settings:
$this->settings = $this->{$this->form_settings} = require_once $file;
$settings = $this->settings;
break;
case $this->form_fields:
$this->fields = $this->{$this->form_fields} = require_once $file;
$fields = $this->fields;
break;
}
}
// This print out the following:
// array (size=1)
// 'text' =>
// array (size=3)
// 'type' => string 'text' (length=4)
// 'label' => string 'Hello world! goes here.' (length=23)
// 'default' => string 'Hello world!' (length=12)
var_dump($fields);
return array(
'fields' => $fields,
'settings' => $this->settings,
);
}
// ...
public function get_template_variables( $instance, $args ){
$c = $this->load_configuration();
// While the following printint out the following:
// array (size=2)
// 'fields' => boolean true
// 'settings' => boolean true
//
// boolean true
var_dump($c);
echo "<br />";
var_dump($this->fields);
return $variables;
}
}
Can someone please explain me what possibly it's wrong with this code ?
UPDATE #1
I found that the parent class has a protected member property named $fields so in my local code I have change now the variable into a fields_settings, but still is converted into boolean.
require_once is very similar to include_once.
If a file has already been included, they will both return boolean TRUE.
Quoting PHP documentation:
If the code from a file has already been included, it will not be included again, and include_once returns TRUE. As the name suggests, the file will be included just once.
Documentation of require_once points to include_once. This is why the block mentions the former.
To solve your problem, make sure you use require instead.
I think the problem is that you execute load_configuration() in the constructor, so the require_once statement returns the correct values here. In the next call to load_configuration(), the require_once statement returns boolean "true", indicating that the file was already included.
Use require instead of require_once.

CSV Import in SilverStripe Duplicates and RelationCallbacks

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.

Get use statement from class

Not quite sure of the best title but I will explain what I am asking as best I can. Assume I have the following file:
MyCustomClass.php
<?php
namespace MyNamespace;
use FooNamespace\FooClass;
use BarNamespace\BarClass as Bar;
use BazNamespace\BazClass as BazSpecial;
class MyCustomClass {
protected $someDependencies = [];
public function __construct(FooClass $foo, Bar $bar) {
$someDependencies[] = $foo;
$someDependencies[] = $bar;
}
}
Now if I were to use reflection, I could get the fully qualified class names from the type hints in the construct.
However, I would recieve FooNamespace\FooClass and BarNamespace\BarClass. Not, FooNamespace\FooClass and BarNamespace\Bar. I would also get no reference to BazNamespace\BazClass.
Basically, my question is: How can I get the fully qualified names from MyCustomClass.php while only knowing FooClass, Bar, and, BazSpecial?
I do not want to use a file parser as this will eat performance. I want to be able to do something like:
$class = new ReflectionClass('MyCustomClass');
...
$class->getUsedClass('FooClass'); // FooNamespace\FooClass
$class->getUsedClass('Bar'); // BarNamespace\BarClass
$class->getUsedClass('BazSpecial'); // BazNamespace\BazClass
How would I go about doing this?
Seeing as no one has answered, I assume there is not an easy way to achieve this. I have therefore created my own class called ExtendedReflectionClass which achieves what I need.
I have created a gist with the class file and a readme, which is at the bottom so get scrolling!.
ExtendedReflectionClass
Usage example:
require 'ExtendedReflectionClass.php';
require 'MyCustomClass.php';
$class = new ExtendedReflectionClass('MyNamespace\Test\MyCustomClass');
$class->getUseStatements();
// [
// [
// 'class' => 'FooNamespace\FooClass',
// 'as' => 'FooClass'
// ],
// [
// 'class' => 'BarNamespace\BarClass',
// 'as' => 'Bar'
// ],
// [
// 'class' => 'BazNamespace\BazClass',
// 'as' => 'BazSpecial'
// ]
// ]
$class->hasUseStatement('FooClass'); // true
$class->hasUseStatement('BarNamespace\BarClass'); // true
$class->hasUseStatement('BazSpecial'); // true
$class->hasUseStatement('SomeNamespace\SomeClass'); // false
I use the TokenFinderTool for that.
Basically, it uses tokens to extract the use statements.
As far as I know, \Reflection objects in php unfortunately don't have such a method yet.
The code below extracts the use import statements from a file, using the TokenFinder tool.
$tokens = token_get_all(file_get_contents("/path/to/MyCompany/MyClass.php"));
a(TokenFinderTool::getUseDependencies($tokens));
Will output:
array (size=9)
0 => string 'Bat\CaseTool' (length=12)
1 => string 'Bat\FileSystemTool' (length=18)
2 => string 'Bat\StringTool' (length=14)
3 => string 'Bat\ValidationTool' (length=18)
4 => string 'CopyDir\AuthorCopyDirUtil' (length=25)
5 => string 'PhpBeast\AuthorTestAggregator' (length=29)
6 => string 'PhpBeast\PrettyTestInterpreter' (length=30)
7 => string 'PhpBeast\Tool\ComparisonErrorTableTool' (length=38)
8 => string 'Tiphaine\TiphaineTool' (length=21)
Note: if you have a class name only, you can use this snippet instead:
$o = new \ReflectionClass($className);
$tokens = token_get_all(file_get_contents($$o->getFileName()));
$useStatements = TokenFinderTool::getUseDependencies($tokens);
Use BetterReflection (composer req roave/better-reflection)
$classInfo = (new BetterReflection())
->reflector()
->reflectClass($class);
$uses = [];
foreach ($classInfo->getDeclaringNamespaceAst()->stmts as $stmt) {
foreach ($stmt->uses??[] as $use) {
$uses[] = join('\\', $use->name->parts);
}
}
print_r($uses);

Can't access global PHP array variable

Within a Drupal module callback function, there is a simple custom function that intakes an array.
The custom function executes correctly when I define the input array within the Drupal module callback function. However, when I define the input array at the root level (global), the custom function within the Drupal module callback function fails.
As a test, I made the custom function simply output the contents of the input array as a string. The first method outputs correctly while the second method does not have any output. Ideally, I'd like to define the array at the global level so that it can be used by other functions.
Thoughts?
<?php
// ** Placement of array for method 2
$mapping = array(
0 => "name",
1 => "match"
);
function mymodule_menu() {
$items = array();
$items['mymodule'] = array(
'title' => 'MyModule',
'page callback' => 'myModule_main',
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM
);
return $items;
}
function myModule_main() {
// ** Placement of array for method 1
$mapping = array(
0 => "name",
1 => "match"
);
$output = myFunction($mapping);
echo $output; // ** Returned to client side via AJAX
}
You need to "import" the global variable into the function's scope using the global keyword.
See http://php.net/manual/en/language.variables.scope.php#language.variables.scope.global
function myModule_main() {
global $mapping;
...
}
<?php
global $foobar;
$foobar = "text";
function myFunction() {
echo $GLOBALS["foobar"]; // Returns "text"
}
?>

Categories