I've been working on code that's intended to be used with objects, without really caring what the kind of object is. I wanted to type hint that the method being written expected an object of any type, but ran into some difficulty.
I tried function myFunc (object $obj) and function myFunc (stdClass $obj) but both of these generated errors when I tried to pass objects in:
Catchable fatal error: Argument 1 passed to MyClass::MyFunc() must be an instance of object, instance of ObjectActualClass given
The same happened with stdClass as well
What am I missing? I thought that all classes that didn't explicitly inherit from another class inherited from stdClass, meaning that the base class of every class in PHP would be stdClass. Is this not the case?
stdClass is NOT a base class! PHP classes do not automatically inherit from any class. All classes are standalone, unless they explicitly extend another class. PHP differs from many object-oriented languages in this respect.
The best way to enforce this would be to create a degenerate interface called Object. A degenerate interface means it has no defined methods.
interface Object {
// leave blank
}
Then in your base classes, you can implement Object.
class SomeBase implements Object {
// your implementation
}
You can now call your function as you wanted to
function myFunc (Object $obj);
myFunc($someBase);
If you pass any object which inherits from your Object interface, this type hint will pass. If you pass in an array, int, string etc, the type hint will fail.
Well it only took eight years, but this will soon be possible: PHP 7.2 introduces the object type hint! As I write this, it's currently in the RFC stage, and is due to be released in November.
Update, 30th November: PHP 7.2 has been released
RFC: Object typehint
Discussion
This behaves exactly as you might expect:
<?php
class Foo {}
class Bar {}
function takeObject(object $obj) {
var_dump(get_class($obj));
}
takeObject(new Foo);
takeObject(new Bar);
takeObject('not an object');
Will result in:
string(3) "Foo"
string(3) "Bar"
Fatal error: Uncaught TypeError: Argument 1 passed to takeObject() must be an object, string given, called in...
See https://3v4l.org/Svuij
One side-effect of this is that object is now a reserved word, which unfortunately renders #Gaz_Edge's existing solution above broken. Fortunately, all you have to do to fix it is delete the interface.
Although there is no type hinting for objects, you can use:
if (!is_object($arg)) {
return;
}
There is no base class that all objects extend from. You should just remove the typehint and document the expected type in the #param annotation.
There is no built-in mechanism to do this without requiring all users of your interface to extend a specified class. But why would you want to do this anyway? What do all object types have in common that's enough to make them suitable input for your API?
In all probability you wouldn't gain anything even if able to type hint like this. On the other hand, type hinting a parameter to implement an interface (such as Traversable) would be much more meaningful.
If you still want something akin to type hinting, the best you can do is substitute a runtime check with is_object on the parameter.
As of php 7.2 this feature has now been implemented. you can type hint for any object now.
function myFunc(object $myObject): object {
return $myObject;
}
You can review this in the official documentation
Typehint for stdClass works since PHP 5.3+ (if I am not wrong).
Following is valid code using typehint for stdClass construct:
Example test.php:
class Test{
function hello(stdClass $o){
echo $o->name;
}
}
class Arg2 extends stdClass{
public $name = 'John';
function sayHello(){
echo 'Hello world!';
}
}
$Arg1 = new stdClass();
$Arg1->name = 'Peter';
$Arg2 = new Arg2();
$Arg2->sayHello();
$test = new Test();
// OK
$test->hello($Arg1);
$test->hello($Arg2);
// fails
$test->hello(1);
Prints out:
Hello world!
Peter
John
Catchable fatal error: Argument 1 passed to Test::hello() must be an instance of stdClass, integer given, called in test.php on line 32 and defined in test.php on line 5
You could do something like this:
function myFunc ($obj)
{
if ($obj instanceof stdClass) { .... }
}
Related
I've been working on code that's intended to be used with objects, without really caring what the kind of object is. I wanted to type hint that the method being written expected an object of any type, but ran into some difficulty.
I tried function myFunc (object $obj) and function myFunc (stdClass $obj) but both of these generated errors when I tried to pass objects in:
Catchable fatal error: Argument 1 passed to MyClass::MyFunc() must be an instance of object, instance of ObjectActualClass given
The same happened with stdClass as well
What am I missing? I thought that all classes that didn't explicitly inherit from another class inherited from stdClass, meaning that the base class of every class in PHP would be stdClass. Is this not the case?
stdClass is NOT a base class! PHP classes do not automatically inherit from any class. All classes are standalone, unless they explicitly extend another class. PHP differs from many object-oriented languages in this respect.
The best way to enforce this would be to create a degenerate interface called Object. A degenerate interface means it has no defined methods.
interface Object {
// leave blank
}
Then in your base classes, you can implement Object.
class SomeBase implements Object {
// your implementation
}
You can now call your function as you wanted to
function myFunc (Object $obj);
myFunc($someBase);
If you pass any object which inherits from your Object interface, this type hint will pass. If you pass in an array, int, string etc, the type hint will fail.
Well it only took eight years, but this will soon be possible: PHP 7.2 introduces the object type hint! As I write this, it's currently in the RFC stage, and is due to be released in November.
Update, 30th November: PHP 7.2 has been released
RFC: Object typehint
Discussion
This behaves exactly as you might expect:
<?php
class Foo {}
class Bar {}
function takeObject(object $obj) {
var_dump(get_class($obj));
}
takeObject(new Foo);
takeObject(new Bar);
takeObject('not an object');
Will result in:
string(3) "Foo"
string(3) "Bar"
Fatal error: Uncaught TypeError: Argument 1 passed to takeObject() must be an object, string given, called in...
See https://3v4l.org/Svuij
One side-effect of this is that object is now a reserved word, which unfortunately renders #Gaz_Edge's existing solution above broken. Fortunately, all you have to do to fix it is delete the interface.
Although there is no type hinting for objects, you can use:
if (!is_object($arg)) {
return;
}
There is no base class that all objects extend from. You should just remove the typehint and document the expected type in the #param annotation.
There is no built-in mechanism to do this without requiring all users of your interface to extend a specified class. But why would you want to do this anyway? What do all object types have in common that's enough to make them suitable input for your API?
In all probability you wouldn't gain anything even if able to type hint like this. On the other hand, type hinting a parameter to implement an interface (such as Traversable) would be much more meaningful.
If you still want something akin to type hinting, the best you can do is substitute a runtime check with is_object on the parameter.
As of php 7.2 this feature has now been implemented. you can type hint for any object now.
function myFunc(object $myObject): object {
return $myObject;
}
You can review this in the official documentation
Typehint for stdClass works since PHP 5.3+ (if I am not wrong).
Following is valid code using typehint for stdClass construct:
Example test.php:
class Test{
function hello(stdClass $o){
echo $o->name;
}
}
class Arg2 extends stdClass{
public $name = 'John';
function sayHello(){
echo 'Hello world!';
}
}
$Arg1 = new stdClass();
$Arg1->name = 'Peter';
$Arg2 = new Arg2();
$Arg2->sayHello();
$test = new Test();
// OK
$test->hello($Arg1);
$test->hello($Arg2);
// fails
$test->hello(1);
Prints out:
Hello world!
Peter
John
Catchable fatal error: Argument 1 passed to Test::hello() must be an instance of stdClass, integer given, called in test.php on line 32 and defined in test.php on line 5
You could do something like this:
function myFunc ($obj)
{
if ($obj instanceof stdClass) { .... }
}
Is there a way to create a mock class with PHPUnit which I can then create a new instance of by using its class name?
I have an interface which defines two methods. Something like:
interface FooInterface {
function getA();
function getB();
}
I then have another class which accepts a class name, creates an instance of that class, checks if it is an instance of what it expects (FooInterface) and then calls two methods on that class to get some information.
class FooInfo {
protected $a;
protected $b;
public function __construct($fooClass) {
$foo = new $fooClass;
if (!($foo instanceof FooInterface)) {
throw new \Exception();
}
$this->a = $foo->getA();
$this->b = $foo->getB();
}
}
I know how to mock an object just fine. The problem is, since this class accepts a class name, not an object (it is a part of a Manager which creates instances of the given class as needed), I can't use a normal mock object.
I tried to make a mock object then use that class name. It seems to create the object just fine, and even seems to have the functions I mocked out. However, it doesn't seem to follow the will($this->returnValue('myValue')) portion I set up later.
public function testConstruct()
{
$foo = $this->getMockForAbstractClass('Foo', array('getA', 'getB'));
$foo->expects($this->any())->method->('getA')->will($this->returnValue('a'));
$foo->expects($this->any())->method->('getB')->will($this->returnValue('b'));
$copyClass = get_class($foo);
$copy = new $copyClass();
// Passes
$this->assertTrue(method_exists($copy, 'getA');
// Fails, $copy->getA() returns null.
$this->assertEquals($copy->getA(), $foo->getA());
}
So, it does have the functions which were mocked, but they all return null.
Any ideas?
To use the new keyword in the constructor of a class is a rather bad habit exactly for the reasons you're experiencing now, even given your flexible use case.
Your test will not work because the mock you create will never be used, since your class will always create a real instance of the injected classname.
That said, what you want to do can be done with padraic's excellent mocking library mockery!
What you need is an 'instance mock':
$mock = \Mockery::mock('overload:MyNamespace\MyClass');
You can define your expectations on that mock which will be transferred to the real object, as soon as it is instantiated.
Integrating mockery with phpUnit is easy and well explained in the readme of the project.
And btw. each unit test should optimally make one assertion only!
The methods getA and getB return null because you have not specified what they should return. You did specify that for the abstract $foo by calling some methods. There's no way around that.
Since it's hard (if not impossible) to test the function, I would rewrite the code itself. Testing would be easy if your constructor would require a class instance rather than a class name. If you also must accept strings, you could write a few lines to check for a string input and create a class if a string is provided.
Fairly new to classes in PHP, so bear with me.
class processRoutes
{
//Next line works
private $doc = "works as as string";
//Next line does not work, "Parse error: syntax error, unexpected T_NEW"
private $doc = new SimpleXMLElement('routingConfig.xml', null, true);
private function getTablenames()
{
//do stuff
}
}
I'm trying to ultimately utilize the SimpleXMLElement object within my class, among several private functions. What is the correct method for going about this, and why doesn't my current method work?
You need to do this in your constructor, as this can't be evaluated at this stage of script parsing. 'Simple' values, as strings, bools and numeric values will work though.
class processRoutes
{
//Next line works
private $doc = "works as as string";
private $doc;
public function __construct()
{
$this->doc = new SimpleXMLElement('routingConfig.xml', null, true);
}
// ....
}
You're attempting to initialise a property with an object instance, but you're only allowed to initialise variables with constants that can be determined at "compile time".
From PHP Manual - Properties
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.
Any initialisation that depends on "run time" knowledge will need to be executed either
in a constructor (refer #Dan-Lee's answer on how to implement this) that operates on $this->doc,
from within eg a "initialiser" function called from your constructor (you might do this to keep the "initialisation" steps distinct from other "real work" that might be done from your constructor), or
manually by your class's consumer. The consumer may operate
directly on the properties or your object (eg myProcessRoutes->doc = 'some other string'),
via a function call to your object eg myProcessRoutes.initialise_doc('some other string'), or
via a setter on your object - I'll let you research those, as I've not used these in PHP! ;-)
(Although it's arguable/philosophical if these approaches that occur later than instantiation/constructor are really initialisation).
The point of class constructors/destructors is to provide a "hook" by which the object instance can be initialised/disposed as required.
You might just need to create some specific new instances as per your example, in which case you don't need to accept any input to the constructor from the consumer.
Or, you might need to accept some values in order for your class to be set up properly. This is exactly what's happening in your example code above, when you're calling
private $doc = new SimpleXMLElement('routingConfig.xml', null, true);
(that is, you're passing the values of 'routingConfig.xml', null and true in to your new instance of SimpleXMLElement, so that this instance's constructor can initialise the instance using the values you passed to it, ready for use).
anytime you want to reference a class's variable, use the keyword $this
public function getTablenames()
{
$my_new_variable = $this->doc; // Transfers the $doc variable
}
When I call this function, and add a constructor to my class, all my class properties are already set. How is that possible -- I thought you had to construct a class before you could set its properties?
I guess that PDO uses some internal code to create an object without invoking the constructor. However it is possible to instance a new object without calling the constructor even in pure PHP, all you have to do is to deserialize an empty object:
class SampleClass {
private $fld = 'fldValue';
public function __construct() {
var_dump(__METHOD__);
}
// getters & setters
}
$sc = unserialize(sprintf('O:%d:"%s":0:{}', strlen('SampleClass'), 'SampleClass'));
echo $sc->getFld(); // outputs: fldValue, without calling the construcotr
As of PHP 5.4.0+ ReflectionClass::newInstanceWithoutConstructor() method is available in reflection API.
In php, any array can be cast to an object. My assumption is pdo creates an associative array an then jus casts it. I dont know if he constuctor is called on cast...
Actually, a cast isnt the right word, a conversion occurs behind the scenes. Read this. Blurb on what happens: http://php.net/manual/en/language.types.object.php
Am I missing something or there really is no support for generic object type hinting in PHP 5.x?
I find it really strange that hinting arrays is supported while hinting objects is not, at least not out of the box.
I'd like to have something like this:
function foo(object $o)
Just as we have:
function foo(array $o)
Example of possible use: methods of an objects collection class.
Workaround: using an interface "Object" implemented by all classes or extending all classes from a generic class "Object" and writing something like this:
function foo(Object $o)
Well, that just ain't cute.
Using stdClass as the type hint doesn't work:
Catchable fatal error: Argument 1
passed to c::add() must be an instance
of stdClass, instance of b given
Since type hinting should make the client code adapt to your API, your solution with accepting interfaces seems just about right.
Look at it this way: yourMethod(array $input) gives yourMethod() an array to use, thereby you know exactly which native functions that applies and can be used by yourMethod().
If you specify your method like: yourSecondMethod(yourInterface $input) you'd also know which methods that can be applied to $input since you know about/can lookup which set of rules that accompanies the interface yourInterface.
In your case, accepting any object seems wrong, because you don't have any way of knowing which methods to use on the input. Example:
function foo(Object $o) {
return $o->thisMethodMayOrMayNotExist();
}
(Not implying that syntax is valid)
No, it can't be done. I wasn't missing anything.
I feel your pain, but I can't find a way of doing it either.
Despite what a number of other posters have said, it makes perfect sense to want 'Object' type hinting; they just haven't considered a scenario that requires it.
I am trying to do some work with the reflection API, and because of that I don't care what class is passed to my function. All I care is that it's an object. I don't want an int, a float, a string or an array. I want an object. Given that reflection is now part of PHP, it definitely makes sense to have object type hinting.
You cannot just say "object" when type casting an object... you must define WHICH object you are expecting.
From: http://php.net/manual/en/language.oop5.typehinting.php
class MyClass
{
/**
* A test function
*
* First parameter must be an object of type OtherClass
*/
public function test(OtherClass $otherclass) {
echo $otherclass->var;
}
/**
* Another test function
*
* First parameter must be an array
*/
public function test_array(array $input_array) {
print_r($input_array);
}
}
// Another example class
class OtherClass {
public $var = 'Hello World';
}
The best way to enforce this would be to create a degenerate interface called Object. A degenerate interface means it has no defined methods.
interface Object {
// leave blank
}
Then in your base classes, you can implement Object.
class SomeBase implements Object {
// your implementation
}
You can now call your function as you wanted to
function myFunc (Object $obj);
myFunc($someBase);
If you pass any object which inherits from your Object interface, this type hint will pass. If you pass in an array, int, string etc, the type hint will fail.
Here's another example where it is required...
I've created a class to implement record locking. Records being one of a number of different object types. The locking class has several methods which require an object (the one to be locked) but don't care what type of object it is.
E.g.
public static function lockRecord($record, User $user, $timeout=null)
{
if(!is_object($record)) throw new \InvalidException("Argument 1 must be an object.");
$lock=new Lock();
$lock->setRecord($record);
$lock->setUser($user);
$lock->setTimeout($timeout);
$lock->activate();
return($lock);
}
You'll see that my solution was to use is_object() and throw an exception, but I'd far rather be able to do it with type hinting instead.
Ok, so not the end of the world, but I think it's a shame.
Objects in php are not subclasses of some StdClass or Object as it is in other OOP languages. So there is no way of type hinting the object. But I see your point because sometimes you want to make sure that the Object is being passed, so I guess the only way is to raise the issue manually.
public function yourFunction($object){
if(is_object($object)){
//TODO: do something
}else{
throw new InvalidArgumentException;
}
}
As of php 5.4 there is also a type hint callable.
See php manual http://php.net/manual/en/language.types.callable.php
Why would you want to hint object when you can hint an actual class name instead - this would be much more useful. Also remember that you can't hint int,float, bool, string or resource either.
public static function cloneObject($source)
{
if ($source === null)
{
return null;
}
return unserialize(serialize($source));
}
This is where you would need it.
Since PHP 7.2 you can finally declare the way you wanted:
function functionName(object $someObjectVariable)
See the table named "Valid types" at this page:
https://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration