In java we have Object type which can be used to cast to specific class type.
How do we do this in PHP ?
Regards,
Mithun
Generic objects in PHP are instances of stdClass. However it is not a base class, meaning classes don't inherit from it unless you specify extends stdClass in the class declaration.
Typecasting something to (object) in PHP yields a stdClass. For example:
$a = array('foo' => 'bar');
$o = (object) $a;
var_dump($o instanceof stdClass); // bool(true)
var_dump($o->foo); // string(3) "bar"
In PHP there is no concept of upcasting and downcasting. You can type hint for superclasses or interfaces but that's about it. An object is always recognized as an instance of whatever class you construct it as, e.g. with new.
As someone with both php and Java experience, I can say that there is no equivalent in php to Java's object. In Java every object extends Object class, in php a class you make does not extend anything by default. The Java's Object has some convenient methods like toString(), hashCode(), getClass() and a few more that would not be relevant to php.
I like these standard methods in Java, they are very convenient for debugging and logging, so I miss then in php. That's why I usually create my own base class in php and make every class extend it. Then it becomes easy to log and debug, you can just $logger->log($obj);
and it will use magic __toString(), dumping at least basic info about the object.
The bottom line is that you can make your own base class in php and then just make every class extend it.
My usual base class:
/**
* Base class for all custom objects
* well, not really all, but
* many of them, especially
* the String and Array objects
*
* #author Dmitri Snytkine
*
*/
class BaseObject
{
/**
* Get unique hash code for the object
* This code uniquely identifies an object,
* even if 2 objects are of the same class
* and have exactly the same properties, they still
* are uniquely identified by php
*
* #return string
*/
public function hashCode()
{
return spl_object_hash($this);
}
/**
* Getter of the class name
* #return string the class name of this object
*/
public function getClass()
{
return get_class($this);
}
/**
* Outputs the name and uniqe code of this object
* #return string
*/
public function __toString()
{
return 'object of type: '.$this->getClass().' hashCode: '.$this->hashCode();
}
}
This would be stdClass (which is not a base class ftm).
Note that you can only typecast to stdClass not any other classes, e.g. this will work
$obj = (object) array( 'foo' => 'bar' );
but not
$observer = (Observer) new Subject;
Quoting the manual:
If an object is converted to an object, it is not modified. If a value of any other type is converted to an object, a new instance of the stdClass built-in class is created. If the value was NULL, the new instance will be empty. Arrays convert to an object with properties named by keys, and corresponding values. For any other value, a member variable named scalar will contain the value.
Well, unless you are willing to utilize black magic and unreliable hacks, such as those given in:
Cast the current object ($this) to a descendent class
Ciaran answer is helpful,
Despite what the other two answers
say, stdClass is not the base class
for objects in PHP. This can be
demonstrated fairly easily:
class Foo{}
$foo = new Foo();
echo ($foo instanceof stdClass)?'Yes':'No';
it outputs 'N' stdClass is instead just a
generic 'empty' class that's used when
casting other types to objects. I
don't believe there's a concept of a
base object in PHP
Since PHP does not have type declarations, there is no need for a global base class. There isn't really anything to do: just declare variables and use them.
Related
I've a question. During a PHP class development I've set in the constructor a class property like this:
public function __construct() {
$this->a = 'ABC';
}
Now my IDE told me that the property was declared dynamically and I should add this property to my class. Now I have two options:
A variable at the top of the class:
protected string $a = '';
Or an annotation in the class doc:
/**
* Class ABC
*
* #property string a
*
* #package Johnny
*/
class ABC {
So whats the difference here and which one should I use? Sometimes I have an error that a property is not defined when using the annotation above so the fix was a protected or private variable.
Thanks for you help!
A protected property can only be accessed from methods in the same class or subclasses. Declaring the property protected prevents it from be assigned or read outside the class.
Adding the #property annotation in a docblock simply lets the IDE know that the property exists. It will use this to suppress warnings like the one you got, and do property name completion, just like it does for properties that are declared explicitly in the class definition. It has little effect on the way PHP itself deals with the property; access control is specified by whether the property is declared public, private, or protected.
You can also create public and private properties in the class definition.
public string $a;
private string $a;
Public means the property can be accessed from outside the class (just like your dynamically-created property), private means it can only be accessed from the class itself (not subclasses).
If you don't declare a property explicitly, and create it dynamically using an assignment, it's automatically public. If you want to prevent this, see Is there a way to disable adding properties into a class from an instance of the class?
This is an explicit declaration of a property type:
protected string $a = '';
This is an internally enforced restriction. If you try to assign something other than a string to $a, you'll get a TypeError exception. Declaring a property type hint like this is a feature that was introduced in 7.4. You should prefer this method if you know your environment will be 7.4+.
This is a docblock:
/**
* #var string
*/
public $a;
It's a comment that has no effect on the runtime of your script. It exists only so that IDEs like PHPStorm or NetBeans can provide hints in your development environment. If you use explicit type hints like above, then these docblock declarations are redundant and unneeded. I.e., there's no need to do this:
/**
* #var string
*/
public string $a;
Note there are quite a few libraries that use comment docblocks to provide special runtime meaning. These libraries use reflection to parse docblock comments on the fly and react to them. In other words, PHP itself is not affected by docblock comments, but your script has the ability to look at them, and is thus capable of basing conditions off them. For example, with Doctrine, you can use docblock comments to explain what your database fields look like:
/**
* #ORM/Column(type="int")
* #ORM/Id
*/
protected $id;
What is the best way to handle scalar type & return type declarations for namespaced instances?
I use namespaced classes for everything and use the 'use' aliases at the top of the class for all classes that will be used in the file.
This is an example without using scalar type & return type declarations:
file 1
use Model\ExampleA\ClassA;
use Model\ExampleB\ClassB;
$classA = new ClassA();
$classB = new ClassB();
$result = $classB->action($classA);
file 2
namespace Model\ExampleB;
class ClassB
{
/**
* #param ClassA $classA The ClassA instance.
* #return ClassA Returns the ClassA instance.
*/
public function action($classA)
{
return $classA;
}
}
Now if using scalar type & return type declarations, file2 will instead be:
namespace Model\ExampleB;
use Model\ExampleA\ClassA;
class ClassB
{
/**
* #param ClassA $classA The ClassA instance.
* #return ClassA Returns the ClassA instance.
*/
public function action(classA $classA) : classA
{
return $classA;
}
}
So if using scalar or return type declarations for namespaced instances, I have to use the 'use' alias again at the top of file2 just for the type hinting to work.
I would much prefer to use the 'object' data type for scalar type or return type for namespaced instances and keep the #param and #return data types in the docblock as the instances like this:
namespace Model\ClassB;
class ClassB
{
/**
* #param ClassA $classA The ClassA instance.
* #return ClassA Returns the ClassA instance.
*/
public function action(object $classA) : object
{
return $classA;
}
}
Is this OK? Or is my other example the best way to do this?
I don't really understand your problem.
If both classes (ClassA and ClassB) are in the same directory, no need to use the use statement.
Scalar type will indicate to your colleague that your method need an instance of this class. This is useful, it documents your code and prevent bugs. See that as a guideline.
Of course your comment will indicate the same thing but the problem is: comments can lie. If somebody modify your code and not your comment to accept an object ClassC (for example) instead of ClassA, it will bring quite some confusion. Imagine a whole codebase like that.
I saw misleading comment creating nasty bugs. More than once.
A use statement at the top of your file doesn't cost anything. Even better: if you have a lot of them, it's a good indicator that your class should be split to respect the SRP. Plus it describes your dependencies. It's always good to know what you can affect when you modify your code.
Conclusion: I would suggest that you use scalar and return types and don't be afraid by the use statement.
To explain a bit why namespaces were introduced in PHP:
Namespaces allow you to have different classes with the same name located in different filepath without having any name collision. If you have two classes called ClassA, how the interpreter can know what ClassA you want to use?
Since your two classes with the same name need to be located in different folders, your namespaces will be different. PHP will know what class to use. It has nothing to do with class instantiation.
Before the namespaces you had some nasty class names like Mage_Application1_Entity_Model.php which was describing the filepath. Ugly as hell.
I often give objects static methods and properties that do not require the object to be initialized. For example:
class SomeObject {
public function __construct($object_id) {
$this->loadProperties($object_id);
}
public static function getSomeStaticString() {
return "some static string";
}
}
Now we subclass these objects and have some sort of controller that returns an object class string under certain circumstances where the object should not yet be initialized. For example:
class SomeObjectController {
public function getSomeObjectWithTheseProperties(array $properties) {
if($properties[0] === "somevalue") {
if($properties[1] === "someothervalue") {
return SomeSubclassObject::class;
}
return SomeObject::class;
}
return NULL;
}
}
At times I might want to call the static function SomeObject::getSomeStaticString() without actually initializing the object (because that would involve an unneeded database fetch). For instance:
$controller = new SomeObjectController;
$properties = array("somevalue", "someothervalue");
$object_class = $controller->getSomeObjectWithTheseProperties($properties);
echo $object_class::getSomeStaticString();
Question: can I somehow tell PhpStorm, preferably through phpDoc, that $object_class is a class string of a subclass of SomeObject?
If I tell my IDE it's a string, it will notify me getSomeStaticString() is an invalid method. On the other hand, if I tell my IDE it's an instance of SomeObject, it thinks I can access regular non-static methods and properties, which I can't.
PHPStan uses the class-string PHPDoc type for this. It has been supported by PHPStorm since 2020.3, but it seems to work properly (with all autocompletion available) as of 2021.2, when they added support for Generics.
In your case the return type annotation would be #return class-string<SomeObject>.
/** #var SomeObject $object_class */
$object_class = $controller->getSomeObjectWithTheseProperties($properties);
Sorry, no other way as to tell that it's an instance of SomeObject.
... if I tell my IDE it's an instance of SomeObject, it thinks I can access regular non-static methods and properties, which I can't.
So? Just do not access non-static methods and properties.
UPDATE 2021-10-26: Since v2020.3 PhpStorm has Psalm Support and PHPStan Support plugins bundled. They support most of the syntax/types supported by those tools (you can check PhpStorm Issue Tracker here for all the tickets for those plugins (resolved and still pending)).
With those tools you can use class-string pseudo type:
https://psalm.dev/docs/annotating_code/type_syntax/scalar_types/#class-string-interface-string
https://phpstan.org/writing-php-code/phpdoc-types#class-string
If you want perfect type hinting you may create an Interface which only lists the static methods (and properties) of your class, and use that Interface as the type hint for your classname string returned from SomeObjectController::getSomeObjectWithTheseProperties().
It adds overhead to the maintenance to ensure the Interface and the classes are kept in sync, but if you need those type hints to work properly, this is the way.
You can also use this workaround:
/**
* #return string|SomeObject
*/
public function getSomeObjectClass()
{
return SomeObject::class;
}
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