Get use statement from class - php

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);

Related

Laravel 5 passing page info with array

I'm trying to pass data about the page state (navbar links having active class when you are in that exact page), page title. I do so with an indexed array $pageInfo, however I am getting a syntax error and doen't know where?
Also do you think this is a good method or should I use view->share() instead?
public function clases()
{
$pageInfo[] =
(
'page_title' => 'Clases',
'menu_active' => 'CLases',
'sub_menu_active' => '',
);
return view('clases.list', compact('pageInfo'));
}
public function domicilio()
{
$pageInfo[] =
(
'page_title' => 'Clases a domicilio',
'menu_active' => 'Clases',
'sub_menu_active' => 'Clases a domicilio',
);
return view('clases.domicilio', compact('pageInfo'));
I suggest you read PHP basic syntax.
Basically you want to do this:
$pageInfo =
[
'page_title' => 'Clases',
'menu_active' => 'CLases',
'sub_menu_active' => '',
];
Arrays have a syntax of [key => val, ...] in PHP, you're using () as it seems.
Also $someArray[] = someValue, will append the someValue to an existing array, in your case that would create another, unwanted level of your array.
And last, you're not ending the domicilio() function. But I'll assume you just didn't paste it in (you should add } at the end, if that's not the case).

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.

Yii2 Object of class Closure could not be converted to string

I've got error
Object of class Closure could not be converted to string
on this code
'class' => \dosamigos\grid\EditableColumn::className(),
'attribute' => 'remidi3',
'url' => function($data){return ['update?id=remidi3&dataid'.$data->id];},
'type' => 'text',
'editableOptions' => [
'mode' => 'inline',
]
even I've try to change
'url' => function($data){return ['update?id=remidi3&dataid'.$data->id];}
into
'url' => function($data){return 'update?id=remidi3&dataid'.$data->id;},
I need to display id in the URL of editable grid, somebody can help me?
According to source code and PHPDoc, you can't specify closure here.
PHPDoc says:
/**
* #var string the url to post
*/
public $url;
Usage in source code:
if ($this->url === null) {
throw new InvalidConfigException("'Url' property must be specified.");
}
...
$url = (array) $this->url;
$this->options['data-url'] = Url::to($url);
As you can see, it's converted to array and then processed by Url::to(), so the valid types are string and array.
I don't think you need to specify id in url, it should be taken automatically depending on row you working with.

Determine what namespace the function was called in

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.

Categories