PHP: Checking if a reflection class has a constructor? - php

I wrote a dependency provider (basically IOC container) for my application. I added the feature of autowiring, although it hits a snag when the class it is trying to autowire doesn't have a constructor.
Uncaught Error: Call to a member function getParameters() on null in C:\xampp\htdocs\src\app\Providers\DependencyProvider.php:68
I am fairly certain it is trying to resolve constructor arguments in the resolveArguments method, on a class that doesn't have a constructor, and this is why the issue is happening.
So, resolveArguments should only be called if the class needs their arguments resolved (autowired), and that is usually only when it has a constructor.
The error message above, is happening because getConstructor is returning null. I am asking what is the best practice to check if a reflection class has a constructor that needs autowiring?
Full class:
<?php
namespace App\Providers;
class DependencyProvider {
private static $objects = [];
/**
* Register an instantiated object to the container.
*
* #param object $object
*/
public static function register(object $object) : void {
self::$objects[get_class($object)] = $object;
}
/**
* Fetch a cached object from the container.
*
* #param string $objectName
* #return object
*/
public static function fetch(string $objectName) : object {
if (array_key_exists($objectName, self::$objects)) {
return self::$objects[$objectName];
}
$object = self::make($objectName);
self::$objects[$objectName] = $object;
return $object;
}
/**
* Creates an object from its name and auto-wires constructor arguments.
*
* #param string $objectName
* #return object
* #throws \ReflectionException
*/
private static function make(string $objectName) : object {
$reflection = new \ReflectionClass($objectName);
if (!$reflection->isInstantiable()) {
throw new RuntimeException($reflection->getName() . ' can\'t be instantiated.');
}
$arguments = self::resolveArguments($reflection);
if (count($arguments) < 1) {
return $reflection->newInstance();
}
else {
return $reflection->newInstanceArgs($arguments);
}
}
/**
* Creates an array of arguments from a reflection class.
* Uses default value if there is one, auto-wires the object if not.
*
* #param $reflection
* #return array
*/
private static function resolveArguments($reflection) : array {
$constructor = $reflection->getConstructor();
$parameters = $constructor->getParameters();
if (!$parameters) {
return $reflection->newInstance();
}
$arguments = [];
foreach ($parameters as $parameter) {
if ($parameter->isDefaultValueAvailable()) {
$arguments[] = $parameter->getDefaultValue();
continue;
}
if ($parameter->getClass() == null) {
exit($parameter->name . ' on ' . $reflection->getName() . ' needs a default value');
}
$arguments[] = self::fetch($parameter->getClass()->getName());
}
return $arguments;
}
}

Edit (misread the question):
Just check that the constructor actually exists before calling that method:
if (! is_null($reflection->getConstructor())) { ... }

Related

ReflectionMethod's get type is retuning an empty object

So, I am trying the get the types of the methods, to instantiate the classes, for example:
I have a class called mycontroller and a simple method called page which has a class Type hint, for example:
class MyController
{
public function page(AnotherClass $class)
{
$class->intro;
}
}
I also have another class, litterly called anotherclass (very original, I know)
class AnotherClass
{
public $intro = "Hello";
}
Okay, so that's the basics, now I am trying to get the type of MYControllers method arguments page: anotherclass
You can see the logic of my code below:
Class Route
{
/**
* Method paramaters
*
* #var array
*/
protected $params;
/**
* The class and method
*
* #var array
*/
protected $action;
/**
* Get the paramaters of a callable function
*
* #return void
*/
public function getParams()
{
$this->params = (new ReflectionMethod($this->action[0], $this->action[1]))->getParameters();
}
/**
* Seperate the class and method
*
* #param [type] $action
* #return void
*/
public function getClassAndMethod($action = null)
{
$this->action = explode("#", $action);
}
/**
* A get request
*
* #param string $route
* #return self
*/
public function get($route = null)
{
if(is_null($route)) {
throw new Exception("the [$route] must be defined");
}
return $this;
}
public function uses($action = null)
{
if(is_null($action)){
throw new Exception("the [$action] must be set");
}
if(is_callable($action)){
return call_user_func($action);
}
// Get the action
$this->getClassAndMethod($action);
// Get the params of the method
$this->getParams();
foreach ($this->params as $param) {
print_R($param->getType());
}
// var_dump($action[0]);
}
}
Which is simply being called like so:
echo (new Route)->get('hello')->uses('MyController#page');
So, what the above does, is it splits the uses method paramater via the # sign, the [0] will be the class and the [1] will be the class's method, then I am simply ReflectionMethod to get the parameters of said method, and then I am trying to get the parameter type, which, is what I am stuck on, because it just keeps returning an empty object:
ReflectionNamedType Object { )
So, my question is, why is it returning an empty object, and how can I get the type of the parameter?
You have to echo instead of print_r :
foreach ($this->params as $param) {
echo $param->getType() ; //AnotherClass
}
Because ReflectionType use __toString() to display it.
Or
foreach ($this->params as $param) {
print_r($param->getClass()) ; //AnotherClass
}

Using one class's properties in another OOP PHP

I have the following class
namespace PG\Referrer\Single\Post;
class Referrer implements ReferrerInterface
{
/**
* #var $authorReferrer = null
*/
protected $isAuthorReferrer = null;
/**
* #var $dateReferrer = null
*/
protected $isDateReferrer = null;
/**
* #var $searchReferrer = null
*/
protected $isSearchReferrer = null;
/**
* #var $taxReferrer = null
*/
protected $isTaxReferrer = null;
/**
* #param array $values = null;
*/
public function __construct(array $values = null)
{
if ($values)
$this->setBulk($values);
}
/**
* Bulk setter Let you set the variables via array or object
*/
public function setBulk($values)
{
if (!is_array($values) && !$values instanceof \stdClass) {
throw new \InvalidArgumentException(
sprintf(
'%s needs either an array, or an instance of \\stdClass to be passed, instead saw %s',
__METHOD__,
is_object($values) ? get_class($values) : gettype($values)
)
);
}
foreach ($values as $name => $value) {//create setter from $name
global $wp_query;
if (array_key_exists($value, $wp_query->query_vars)) { //Check that user don't set a reserved query vars
throw new \InvalidArgumentException(
sprintf(
'%s is a reserved query_vars and cannot be used. Please use a unique value',
$value
)
);
}
$setter = 'set' . $name;
$condition = isset($_GET[$value]);
if ($setter !== 'setBulk' && method_exists($this, $setter)) {
$this->{$setter}($condition);//set value (bool)
}
}
return $this;
}
/**
* #param bool $authorReferrer
* #return $this
*/
public function setAuthorReferrer($isAuthorReferrer)
{
$this->isAuthorReferrer = $isAuthorReferrer;
return $this;
}
/**
* #param bool $dateReferrer
* #return $this
*/
public function setDateReferrer($isDateReferrer)
{
$this->isDateReferrer = $isDateReferrer;
return $this;
}
/**
* #param bool $searchReferrer
* #return $this
*/
public function isSearchReferrer($isSearchReferrer)
{
$this->isSearchReferrer = $isSearchReferrer;
return $this;
}
/**
* #param bool $taxReferrer
* #return $this
*/
public function setTaxReferrer($isTaxReferrer)
{
$this->isTaxReferrer = $isTaxReferrer;
return $this;
}
}
with its interface
namespace PG\Referrer\Single\Post;
interface ReferrerInterface
{
/**
* #param array $values
* #return $this
*/
public function setBulk($values);
/**
* #param bool $authorReferrer
* #return $this
*/
public function setAuthorReferrer($isAuthorReferrer);
/**
* #param bool $dateReferrer
* #return $this
*/
public function setDateReferrer($isDateReferrer);
/**
* #param bool $searchReferrer
* #return $this
*/
public function isSearchReferrer($isSearchReferrer);
/**
* #param bool $taxReferrer
* #return $this
*/
public function setTaxReferrer($isTaxReferrer);
}
This class sets up 4 conditionals that I need to use in another class. The values that is used in this class is also set from the other class, so basically the user sets values in the other class (lets call it class b) that is then used by class Referrer and returns the 4 conditionals which is then used by class b.
The reason why I'm doing it this way is because there will be two other classes that will need to do the same, but will returns different info
What is the more correct way to achieve this?
EDIT
To clear this up
class Referrer
The properties $isAuthorReferrer, $isDateReferreretc will either have a value of null or a boolean value depending on what is set by the user.
Example:
$q = new Referrer(['authorReferrer' => 'aq']);
In the code above, $isAuthorReferrer is set via the setBulk() method in the class to true when the variable aq is available in the URL or false when not present. The three other properties will return null because they are not set in the example.
The above works as expected, but I need to do this in another class, lets again call it class b. The arguments will be set to class b, and in turn, class b will set this arguments to class Referrer, class Referrer will use this arguments and return the proper values of its properties, and class b will use this results to do something else
Example:
$q = new b(['authorReferrer' => 'aq']);
Where class b could be something like this (it is this part that I'm not sure how to code)
class b implements bInterface
{
protected $w;
protected $other;
public function __construct($args = [])
{
//Do something here
// Do something here so that we can use $other in other classes or functions
}
public function a()
{
$w = new Referrer($args);
}
public function b()
{
// use $w properties here
// return $other for usage in other classes and functions
}
}
The best way is to inject the referrer to your classes in order to do loose coupling between them and the referrer (this pattern use the benefit of your ReferrerInterface):
class b implements bInterface
{
protected $referrer;
public function __construct(ReferrerInterface $referrer, array $values = array())
{
$this->referrer = $referrer;
$this->referrer->setBulk($values);
}
public function getReferrer()
{
return $this->referrer;
}
public function b()
{
// use $this->referrer properties here
}
}
// Instantiation (use your dependency injection if you have one):
$referrer = new Referrer();
$b = new b($referrer, ['authorReferrer' => 'aq']);
I do not understand what is $other so I removed it but explain me if you want me to I add it again.
If you need to use the properties of the referrer in b, you should add some getters in your ReferrerInterface to allow that. I would use setAuthorReferrer($isAuthorReferrer) to set the value and isAuthorReferrer() to get it for instance.

Properties, public or private OOP PHP

I have the following two classes (I have not included the interfaces)
ConditionsRefer
namespace PG\Referrer\Single\Post;
class ConditionsRefer implements ConditionsReferInterface
{
/**
* #var $authorReferrer = null
*/
private $isAuthorReferrer = null;
/**
* #var $dateReferrer = null
*/
private $isDateReferrer = null;
/**
* #var $searchReferrer = null
*/
private $isSearchReferrer = null;
/**
* #var $taxReferrer = null
*/
private $isTaxReferrer = null;
/**
* #param array $values = null;
*/
public function __construct(array $values = null)
{
if ($values)
$this->setBulk($values);
}
/**
* Bulk setter Let you set the variables via array or object
*/
public function setBulk($values)
{
global $wp_query;
if (!is_array($values) && !$values instanceof \stdClass) {
throw new \InvalidArgumentException(
sprintf(
'%s needs either an array, or an instance of \\stdClass to be passed, instead saw %s',
__METHOD__,
is_object($values) ? get_class($values) : gettype($values)
)
);
}
foreach ($values as $name => $value) {//create setter from $name
if (array_key_exists($value, $wp_query->query_vars)) { //Check that user don't set a reserved query vars
throw new \InvalidArgumentException(
sprintf(
'%s is a reserved query_vars and cannot be used. Please use a unique value',
$value
)
);
}
$setter = 'set' . $name;
$condition = isset($_GET[$value]);
if ($setter !== 'setBulk' && method_exists($this, $setter)) {
$this->{$setter}($condition);//set value (bool)
}
}
return $this;
}
/**
* #param $authorReferrer
* #return $this
*/
public function setAuthorReferrer($isAuthorReferrer)
{
$this->isAuthorReferrer = $isAuthorReferrer;
return $this;
}
/**
* #param $dateReferrer
* #return $this
*/
public function setDateReferrer($isDateReferrer)
{
$this->isDateReferrer = $isDateReferrer;
return $this;
}
/**
* #param $searchReferrer
* #return $this
*/
public function isSearchReferrer($isSearchReferrer)
{
$this->isSearchReferrer = $isSearchReferrer;
return $this;
}
/**
* #param $taxReferrer
* #return $this
*/
public function setTaxReferrer($isTaxReferrer)
{
$this->isTaxReferrer = $isTaxReferrer;
return $this;
}
}
QueryArgumentsRefer
namespace PG\Referrer\Single\Post;
class QueryArgumentsRefer implements QueryArgumentsReferInterface
{
private $referrer;
public function __construct(ConditionsReferInterface $referrer, array $values = array())
{
$this->referrer = $referrer;
$this->referrer->setBulk($values);
}
public function getReferrer()
{
return $this->referrer;
}
public function b()
{
$test = (object) $this->referrer;
if($test->isAuthorReferrer === false)
return 'This is just a test';
}
}
This is how I use it in a file
$a = new QueryArgumentsRefer(new ConditionsRefer(), ['authorReferrer' => 'aq']);
?><pre><?php var_dump($a->b()); ?></pre><?php
In function b() in class QueryArgumentsRefer, I need to use the properties of class ConditionsRefer.
This is the result of $test, which is the expected result, so this is working
object(PG\Referrer\Single\Post\ConditionsRefer)#522 (4) {
["isAuthorReferrer":"PG\Referrer\Single\Post\ConditionsRefer":private]=>
bool(false)
["isDateReferrer":"PG\Referrer\Single\Post\ConditionsRefer":private]=>
NULL
["isSearchReferrer":"PG\Referrer\Single\Post\ConditionsRefer":private]=>
NULL
["isTaxReferrer":"PG\Referrer\Single\Post\ConditionsRefer":private]=>
NULL
}
If I try to use $test->isAuthorReferrer, I get the following error
Fatal error: Cannot access private property PG\Referrer\Single\Post\ConditionsRefer::$isAuthorReferrer
which is expected I guess. The only way to make this work in my mind is setting the properties in ConditionsRefer to public
I've read properties should be private, and not public. How can I properly work around this problem, or do I have to make my properties public
EDIT
I have tried setting my properties to protected, but that does noet help as this also gives me a fatala error
Use protected and your child classes can use it. This way you can still have access to it in children classes without making it public. private means only the base class may use it.
Also, as a side note, all variables in a class without a default value will default to null
/**
* #var $authorReferrer = null
*/
protected $isAuthorReferrer;
Solve this issue. I'm still new to OOP and had a slight misunderstanding about setters and getter.
What I did is, I created a getter for each setter in the ConditionsRefer class, and instead of trying to use the properties of this class in the QueryArgumentsRefer class (which caused the initial error), I used the getters to get my info from the ConditionsRefer class inside the QueryArgumentsRefer class like
$this->conditionalReferrer->isAuthorReferrer();

Use subclass of a typesafe method parameter [duplicate]

This question already has answers here:
Override method parameter with child interface as a new parameter
(2 answers)
Closed 8 years ago.
I try to use a subclass in a method with typesafe parameter. Don't know if I'm missing something or if the solution is to just don't make the parameter typesafe. What is the cleanest design option for this situation?
Look at following code:
<?php
class MyObject {
public $name;
}
class MySubObject extends MyObject {
public $subProperty;
}
abstract class Renderer {
protected $someProperty;
public abstract function render(MyObject $obj);
}
class RendererA extends Renderer {
public function render(MyObject $obj) {
return $this->someProperty . ': ' . $obj->name;
}
}
// This is the problematic case ------|
class RendererB extends Renderer { // |
public function render(MySubObject $obj) {
return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
}
}
/* EOF */
Take a look at this:
https://github.com/symfony/Routing/blob/2.4/Router.php#L225
In this code, the Router will match a request, however it has two ways of doing this depending on the type of Matcher it has an instance of.
That case uses interfaces to differentiate between the object type, however to perform the same technique to your case try this:
class MyObject {
public $name;
}
class MySubObject extends MyObject {
public $subProperty;
}
class Renderer {
public function render(MyObject $obj) {
if($obj instanceof MySubObject) {
return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
}
return $this->someProperty . ': ' . $obj->name;
}
}
Assuming that is the only variation of functionality, you don't need an AbstractRenderer.
Since MySubObject extends MyObject it is still type safe to use the extending class as an argument.
EDIT:
This example uses a basic form of the visitor pattern.
The Renderer essentially becomes a parent manager of multiple potential handlers.
The input is handled differently by drivers added at the configuration stage depending of the input's attributes (class in this case, but there are more advanced forms of criteria).
This example can be executed outside of namespaces.
interface TemplateInterface
{
/**
* #return string
*/
public function getName();
}
interface DriverInterface
{
/**
* #param TemplateInterface $template
* #return string
*/
public function doRender(TemplateInterface $template);
}
class Renderer
{
/**
* #var array
*/
protected $drivers;
protected $property = 'Sample Property';
/**
* Constructor
*/
public function __construct()
{
$this->drivers = array();
}
/**
* #param TemplateInterface $template
* #return string
*/
public function render(TemplateInterface $template)
{
$class = get_class($template);
if(false === isset($this->drivers[$class])) {
throw new InvalidArgumentException(sprintf('Renderer Driver supporting class "%s" is not present.', $class));
}
return sprintf('%s: %s', $this->property, $this->drivers[$class]->doRender($template));
}
/**
* #param DriverInterface $driver
* #param $class
* #return $this
*/
public function addDriver(DriverInterface $driver, $class)
{
$this->drivers[$class] = $driver;
return $this;
}
}
class MyTemplate implements TemplateInterface
{
public function getName()
{
return 'my_template';
}
}
class MyOtherTemplate implements TemplateInterface
{
public function getName()
{
return 'my_other_template';
}
public function getOtherProperty()
{
return 'this is another property';
}
}
class MyDriver implements DriverInterface
{
public function doRender(TemplateInterface $template)
{
return $template->getName();
}
}
class MyOtherDriver implements DriverInterface
{
public function doRender(TemplateInterface $template)
{
if(false === $template instanceof MyOtherTemplate) {
throw new InvalidaArgumentException('OtherDriver::doRender argument must be an instance of MyOtherTemplate');
}
return sprintf('%s %s', $template->getName(), $template->getOtherProperty());
}
}
$renderer = new Renderer();
$renderer
->addDriver(new MyDriver(), 'MyTemplate')
->addDriver(new MyOtherDriver(), 'MyOtherTemplate')
;
echo '<pre>';
echo $renderer->render(new MyTemplate()).PHP_EOL;
echo $renderer->render(new MyOtherTemplate());
This is a more advanced example.
This time you don't specify the class of the template, instead you only add the driver, but this driver interface required the supports method be implemented. The renderer will loop through each driver asking them if they support the template.
The first driver to return true will render and return the template.
interface DriverInterface
{
/**
* #param TemplateInterface $template
* #return string
*/
public function doRender(TemplateInterface $template);
/**
* #param TemplateInterface $template
* #return bool
*/
public function supports(TemplateInterface $template);
}
class Renderer
{
/**
* #var array
*/
protected $drivers;
protected $property = 'Sample Property';
/**
* Constructor
*/
public function __construct()
{
$this->drivers = array();
}
/**
* Returns the rendered template, or false if the template was not supported by any driver.
*
* #param TemplateInterface $template
* #return string|false
*/
public function render(TemplateInterface $template)
{
$class = get_class($template);
foreach($this->drivers as $driver) {
if($driver->supports($template)) {
return sprintf('%s: %s', $this->property, $driver->doRender($template));
}
}
return false;
}
/**
* #param DriverInterface $driver
* #param $class
* #return $this
*/
public function addDriver(DriverInterface $driver)
{
$this->drivers[] = $driver;
return $this;
}
}

How to implement this feature in PHP?

When accessing member that doesn't
exist, automatically creates the
object.
$obj = new ClassName();
$newObject = $ojb->nothisobject;
Is it possible?
Use the magic overloading functions
You can achieve this kind of functionality with Interceptor __get()
class ClassName
{
function __get($propertyname){
$this->{$propertyname} = new $propertyname();
return $this->{$propertyname}
}
}
Though example in the previous post will work just fine also when the attribute is changed to public so you can access it from outside.
If you mean lazy initalization, this is one of many ways:
class SomeClass
{
private $instance;
public function getInstance()
{
if ($this->instance === null) {
$this->instance = new AnotherClass();
}
return $this->instance;
}
}
$obj = new MyClass();
$something = $obj->something; //instance of Something
Use the following Lazy loading pattern:
<?php
class MyClass
{
/**
*
* #var something
*/
protected $_something;
/**
* Get a field
*
* #param string $name
* #throws Exception When field does not exist
* #return mixed
*/
public function __get($name)
{
$method = '_get' . ucfirst($name);
if (method_exists($this, $method)) {
return $this->{$method}();
}else{
throw new Exception('Field with name ' . $name . ' does not exist');
}
}
/**
* Lazy loads a Something
*
* #return Something
*/
public function _getSomething()
{
if (null === $this->_something){
$this->_something = new Something();
}
return $this->_something;
}
}

Categories