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).
Related
There is clearly I miss something. I have the following code:
<?php
class module {
protected $registry;
public $controller;
public $model;
public $view;
public $var = 'global';
}
class controller extends module {
public function test_controller() {
echo 'controller test';
echo $this->var;
$this->model->test_model();
$this->view->test_view();
}
}
class model extends module {
public function test_model() {
echo 'model test';
echo $this->var;
}
}
class view extends module {
public function test_view() {
echo 'view test';
echo $this->var;
}
}
$module = new module();
$module->controller = new controller();
$module->model = new model();
$module->view = new view();
echo "\n\n\n" . print_r($module);
$module->controller->test_controller();
And at the end I get "Call to a member function test_model() on null". I do understand that variables of class 'module' are being re-initialized each time the 'extender' class is being instantiated. Ok, no problem, but I assign 'parent' class property with needed 'value' right after that (I mean $module->controller = new controller();).
I do not understand how to deal with this behavior. I'd like to achieve this type of referencing inside a module that I've wrote in controller function: $this->model->some_func(), $this->view->some_other(). There also will be a so called registry with other classes that also should be available for extended classes.
If this is a design issue - well, please, point me :)
Thanks.
These are different instances. Same as below:
<?php
class Foo
{
public $bar;
public $baz;
}
class Baz extends Foo
{
public function showMeTheBar()
{
return $this->bar;
}
}
$foo = new Foo;
$foo->bar = 'Hat';
$foo->baz = new Baz;
var_dump($foo->baz->showMeTheBar());
Output:
NULL
$foo's bar and $foo::baz's bar are not one and the same.
As noted by other answer(s), the controller has ownership of the $this->model and the $this->view so in your instance, you have to assign the controller the new instances:
$module = new module();
$module->controller = new controller();
# Need to assign to the controller now, not the $module
$module->controller->model = new model();
$module->controller->view = new view();
This is probably what you are not intending to do. If you want to do what you are doing, you have to do it in the $module scope and bring the single elements back into module:
<?php
class module
{
public $controller,
$model,
$view;
public $var = 'global';
protected $registry;
# Add a new method that basically does what your controller method was doing.
# Difference is that now these other classes are in the $module scope
public function get()
{
$this->controller->test_controller();
$this->model->test_model();
$this->view->test_view();
}
}
class controller extends module
{
public function test_controller()
{
echo $this->var;
}
}
class model extends module {
public function test_model()
{
echo $this->var;
}
}
class view extends module {
public function test_view()
{
echo $this->var;
}
}
$module = new module();
$module->model = new model();
$module->view = new view();
$module->controller = new controller();
$model->get();
You may want to use injection though I am not sure how many classes you are intending to assign:
$module = new module(new model(), new view(), new controller());
$module->view->test_view();
If you inject and want to use dynamic injection, you may want to look at Reflection so your classes don't need explicit param assignment.
You could move into the range of dynamic calling, but if you aren't careful, that could get into a bit of a rabbit hole!
<?php
class module
{
public $var = 'global';
protected $registry;
# Might want to add a getter
public function __get($prop)
{
if(property_exists($this, $prop)) {
return $this->{$prop};
}
}
# Create a method getter
public function __call($class, $args = false)
{
$class = strtolower(str_replace('get','', $class));
# Set the dynamic variable
if(!isset($this->{$class}))
$this->{$class} = (is_array($args))? new $class(...$args) : new $class($args);
# Send back new variable
return $this->{$class};
}
}
class controller extends module
{
public function test_controller()
{
echo $this->var;
}
}
class model extends module
{
public function test_model()
{
echo $this->var;
}
}
class view extends module
{
public function __construct()
{
print_r(func_get_args());
}
public function test_view()
{
echo $this->var;
}
}
# Create instance of module
$module = new module();
# use getView() to fetch and assign view
print_r($module->getView('arg1', 'arg2')->test_view());
# Since you set this value already, it can be pulled directly or by using
# getView()
print_r($module->view);
What you get from this:
Array
(
[0] => arg1
[1] => arg2
)
global
view Object
(
[var] => global
[registry:protected] =>
)
Dynamic calling has it's place, so if you employ it, just do it mindfully.
For persistant variables, as mentioned you could use a static variable so if you change the variable in one class it changes in all.
The state of $module before calling Controller::test_controller() as a tree (I'll show class names in a camel-case manner):
$module: Module
Props:
$controller: Controller
Props:
$controller: null
$model: null
$view: null
$var: "global"
$model: Model
Props:
$controller: null
$model: null
$view: null
$var: "global"
$view: View
Props:
$controller: null
$model: null
$view: null
$var: "global"
$var: "global"
Can you see? $module->controller is different from $module->controller->controller. Even $module->var and $module->controller->var are not the same thing. $module and $module->controller are different.
I don't know if you're doing the right thing, but the solution would be using dependency injection; which means you should pass (inject) $module (dependency) as an argument to Controller::test_controller() or whatever it is.
thanks for your answers. That is how it happens: you have to ask to find answer yourself :)
The points of entry to the app are controller and api, so I have to make stuff available there, but not elsewhere: 'view' must not see 'model'.
So in case if anyone might be looking for this design - there it is:
$controller = new Controller($registry);
$model = new Model($registry);
$api = new Api($registry);
$view = new Template($modulename);
//these classes above are already extended by abstract classes to bring $registry classes with __get()
$settings = new Settings($modulename);
$language = new Language($code);
$module = new stdClass();
$module->controller = $controller;
$module->controller->model = $model;
$module->controller->view = $view;
$module->controller->api = $api; //this might be questionable, but sometimes it's easier to do so
$module->controller->settings = $settings;
$module->controller->language = $language;
$module->api = $api;
$module->api->model = $model;
$module->api->view = $view;
$module->api->controller = $controller; //this might also be questionable, but sometimes it's easier to do so
$module->api->settings = $settings;
$module->api->language = $language;
$model->settings = $settings;
The goal was to make calls (in controller and api) like $this->model->method(), $this->view->method(), etc., which I've managed to achieve.
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"
I have some variables and functions which need to be available for different classes. Hence, I put all definitions (Variables / functions) to some class:
class common_functions() {
function __construct() {
$this->define_variables();
$this->connect_to_database();
echo "EXEC";
}
function define_variables() {
$this->var1 = "foo";
$this->var2 = "bar";
}
function connect_to_database() {
mysql_connect(...)
}
function do_something() {
//...
}
}
which is the parent of all the others:
class orders extends common_functions {
private $order_item;
function __construct() {
parent::__construct()
$order_item = new item();
}
function show_something() {
echo $order_item->get_something()*$this->var1;
}
}
class item extends common_functions {
pivate $some_number;
function __construct() {
parent::__construct()
$this->number = 123;
}
function get_something() {
return $this->var2*$this->var1*$this->number;
}
}
class some_other_class extends common_functions {
function __construct() {
parent::__construct()
}
// ..
}
However, as executing
$o = new order();
$o->show_something();
the output is
EXEC
EXEC
since the common_functions class is called twice. Especially also mysql-connection is established several times which is quite unefficient.
What I need is some technique so that all the functions and variables (and database-connections) from common_functions are available to all classes without the drawback that e.g. connect_to_database() is executed several times. Some ideas?
If I were you I'd redesign my implementation. Why? Well because it seems to me that neither some_other_class nor item is a common_functions. However they both have common_functions. Thus I'd create only one instance of that class and pass it into the constructor.
Something like this:
class Item {
private $common_functions;
public function __construct($common_functions) {
$this->common_functions = $common_functions;
}
}
class Order {
private $common_functions;
public function __construct($common_functions) {
$this->common_functions = $common_functions;
}
}
What happens now is that both the item and some_other_class objects has a dependency which we inject to common_functions. This obviously means that you have to pass some values to the methods in common_functions but that is a very small price to pay considering what you gain from not inheriting common_functions, like only one db-connection.
Inheritance is cool but in practice it isn't used all that much. It's often much better compose objects than to inherit a bunch of stuff. When designing OO-classes always consider wether an objects relation is an is a or has a relation.
So what you could do using the above example of the orders constructor is the following:
class orders {
private $common_functions;
public function __construct($common_functions) {
$this->common_functions = $common_functions;
$order_item = new Item($common_functions);
}
}
That way both item and order will share the same common_functions object.
Assign a static null variable initially in parent class and check if its null or not.
class common_functions {
private static $dbInstance = null;
function __construct() {
if(self::$dbInstance == null) {
self::$dbInstance = $this->connect_to_database();
}
}
...
return the the database connection handler or any other than the null value in $this->connect_to_database();
Here is the code layout outline all nicely laid out in 3 file and class's
$aa = new className();
class className {
/**
* Constructor
*/
function className() {
$this->init_SubClass();
}
function init_SubClass() {
require_once('sub_class.class.php');
$sub_class = new sub_class();
}
}
sub_class.class.php
class sub_class {
/**
* Constructor
*/
function sub_class() {
$this->init_Sub_Sub_Class();
}
function init_Sub_Sub_Class() {
require_once('Sub_Sub_Class.class.php');
$Sub_Sub_Class = new Sub_Sub_Class();
}
}
sub_sub_class.class.php
class Sub_Sub_Class {
public function function_I_to_call() {
echo ' show this text'
}
}
How to a call function_I_to_call()
This was mybest guess so far
$aa->className->sub_class->function_I_to_call()
Not sure how to do this or if it can be done.
Many Thanks
You are not assigning the newly created object to the instance. You need to use
$this->sub_class = new Subclass;
That will make them public properties and then you can use your
$aa = new className;
$aa->sub_class->function_I_to_call();
However, the entire approach is completely flawed:
The constructor should be __construct. The old style constructor is a relic from PHP4 times and wont work with namespaced classes.
Assigning properties on the fly is considered bad practice, because it's unobvious they exist when looking at the API. Declare them as members in the class.
Calls to require are unneeded when you use an Autoloader.
Use Dependency Injection to decouple your components. Makes them easier to unit-test as well.
If you need to assemble complex collaborator graphs, use a Factory or a Builder pattern instead.
Alternate approach
class Foo
{
protected $bar;
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
public function getBar()
{
return $this->bar;
}
}
And then Bar
class Bar
{
protected $baz;
public function __construct(Baz $baz)
{
$this->baz = $baz;
}
public function getBaz()
{
return $this->baz;
}
}
And Baz:
class Baz
{
public function fn()
{
return 'called';
}
}
And then assemble it via:
$foo = new Foo(new Bar(new Baz));
Or move that code to a Factory:
class FooFactory
{
public function create()
{
return new Foo(new Bar(new Baz));
}
}
Finally, the Autoloader (simplified):
spl_autoload_register(function($className) {
$classMap = array(
'Foo' => '/path/to/Foo.php',
'Bar' => '/path/to/Bar.php',
'Baz' => '/path/to/Baz.php',
);
require $classMap[$className];
});
And then you could call (demo)
$fooFactory = new FooFactory;
$foo = $fooFactory->create();
echo $foo->getBar()->getBaz()->fn();
But you shouldnt (unless it's some sort of DSL), because that is violating Law of Demeter because you are digging too deep into the collaborators.
is it possible to easily and quickly "assigning properties of one object to another"
class a {
public $number_one;
public $number_two;
public $number_three;
function __contruct() {
//do stuff
}
}
class b {
public $my_var;
function __contruct() {
$instanc_a = new a();
extract( $instance ); // but make these extracted object properties of class b????
// how? :-(
echo $this->number_one;
}
}
You can use get_object_vars to copy the public (only) properties of class a to the current object:
class b {
public $my_var;
function __construct() {
$instanc_a = new a();
$vars = get_object_vars($instanc_a);
foreach($vars as $name => $value) {
$this->$name = $value;
}
echo $this->number_one;
}
}
See it in action.
Note: You have a typo in your code (two cases of "contruct" instead of "construct") which will prevent things from working as they should.
Sounds like you actually want class b to extend class a
class b extends a {
public $my_var;
function __construct () {
parent::__construct();
// Now $this refers to anything in class b, or if it doesn't exist here, looks to class a for it
echo $this->number_one;
}
}