I have a parent class that depends on whether child class are instantiated.
class GoogleApp {
protected $auth_token;
public function __construct($scopes) {
$this->auth_token = $scopes;
}
}
class Gmail extends GoogleApp {
public function __construct() {
print_r($this->auth_token);
}
}
$googleApp = new GoogleApp('gmail'); // Change the actual class for all child instances
$gmail = new Gmail();
The idea is that all the children use the same auth_token (which is generated on whether the child classes are used - as of now, I'm just manually adding them to whether I included them in my code). Since I have quite a few child classes (like Calendar or Drive), do I have to inject the parent into each child instance or is there an easier way?
If I understand your request correctly, you're pretty close, you just need to declare your property as static.
class FooParent
{
protected static $scope = null;
public function __construct($scope)
{
self::$scope = $scope;
}
public function getScope()
{
return self::$scope;
}
}
class FooChild extends FooParent
{
public function __construct()
{
if (self::$scope === null) {
throw new Exception('Must set scope first.');
}
}
}
$parent = new FooParent('foo');
$child = new FooChild();
echo $child->getScope(), "\n"; // prints "foo"
Related
I have a Factory Method to instance a class. Is there a way to prevent this class from direct instancing?
The only option I see is to use an argument passed into the __construct(), but that's not something I'm looking for.
On the other hand, making the __construct() private would be ideal, but I don't want MyClass to extend the Factory without actual need.
What do you guys think?
Factory Method:
class Factory
{
public static function instance()
{
return new MyClass(true);
}
}
MyClass:
class MyClass
{
public function __construct($isFactory = false)
{
if (!$isFactory) {
throw new Exception('Use Factory::instance() to create an object');
}
}
}
There are hacks to do that:
abusing inheritance to use a protected constructor
putting the factory method inside the class so that it can call the private constructor, which is actually not a hack. But then why not using the constructor in the first place?
using reflection to access the private constructor
I'm not promoting anything of that. What I personally do is documenting the API with things like #internal and leave it to the client following that contract.
In essence, your code should have read something like this:
THE FACTORY
<?php
class Factory {
public static function instance(){
return new MyClass(true); //HERE YOU ARE INSTANTIATING
}
}
THE CLASS TO BE INSTANTIATED VIA THE FACTORY
<?php
//NOT MyClass() <--- YOU ARE DEFINING.... NOT INSTANTIATING...
class MyClass {
public function __construct($isFactory = false) {
if (!$isFactory) {
throw new Exception('Use Factory::instance() to create an object');
}
}
//...MORE METHODS
}
Could you try this instead?
<?php
class Factory
{
private static $FACTORY_GUARANTOR; //ONLY SET DURING INSTANTIATION
public static function instance($type) {
if (class_exists($type)) {
self::$FACTORY_GUARANTOR = 1;
$instance = new $type();
self::$FACTORY_GUARANTOR = null;
return $instance;
}
else {
throw new Exception("Class not found...");
}
}
//YOU CAN GET $FACTORYGUARANTOR EXTERNALLY BUT NEVER SET IT;
public static function getGuarantor(){
return self::$FACTORY_GUARANTOR;
}
}
class MyClass {
protected $property1;
protected $property3;
protected $property2;
public function __construct() {
// IF SOMEONE TRIES TO INSTANTIATE THE CLASS OUTSIDE OF THE FACTORY... BLOW A WHISTLE
if(!Factory::getGuarantor()){
throw new Exception('Use Factory::instance() to create an object');
}
// IF THE PROGRAM MADE IT TO THIS POINT;
// JUST INSTANTIATE THE CLASS BECAUSE MOST LIKELY IT IS COMING FROM THE FACTORY
var_dump($this); // A LITTLE CONFIRMATION....
}
//...MORE METHODS
}
// TRY IT OUT:
/*INSTANCE A: RIGHT*/ $theClass = Factory::instance("MyClass"); //INSTANTIATES THE CLASS
/*INSTANCE B: WRONG*/ $theClass = new MyClass(); //THROWS AN EXCEPTION
The easiest way is to define your base class as abstract. The abstract classes cannot be directly instanced, so you will have to redefine their abstract members in the inherited classes:
abstract class Factory
{
abstract public function foo();
}
class InheritedClass extends Factory
{
public function foo()
{
// Do something
}
}
// $obj1 = new Factory(); // Will produce an error
$obj1 = new InheritedClass(); // Will be executed successfully
You can read more for the abstract classes here: PHP: Class Abstraction - Manual.
For me, the best way is to use ReflectionClass:
class MyClass
{
public const FRIEND_CLASSES = [Factory::class];
protected function __construct() {}
}
trait Constructor
{
protected function createObject(string $className, array $args = [])
{
if (!in_array(static::class, $className::FRIEND_CLASSES)) {
throw new \Exception("Call to private or protected {$className}::__construct() from invalid context");
}
$reflection = new ReflectionClass($className);
$constructor = $reflection->getConstructor();
$constructor->setAccessible(true);
$object = $reflection->newInstanceWithoutConstructor();
$constructor->invokeArgs($object, $args);
return $object;
}
}
class Factory
{
use Constructor;
public function MyClass(): MyClass
{
return $this->createObject(MyClass::class);
}
}
In constant FRIEND_CLASSES you can define in which classes the class can be instanced.
trait is used because this functionality can be used in different factories that are not related.
If you need to put parameters into constructor of the class, put them as second parameter of createObject.
Details I described in the article "Forbidding of creating objects outside factory in PHP"
Is there a way in PHP to make a class only allowed to be instantiated by another class? For example:
<?php
class Graph {
private $nodes;
public function __construct() {
$this->nodes = array();
}
public function add_node() {
$this->nodes[] = new Node();
}
}
class Node {
public function __construct() {
}
}
?>
In my example I want to prevent access to calling new Node() directly. Only access to Node should be from the Graph class.
Thanks.
No, you can't do it. You can use a "hack" which consist in throwing an exception in the Node constructor if the argument passed to it is not a graph
class Node {
public function __construct() {
if(func_get_num_args() < 1 && !(func_get_args(0)instanceof Graph)){
throw BadCallException('You can\'t call Node outside a Graph');
}
}
}
Basically this is what i want to do:
<?php
class App {
public $var = "main-class";
public function load() {
$this->var = "child-class";
$child = new Child;
$child->echo_var();
}
}
class Child extends App {
public function echo_var() {
echo $this->var;
}
}
$app = new Child;
$app->load();
?>
It outputs "main-class", i want it to output "child-class" without having to modify the child class (because i want it to be sort of a "clean" and dynamic class).
I accept suggestions for another course of action
PS: This is part of an Small MVC Framework i'm trying to develop.
There are two ways that you could do this. Both are going to need to use constructors. With the first one, the child will declare itself when created
<?php
class App {
public $var = "main-class";
public function __construct($var=null) {
if($var !== null) {
$this->var = $var;
}
}
public function load() {
$child = new Child ();
$child->echo_var();
}
}
class Child extends App {
public function __construct(){
parent::__construct("child-class");
}
public function echo_var() {
echo $this->var;
}
}
$app = new Child();
$app->load();
?>
The second one allows the parent to declare the name of the child.
<?php
class App {
public $var = "main-class";
public function __construct($var=null) {
if($var !== null) {
$this->var = $var;
}
}
public function load() {
$child = new Child ("child-class");
$child->echo_var();
}
}
class Child extends App {
public function echo_var() {
echo $this->var;
}
}
$app = new Child();
$app->load();
?>
Both of those examples work and do what you want, I believe.
This isn't how inheritance works - By creating a new Child object, its data members are all initialized with their default values. When you do $this->var = "" in the parent class, you're setting the data members for the $app object, not the $child object.
You can modify the child class to incorporate a constructor that accepts parameters, and that constructor would set its data members properly. To achieve something similar to what you want, you can use constructors:
<?php
class App {
public $var = "main-class";
public function __construct() {
$this->var = "child-class";
}
public function load() {
$child = new Child;
$child->echo_var();
}
}
class Child extends App {
public function __construct()
{
parent::__construct();
}
public function echo_var() {
echo $this->var;
}
}
$app = new App;
$app->load();
I find it very strange that your parent class instanciates it's child. Generally, you would instanciate the child, and you get all the functionality of the parent.
$app = new Child();
$app->load();
The problem is that you actually have 2 different instanciations. You have an object of App and it's holding a separate object of Child.
The other way to do this would be to make $var a static variable and then it would be available independent of the instantiation. I don't generally recommend making properties static though. It's generally considered bad form (for numerous reasons).
PHP
If I create a new instance of a parent class and a new instance of a child class, how can I change the variable in the parent class directly and view the change in the child class?
Take the following code:
class parentClass {
public $varA = 'dojo';
public function setVarA() {
$this->varA = 'something grand';
}
public function getVarA() {
return $this->varA;
}
}
class childClass extends parentClass {
public function useVarA() {
echo parent::getVarA();
}
}
$parentInstance = new parentClass();
$childInstance = new childClass();
$initialVarA = $parentInstance->getVarA(); // should set $initialVarA variable to 'dojo'
$childInstance->useVarA(); // should echo 'dojo'
$parentInstance->setVarA(); // should set $varA to 'something grand'
$changedVarA = $parentInstance->getVarA(); // should set $changedVarA variable to 'something grand'
$childInstance->useVarA(); // should echo 'something grand' but fails to do so...how can I do this?
If you have either a private or a protected variable (member) in the parent then you can access it simply like this from you child class:
$this->varA = ‘something’;
There reason why your child method does not reflect the change, is that child and parent are two different objects in separate memory space. If you want them to share a value you could make it static.
You don’t need to declare it public.
class Parent {
private $varA;
protected $varB;
public $varC;
protected static $varD;
public function getD() {
return self::$varD;
}
public function setD($value) {
self::$varD = $value;
}
}
class Child extends Parent {
public function getA() {
return $this->varA;
}
public function getB() {
return $this->varB;
}
public function getC() {
return $this->varC;
}
}
$child = new Child();
$child->getA(); // Will not work since $varA is private to Parent
$child->getB(); // Works fine because $varB is accessible by Parent and subclasses
$child->getC(); // Works fine but ...
$child->varC; // .. will also work.
$child->getD(); // Will work and reflect any changes to the parent or child.
If you don’t want all instance of the parent class to share values. You could pass on the parent or child to either new instance and through and update the values of all the related objects accordingly.
$parent->addChild(new Child());
And in the set method:
$this->varA = $value;
foreach ($this->children as $child) {
$child->setVarA($value);
}
Hopes this helps.
This is a follow up from yesterday's scope question.
stackoverflow.com/questions/3301377/class-scope-question-in-php
Today I want to share the "$template_instance" variable with a child class.
How is this accomplished?
require_once("/classes/Conf.php");
require_once("/classes/Application.php");
class index extends Application
{
private $template_instance;
// Dependency injection
public function __construct(Smarty $template_instance)
{
$this->template_instance = $template_instance;
}
function ShowPage()
{
// now let us try to move this to another class
// $this->template_instance->assign('name', 'Ned');
// $this->template_instance->display('index.tpl');
}
}
$template_instance = new Smarty();
$index_instance = new Index($template_instance);
//$index_instance->showPage();
$printpage_instance = new printpage();
$printpage_instance->printSomething();
------------------------------------------------------------------
class printpage
{
public function __construct()
{
}
public function printSomething()
{
// now let us try to move this to another class
$this->template_instance->assign('name', 'Ned');
$this->template_instance->display('index.tpl');
}
}
Make it protected. Protected members will be accessible to the class and its children only.
Visibility Overview
Public members: members that
are visible to all classes.
Private variables: members that
are only visible to the class
they belong to.
Protected variables: members
that are only visible to the class
in which they belong as well as any of its children (subclasses)
In exactly the same way you were told before
$printpage_instance = new printpage($template_instance);
$printpage_instance->printSomething();
------------------------------------------------------------------
class printpage
{
private $template_instance;
public function __construct(Smarty $template_instance)
{
$this->template_instance = $template_instance;
}
public function printSomething()
{
// now let us try to move this to another class
$this->template_instance->assign('name', 'Ned');
$this->template_instance->display('index.tpl');
}
}
or pass your index to the printpage constructor
$printpage_instance = new printpage($template_instance);
$printpage_instance->printSomething();
------------------------------------------------------------------
class printpage
{
private $index;
public function __construct(index $index)
{
$this->index = $index;
}
public function printSomething()
{
$this->index->ShowPage();
}
}