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.
Related
I have a core class as a collector and two subclasses stored in public variables in this core class:
class Core
{
public $cache;
public $html;
public function __construct()
{
$cache = new Cache();
$html = new Html();
}
}
class Cache
{
public function __construct()
{
}
public function store($value)
{
// do something
}
}
class Html
{
public $foo;
public function __construct()
{
$foo = "bar";
global $core;
$core->cache->store($foo);
}
}
QUESTION:
I would like to get rid of the line "global $core" and do something like:
$this->parent->cache->store($foo)
$cache should be connected to $core in some way because it is a public member of $core
I know that $html is a subclass stored as a variable and not an inheritance.
Any ideas?
Second question: Can I leave out the empty constructor in the Cache-Class?
What you can do is to use the concept of dependency injection to inject in your HTML class an object of the class Cache, and then, use it to call method store. I believe that this is a very good approach. So you can do something like this.
class Core
{
public $cache;
public $html;
public function __construct()
{
$cache = new Cache();
$html = new Html($cache);
}
}
In your class HTML:
class Html
{
public $foo;
public function __construct(Cache $cache)
{
$foo = "bar";
$cache->store($foo);
}
}
About your second question, if there is no necessity of do something in the constructor, you could just ommit it. But there is no problem to let it empty as well. So I think that it up to you.
Your object can't access caller class methods, because he do not know anything about it's caller.
You can try to pass parent when creating new object
class Core {
public $cache;
public $html;
public function __construct() {
$this->cache = new Cache($this);
$this->html = new Html($this);
}
}
class Html {
public $parent;
public function __construct($parent) {
$this->parent = $parent;
if (!empty($this->parent->cache)) {
$this->parent->cache->store();
}
}
}
Can I leave out the empty constructor - yes, you even do not have to declare __construct method at all, as all classes has it's default constructor/destructor.
I found something about this error but I think this is little bit different. I defined a public variable.
Class Controller{
public $model;
And I'm trying add extra word(model) between $model_name and $this.
public function call_model($model_name){
$this->model->$model_name = new $model_class;
What is the solution?
EDIT:
Warning: Creating default object from empty value in C:\xampp\htdocs\alisveris\project_library\Controller.php on line 16
You can create static factory method to create different models like this:
abstract class Model {
static function CallModel($name) {
switch ( $name ) {
case 'Naomi': return new NaomiCampbell();
case 'Anja': return new AnjaRubik();
default: return new $name;
}
}
}
class NaomiCampell extends Model {}
class AnjaRubik extends Model {}
Then use:
$MyModel = Model::CallModel($name);
You can do it by assigning $model variable to $this
class Db
{
public function great()
{
echo 'great';
}
}
class Controller{
public $model;
public function __construct()
{
$this->model = $this;
$model_name = 'Db';
$this->model->$model_name = new $model_name;
}
}
$cc = new Controller();
echo $cc->model->Db->great();
Output
great
I have a hard time figuring out how to add a variable value to an instantiated class in php,
I've been looking at the reflectionClass and tried to return an assigned variable, and now I'm ended up with a getter setter.
I would really appreciate some help, here's an example of my code:
class controller
{
public $models;
private $load;
public function __construct()
{
$this->load = new loader();
}
public function action_being_run()
{
$this->load->set_model('model_name');
}
}
class loader
{
public function set_model($name)
{
{controller_class}->models[$name] = new model();
}
}
The controller class is instantiated without assigning it to a variable, but just:
new controller();
And then the action is executed from within the controller class.
You could pass a reference to $this into set_model()
class controller
{
public $models;
private $load;
public function __construct()
{
$this->load = new loader();
}
public function action_being_run()
{
$this->load->set_model('model_name', $this);
}
}
class loader
{
public function set_model($name, &$this)
{
{controller_class}->models[$name] = new model();
}
}
You also need to change public $model to public $models. There are probably other ways to achieve what you want, by either extending a class or just using magic methods to access the model.
Like this:
class tester{
public function lame(){
return 'super lame';
}
}
function after(){
return 'after function';
}
$tst = new tester; $tst->afterVar = 'anything'; $tst->afterFun = 'after';
echo $wh->afterVar;
echo $wh->afterFun();
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.
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).