2

I have a constructor that asks for a type of class, but it doesn't define that as a type hint. You are able to pass anything you want to it, and it will accept it. Is there a way to pass a class type to the constructor, and in the add() method it only accepts that type?

Currently what I have, is the ability to pass anything to the constructor such as an int, string, bool, etc. Is there a way to make it so that the constructor only accepts class types?

class Main{ protected $items = []; protected $type = ''; public function __construct($type){ $this->type = $type; } public function add($object){ if($object instanceof $this->type){ $this->items[] = $object; } } } class Test{} class Awesome{} $main1 = new Main(Test::class); $main2 = new Main(Awesome::class); // Successful: $main1->add(new Test()); // Fail: $main1->add(new Awesome()); // Successful: $main2->add(new Awesome()); // Fail: $main2->add(new Test()); 

If I were to do it in C# it would look something like this:

Main main1 = new Main<Test>(); Main main2 = new Main<Awesome>(); 

Basically it says that add() will only allow instances of Test. Is there a way to do some

12
  • So you just want to make sure that the class which you pass to the constructor exists? Commented Aug 26, 2015 at 18:45
  • I basically want to define what type of object types can be passed to add(), and the problem with using MyClass::class as a parameter is that the value of that is a string, meaning I have to allow for strings to get passed in, so new Main('bool') is a valid, but nothing is an instanceof bool Commented Aug 26, 2015 at 18:49
  • 1
    Something like this: public function __construct($type){ if(class_exists($type)) $this->type = $type; else $this->type = "default"; } ? Commented Aug 26, 2015 at 18:50
  • I feel that should be the correct answer @Rizier123, although I'm not sure how it works with autoloading (if it tries to load before or after). Commented Aug 26, 2015 at 18:59
  • 1
    @felipsmartins I am using Rizer123's comment which works perfectly without having to create a Reflection Commented Aug 26, 2015 at 20:01

4 Answers 4

2

Php doesn't support template like declarations like e.g. c++.

The best way you may be able to achive this is by passing a lambda which then in return gets used in order to validate the passed parameter in add.

<?php class Test { private $validator = null; public function __construct($validator) { $this->validator = $validator; } public function add($value) { $func = $this->validator; $validated = $func($value); echo $validated ? 'OK' : 'NG'; } } $obj = new Test(function($value) { return is_int($value); }); $obj->add(11); $obj->add('string'); 

Another possibility would be to pass the type e.g. "ClassName" in your constructor and use get_class() and gettype() for the validation.

In the future there may be smarter solutions since you'll be able to write anonymous classes but I haven't really thought about that but in the end they would work similarly to lambdas.

Sign up to request clarification or add additional context in comments.

Comments

1

Basically it says that add() will only allow instances of Test.

It's possible to achieve this in PHP by simply adding the type before the argument name in the function definition (similar with C/C++/C# types):

class Main { protected $items = []; public function add(Test $object) { $this->items[] = $object; } } 

PHP 5 accepts classes, interfaces, array and callable as type hints. If Test is a class then Main::add() accepts objects of class Test and its children. If Test is an interface, then the method Main::add() accepts objects that implement Test or one of its children.

PHP 7 (coming soon to a server near you) introduces type hinting for scalar types too.

PHP does not support anything similar with C++ templates or C# generics. If you want to create a class that works with objects of type A and another class that has identical behaviour but works with objects of type B you have several options but none of them is as elegant as the templates/generics:

  1. Create two classes having identical behaviour, one for objects of type A, another for objects of type B; use different type hints (A and B) in the arguments lists of the methods of the two classes to enforce the separation - not scalable;
  2. Something similar to your code, use the allowed class name as a string property and check it on any operation; you can also validate the argument of the constructor using class_exists() - the code becomes cluttered with tests and less readable;
  3. Use OOP polymorphism; extend both A and B from the same class T or, even better, make A and B implement the same interface I. A PHP interface can be empty, it doesn't need to declare anything; empty interfaces used just for type hinting are common practice in PHP.

    Then write a single class Main and use I as type hint for all its methods that accept objects. It will accept objects of both types A and B but if you also declare functions in I (and implement them in A and B, of course) then use them in Main you can be sure nothing breaks (I becomes a contract between Main and the objects its accepts as arguments for its methods).

I would choose option #3 because it gets the most help from the interpreter; it verifies the type of the arguments on each function call that has type hints and triggers a recoverable fatal error (in PHP 5) or throws an exception (in PHP 7).

Also some IDEs and static code analysis tools can validate the calls without running the code and help you fix it.

Comments

1

Is there a way to make it so that the constructor only accepts class types?

Nope!
It is not possible in PHP. Not like C#, at least.
You need either set a type hint or set any types. However, there's a closer solution in order to accept only class when instancing a class: Using ReflectionClass!

class Main { protected $items = []; protected $type = null; public function __construct($type) { $reflector = new ReflectionClass($type); $this->type = $reflector->getName(); # or: $this->type = $type; } public function add($object) { if($object instanceof $this->type) { $this->items[] = $object; } } } 

As ReflectionClass contructor argument only accpets a string containing the name of the class to reflect, you can take advantage that, so passing scalars strings will cause an exception.

$main = new Main(Test::class); # Okay! $main = new Main('Test'); # Okay! 

However

$main = new Main('bool'); // Results # PHP Fatal error: Uncaught exception 'ReflectionException' # with message 'Class bool does not exist' in ... 

Comments

0

Change your constructor to this:

public function __construct(Type $type){ $this->type = $type; } 

This is based on the assumption that $type is an instance of Type.

5 Comments

Why the downvote? I'm new to php, but isn't this answer correct?
It isn't a correct answer because the question is asking how to type hint dynamically.
@MinusFour Oh, now that I see his C# examples, it seems like he wants something like generics
That will only allow for Type, it wont allow for Awesome or Amazing as class types
@GetOffMyLawn Unless all classes implements the Interface Type.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.