I am trying to build an abstract base controller that will extend all other controllers. So far I have something like:
abstract class BaseController {
protected $view;
protected $user;
public function __construct() {
$this->view = new View; //So a view is accessible to subclasses via $this->view->set();
$this->user = new User; //So I can check $this->user->hasPermission('is_admin');
}
abstract function index();
}
class UserController extends BaseController {
public function index() {}
public function login() {
if($this->user->isLoggedin()) {
redirect to my account
}
else {
$this->view->set('page_title', "User Login");
$this->view->set('sidebar', $sidebar); //contains sidebar HTML
$this->view->set('content', $content); //build main page HTML
$this->view->render();
}
}
}
The problem i get is I get errors like this:
Call to a member function set() on a non-object in C:\xampp\htdocs\program\core\controllers\admin.controller.php on line 44
If I put the $user and $views properties in the main controller (ie UserController), everything works fine. But I only want to set up these objects once (in the base controller) and not have to add $this->view = new View; in all my controllers.
FIXED: I overrode my constructors and I thought you couldn't call parent::__construct() on abstract classes.
What you are trying to do should work. Make sure you aren't covering up your constructor in UserController. (i.e., if it has a constructor, it needs to call its parent constructor.)
Otherwise, do some debugging to see where $this->view is being reset.
Your code works for me. You are either overriding your __construct() method in UserController, or you are overridding the view field with something other than a View object.
What you have in this form would work.
Related
I am learning OOP right now and I have a property that specifies what class it should be.
class Controller{
public ?Model $model;
}
class LoginModel extends Model{
public function login(){}
}
Now, I want to load a class that is an inheritance of Model but contains a unique method not from Model. That causes the lint to think that there is an error in the code. Is there a way to fix the issue from Controller->model?
$controller = new Controller();
$controller->model = new LoginModel();
echo $controller->model->login();
I know that one way is we can change Model to LoginModel but that would be tedious if there are a lot of different controllers with different model designed to do different things.
consider this example
class Controller
{
public ?Model $model;
}
class Model { }
class LoginModel extends Model
{
public function login() { }
}
class LogoutModel extends Model { }
$controller = new Controller();
$controller->model = new LoginModel();
$controller->model->login(); //This works
$controller->model = new LogoutModel();
$controller->model->login(); //This doesn't works
The lint is saying that you cannot assure that there will be a login() to call. So unless you'll have a login() method in all models, in which case you can define an abstract or interface, otherwise since it can fail it will show you the error.
PS: there is a way around it, but I wouldn't recommend it for regular use.
if($controller->model instanceof LoginModel)
{
$controller->model->login(); //This works
}
I am using Ion_Auth library with Codeigniter 3.0.*. I managed to display the user email I am logged in with with this line of code in the __construct() method of my Admin_Controller:
$this->user_email = $this->ion_auth->user()->row();
But I need to repeat this code:
$data['user_email'] = $this->user_email->email;
In every view method within every controller. I display the variable $user_email in my header.php, which is the same for every page. How do I make it accessible for everyone without repeating this line of code?
Add a class property $data to the Admin_controller class definition.
The $data property will be available to each controller that extends Admin_controller. Because it is a class property it is accessed with the syntax $this->data.
class Admin_controller extends CI_Controller
{
//our new class property
protected $data = array();
public function __construct(){
parent :: __construct();
// do what is needed to get $ion_auth working
}
}
In any class extending Admin_controller set $data['$user_email'] in the constructor. It is then available to every view that is given $this->data
class Some_controller extends Admin_controller
{
public function __construct(){
parent :: __construct();
//I am assuming that by this time $this->ion_auth->user() exists
//so we add a key and value to the class' $data property
//Note the use of the "$this->" syntax)
$this->data['user_email'] = $this->ion_auth->user()->row();
}
public function sets_up_a_view(){
//do stuff until you're ready for the header
//note that we are sending the class property "$this->data" to the view
$this-load->view('header', $this->data);
//load other views as needed using $this-data or other array - your choice
}
public function some_other_view(){
//send class property to view
$this-load->view('header', $this->data);
$data['foo'] = 42;
//send local var to view
$this-load->view('other_parts', $data);
}
}
Notice that both sets_up_a_view() and some_other_view() send the class property '$this->data' to header.php. But in some_other_view() we setup a local variable called $data to send to the other_parts.php view.
The solution was adding these two lines of code in my Admin_controller:
$data['user_email'] = $this->ion_auth->user()->row()->email;
$this->load->vars($data);
With this, every view method in the controllers extending Admin_controller can access this variable.
I have a model -
class Model{
public $childModel;
public function getModel($url){
$this->childModel = 'model/'.$url;
include($this->childModel);
new ModelHomeHome();
}
}
this is child model -
class ModelHomeHome extends Model{
function __construct(){
echo 'This is Home Model.';
}
}
a Controller Class -
class Controller{
public $model;
function __construct(){
$this->model = new Model();
}
}
and this is the child controller -
class ControllerHomeHome extends Controller{
function help(){
$this->model->getModel('home/home.php');
}
}
and in a page -
include(controller.php);
include(model.php);
The problem is the child model class is not initializing. Fatal error: Call to a member function getModel() on a non-object ... .
If I place the $this->model = new Model(); inside the help() function of the child controller instead of placing inside the parent controller, it works. But I want to initialize the model inside the constructor of parent controller. Please help.
Are you sure that is the path to your file. Most frameworks I have seen usually have files all over the place so I try to have constants that ensure I am in the correct parent director when retrieving files.
This works fine for me when I include the model and controller class and run the following code
include('controller.php');
include('model.php');
$home = new ControllerHomeHome();
$home->help();
outputs:
This is Home Model.
I have a custom MVC PHP framework that has a router class, which calls a controller, which uses a model, then the controller presents the view, etc etc.
My problem is that I can't figure out technically how to allow variables to pass between the controller and the view, semantically. I could do a quick-and-dirty fix, but what I want to have is this for a controller:
class IndexController extends Controller{
var $name = "John"; // instance variable
}
And have this for a view:
<p> <?=$name?> </p>
My question is this:
How can I create a Controller->render() function, or something similar, that allows the view to access instance variables from the controller? and,
How can I do this without doing klutzy things like $data['view']['name'] = "John"; or having to write ten lines of code by default for any new controller I make. I want to do this so it's as DRY as possible.
Thanks.
Edit: FabioCosta's solution
I'm not sure I understand, so far I have my base controller like this:
<?php
class Controller{
public function __get($key){
if(isset($this->$$key)) return $this->$$key;
}
}
?>
My base view class looks like this:
<?php
class View{
public $controller;
public function render(){
$this->controller = $this;
}
?>
And I initialize from the router like this:
<?php
$controller = new IndexController();
$view = new IndexView();
$view->render();
?>
However, this doesn't work, and I know I'm doing something wrong.
Why not pass the controller that instantiates the view and use the __get magic method?
like so:
public function __get($key){
if(isset($this->$key)) return $this->$key;
}
Here is a working example View.php:
class View{
protected $_controller;
public function __construct(Controller $controller){
$this->_controller=$controller;
}
public function render(){
echo '<h1>Hello '.$this->_controller->name.'</h1>';
}
}
Controller.php
class Controller{
protected $name='fabio';
protected $_myView;
public function __get($key){
if(isset($this->$key)) return $this->$key;
}
public function __construct(){
$this->_myView=new View($this);
}
public function indexAction(){
$this->_myView->render();
}
}
And the router:
$c=new Controller();
$c->indexAction();
Controller should not be responsible for rendering output. That is something view instances should do. Rendering should happen outside the controller.
View should request data from model layer. Then, based on information it received, select the right template, assign data and render this template (or in some cases - group of templates).
Also , router should not initialize neither controllers nor views. Controller should be responsible only for processing the request.
I have a model class ModelHome that is a child of Model ie:
class ModelHome extends Model
Model is a variable of the Controller class ie:
class Controller {
public $model;
public function __construct () {
$this->model = new Model;
}
}
Is it possible to access a method within the Controller class from within a method inside the ModelHome class?
I've tried parent:: and calling the class by name ie Controller::method but I can't seem to find the right scope to access the method I need.
Thanks.
-Vince
First of all, you must have an instance of ModelHome. If you make an instance of Model, that has not automatically been extended by ModelHome just because ModelHome exists. So, i guess your Controller::__construct() should be:
public function __construct () {
$this->model = new ModelHome;
}
However, your ModelHome does not know about your Controller class/instance. You could make a __construct in ModelHome that takes a parameter with a link to the controller. Like this:
class ModelHome extends Model {
public $controller;
public function __construct ($controller) {
$this->controller = $controller;
}
}
class Controller {
public $model;
public function __construct () {
$this->model = new ModelHome($this);
}
}
Now, your ModelHome knows about the controller by using $this->controller