I ran into an issue with parent/child classes in different namespaces that I'm wondering if it's my improper use, or if this is a quirk of PHP namespaces:
<?php
namespace myVendorName;
class MyParentClass {
protected $args = array();
function factory($class) {
return new $class($this->args);
}
}
?>
<?php
namespace myVendorName\subPackage;
class foo {
}
class bar extends \myVendorName\MyParentClass {
function myFunc() {
var_dump(new foo()); // Works (we're in the same namespace)
var_dump($this->factory('foo')); // Doesn't work (no such class as myVendorName\foo)
var_dump($this->factory('subPackage\foo')); // Works
}
}
?>
The above code doesn't work as I'd expect. In the \myVendorName\subPackage\bar->myFunc() method, I'd like to get a \myVendorName\subPackage\foo class. If I just create it there, I can create it by only its class name since the namespace is the same. However, those classes in reality are more complex, and there's a factory method to create them. The factory method is universal, and is defined in the root vendor namespace. And the bar class doesn't overwrite that factory method; it simply inherits it. However, when referring to a class object by name, it seems to still operate in the parent's namespace, rather than having the method truly get inherited by the child and operate in the child's namespace.
Is there a way for a parent class method that's going to be inherited directly to use the child's namespace, or at least peek at what it is? Or does each of my child classes have to overwrite the factory method to fix the namespace, and call the parent method within themselves?
The answer, as Passerby indicated is that PHP does have a __NAMESPACE__ constant that is filled with the current namespace (though no leading slash). So modifying the factory function to:
function factory($class) {
if ($class[0] != '\\') {
$class = '\\'.__NAMESPACE__.$class; // If not a fully-qualified classname, prepend namespace
}
return new $class($args);
}
works as expected (there's an example of this in the PHP documentation)
Related
In php, is it possible for a method in a parent class to use an alias in a child class from within an instance of the child class?
Parent class:
class ParentClass
{
public function getNewFoo()
{
return new Foo();
}
}
Child class:
use My\Custom\Path\To\Foo;
class ChildClass extends ParentClass
{
}
Code:
$child = new ChildClass();
return $child->getNewFoo();
I would like this code to return a new instance of My\Custom\Path\To\Foo() rather than a new instance of Foo().
I realize I can store the desired path to Foo in a class property, or I can simply instantiate the desired Foo in the child class. However, both of these seem redundant considering the path is already stored in the use statement in the child class.
You're asking a lot of PHP here. It's supposed to know that your use statement is going to impact something in a completely different class, in a completely different file, just because?
If PHP did that by default it would cause a lot of very strange problems for people. Re-define the method, or as you point out, store that property in the class itself.
I think a lot of developers would expect, or at least prefer that PHP behave the way it does.
It sounds like what you need here is a factory function that can be redefined in the subclass to behave differently, or in other words, that getNewFoo() should be overridden in the subclass to use the alternate version.
Background
In a project with a PHP 5.6 runtime, I need to work around some third-party code. This code will not be changed by the vendor, nor will it be removed from the codebase.
Specifically, the third-party code (let's call its namespace Theirs) contains a class (\Theirs\BaseClass) whose constructor instantiates another class (\Theirs\Detector).
BaseClassTheirs.php:
<?php namespace Theirs;
class Detector {
public function __construct() {
print "Theirs\n";
}
}
class BaseClass {
public function __construct() {
$detector = new Detector();
}
}
I do not want BaseClass to instantiate \Theirs\Detector. Instead, I want BaseClass to instantiate a different Detector class, from a different namespace (Mine) that is outside of the third-party's control.
In all other respects, though, I want BaseClass to behave as it does in the third-party code, including if the vendor later adds additional functionality to \Theirs\BaseClass. (I'll call this property "non-fragility" and the lack of it "fragility".) As such, it seems sensible for me to create my own \Mine\BaseClass as a child of \Theirs\BaseClass, inheriting everything from it.
If I take the fragile, non-DRY approach of copy-pasting \Theirs\BaseClass's constructor into \Mine\BaseClass, then \Mine\Detector is instantiated, as I desired:
BaseClassMine.php:
<?php namespace Mine;
include "BaseClassTheirs.php";
class Detector {
public function __construct() {
print "Mine\n";
}
}
class BaseClass extends \Theirs\BaseClass {
public function __construct() {
$detector = new Detector();
}
}
\\ Prints "Mine"
$obj = new BaseClass();
However, if I change this into a DRY, non-fragile approach by removing the duplicated code so that \Mine\BaseClass invokes exactly the same constructor, but as inherited from its parent rather than being copy-pasted, then \Theirs\Detector gets invoked, which is not what I want:
BaseClassMine.php:
<?php namespace Mine;
include "BaseClassTheirs.php";
class Detector {
public function __construct() {
print "Mine\n";
}
}
use \Mine\Detector;
class BaseClass extends \Theirs\BaseClass {
}
\\ Prints "Theirs"
$obj = new BaseClass();
This happens regardless of whether the file contains a use \Mine\Detector; line, as above.
Question
How can I get the best of both approaches?
I.e. how can I invoke \Theirs\Baseclass's constructor from \Mine\Baseclass's constructor in order to have it invoke \Mine\Detector, as though \Theirs\Baseclass's constructor had simply been copy-pasted into \Mine\Baseclass's context, but without actually copy-pasting it and introducing the corresponding fragility?
For instance, is there a good way to use reflection or some other introspective technique to dynamically read the parent's constructor and to "paste" it at runtime into the child class?
Related but not identical questions
late static binding | without modifying parent class with static keyword
Is there any way to set a property before calling a constructor?
I have an object with some protected fields and a method that uses them. The method doesn't do exactly what I need it to do, but I cannot change the original code since I am writing an add-on.
Is it somehow possible to extend the class and override the method so that I could call it on predefined objects of the original class? I thought about monkey patching but apparently it is not implemented in php.
You can override a method by extending the parent class, initiating the new class instead of the parent class and naming your method exactly the same as the parent method, that was the child method will be called and not the parent
Example:
class Foo {
function sayFoo() {
echo "Foo";
}
}
class Bar extends Foo {
function sayFoo() {
echo "Bar";
}
}
$foo = new Foo();
$bar = new Bar();
$foo->sayFoo() //Outputs: Foo
$bar->sayFoo() //Outputs: Bar
I hope below stategy will be works. asume that class is Foo and method is bar(). for override bar() method you have to make customFoo class as mentioned below.
class CustomFoo extends Foo{
public function bar(){
parent::bar();
}
}
I dont know actually what you need because you dont have explained in detail. Still I have tried my best. :)
Try creating a child class that extends the base or parent class that the object currently derives from.
Create a new method with exactly the same name as the method in the Parent class and put your logic in there.
Now instantiate your object from your new class, you would have succeeded in overriding that particular method and still have access to the methods and properties of the base class.
Problem is, once you've loaded the class, you can't officially unload it, and you do need to load it in order to extend it. So it's pretty tied up. Your best bet is to either hack the original class (not ideal) or copy paste the original class definition into a new file:
class ParentClass {
//Copy paste code and modify as you need to.
}
Somewhere after the bootstrapping of your framework:
spl_autoload_register(function ($class) {
if ($class == "ParentClass") { //Namespace is also included in the class name so adjust accordingly
include 'path/to/modified/ParentClass.php';
}
},true,true);
This is done to ensure your own modified class will be loaded before the original one.
This is extremely hacky so first check if the framework you're using has native support for doing this.
I need to get all declared classes which are have extended another parent class.
So for example...
class ParentClass {
}
class ChildOne extends ParentClass {
}
class ChildTwo extends ParentClass {
}
class ChildThree {
}
I need an array that outputs this:
array('ChildOne', 'ChildTwo')
I'm new to PHP OOP, but based on some Googling, I came up with this solution.
$classes = array();
foreach( get_declared_classes() as $class ) {
if ( is_subclass_of($class, 'ParentClass') ){
array_push($classes, $class);
}
}
What I want to ask is whether this is the best practice to do what I want to do, or is there a better way? The global scope will contain a lot of other classes that isn't a child of ParentClass. Is looping through all declared classes the best way to go?
EDIT (clarification of purpose):
What I want to achieve with this is to instantiate each child class extending the parent class.
I want to do $childone = new ChildOne; $childtwo = new ChildTwo; for every child of ParentClass.
you can try to log the declaration of a class the first time it is loaded.
it suppose you are using autoloading.
if you do not use composer but a custom loader :
It's the easiest way :
$instanciatedChildren = array();//can be a static attribute of the A class
spl_autoload_register(function($class)use($instanciatedChildren){
//here the code you use
if(is_subclass_of($class,'A')){
$instanciatedChildren[] = $class;
}
}
if you use composer :
you can, make a class that extends composer/src/Composer/Autoload/ClassLoader.php
and then override the loadClass method to add the condition given above. and then register your new loader and unregister the old one.
Your solution seems fine, though I'm not sure why you'd do this. There is no easy way in php to say, 'give me all the declared classes of a certain parent class globally' without actually checking globally each declared class. Even if you have a couple hundred classes loaded to loop through, it shouldn't be too heavy as they're all in memory.
If you're trying to just track loaded child classes for a specific parent, why not create a registry that tracks them when they're loaded? You could do this tracking in an autoloader or factory used for the child classes or event as a hack, just by putting something at the top of the class file before the class definition.
I'll try to describe the situation I'm having problems with:
I have one main folder. I keep all files in there (empty classes for a reason), and one sub-folder, containing the same files, with all implementations here (empty classes extend them).
the main folder's namespace is declared as Project/Folder, and the sub-folder as Project/Folder/Subfolder. These are class's declarations:
namespace Project\Folder;
class Foo extends Subfolder\Foo { }
namespace Project\Folder\Subfolder;
class Foo { }
What I want to achieve is to be able to call other classes from inside of the Project\Folder\Subfolder\Foo through these empty classes on the lower level, with only its name, e.g.:
namespace Project\Folder\Subfolder;
class Foo {
function bar() {
Another_Class::do_something();
}
}
By default, there will be called Another_Class from the Project\Folder\Subfolder namespace. I want this to refer to Another_Class from the Project\Folder namespace with the same syntax - is that possible?
I hope I explained this clear enough, if not, write a commend, and I'll try to make it clearer.
You can achieve that using the use statement.
use Project\Folder\Subfolder\Another_Class as SomeAlias;
// ...
SomeAlias::doSomething();
// or
$object = new SomeAlias();
$object->doSomething();
Alternatively, you would have to reference the entire namespace:
\Project\Folder\Subfolder\Another_Class::doSomething();
// or
$object = new \Project\Folder\Subfolder\Another_Class();
$object->doSomething();
More information here.