DRY MVC view/controller separation - php

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.

Related

Laravel Reusing Controller Logic

I have multiple controllers, with multiple methods, which all return views.
class PageController extends Controller {
public function index()
{
// do lots of stuff
return view('view.name', $lotsOfStuffArray);
}
public function list()
{
//...and so on
}
I now have the need to create an API, which performs much of the same logic as the methods above, but returns a JSON output instead:
class PageApiController extends Controller {
public function index()
{
// do lots of the same stuff
return $lotsOfStuffCollection;
}
public function list()
{
//...and so on
}
What is the best way to accomplish this without having to copy and paste code from one controller to the other?
I've tried placing a lot of the logic into traits and using them in my Eloquent models, but that still requires that I copy and paste code from controller to controller. I should also note its not viable to check expectsJson() and return a response accordingly as I have many, many methods.
Is it a good idea to have the logic stored in a parent class and then create a child controller that responds with a view and a child controller that responds with JSON?
You could abstract the logic to a service class. I have answered a similar question.
You have PageController, PageAPIController and PageService.
class PageService {
public function doStuff()
{
return $stuff;
}
}
class PageController extends Controller {
public function index()
{
$service = new PageService();
$stuff = $service->doStuff();
return $stuff;
}
}
class PageAPIController extends Controller {
public function index()
{
$service = new PageService();
$stuff = $service->doStuff();
return $stuff->toJSON();
}
protected function toJSON(){
//You could also abstract that to a service or a trait.
}
}

Accessing an extended class method from a sibling extended class (PHP OOP)

First off, I am relatively new to OOP as well as using an MVC, so I apologize if I do not use the right terminology or if I seem confused (because I am, haha)
I will start this off as basic as possible and if you need more information please let me know.
I am using Panique's MVC (Version HUGE)
https://github.com/panique/huge
So here goes nothing!
I have a base controller class that is setup like this...
Controller
<?php
class Controller {
public $View;
function __construct() {
$this->View = new View();
}
}
?>
With some extended controller classes like this (I will show two here)
IndexController
class IndexController extends Controller {
public function __construct() {
parent::__construct();
}
public function index() {
$this->View->render('index');
}
}
?>
ProfileController
class ProfileController extends Controller {
public function __construct() {
parent::__construct();
}
public function profile() {
$this->View->render('profile');
}
}
?>
My Question is, what does it take (if at all possible) to use an extended class method within another extended class method when both have the same parent class. Something Like...
<?php
class ProfileController extends Controller {
public function __construct() {
parent::__construct();
}
public function profile() {
$this->IndexController->index(); //Here I would like to use the method from the IndexController
}
}
?>
I have tried many of attempts to make this work but I think my lack of knowledge using OOP is hindering me. It seems that most everything I try except for a few cases, throws an error of...
Fatal error: Class 'IndexController' not found in blah/blah/ProfileController.php
I think if I could learn to target the extended class the right way I could manage the rest...hopefully ;)
There's no easy or elegant way to do that. You would need to instantiate the other class inside the class that needs to borrow the code, and that would probably cause many side effects in your app.
There may be other ways to do that, and that also depends on the possibilities / limitations of the framework, but thinking from the perspective of OOP in PHP, ignoring other factors, the best approach would be to implement the shared code in a method on Controller class:
<?php
class Controller {
public $View;
function __construct() {
$this->View = new View();
}
protected function myCustomCode() {
...
}
}
?>
And then call it normally on descendents:
<?php
class IndexController extends Controller {
public function __construct() {
parent::__construct();
}
public static function index() {
$this->myCustomCode();
$this->View->render('index');
}
}
?>
<?php
class ProfileController extends Controller {
public function __construct() {
parent::__construct();
}
public function profile() {
$this->myCustomCode();
...whatever...
}
}
?>
I don't see a better way of doing that. Besides, this is the natural way of OOP, where common stuff is up on class hierarchy (ancestors), never sideways or down (descendents). That helps keeping your code logical and easier to maintain.
Include the file of the class IndexController:
require_once('IndexController.php');
$this->controller = new IndexController();
Then invoke the method
$this->IndexController->index();

How to load a controller from another controller in codeigniter?

I want to load a controller from a function in another controller because the library I integrated to my project I don't want to load it to the controller because I want to keep it clean and related.
I tried using modules but I still had to put controller in the url like
http://example.com/maincontroller/function
http://example.com/othercontroller/function
I have default controller so I can load http://example.com/function so how could I access the controller from a function from main so I don't have to put the controller in the url.
I'm still willing to use HMVC if I can load the controller function from the main controller function.
yes you can (for version 2)
load like this inside your controller
$this->load->library('../controllers/whathever');
and call the following method:
$this->whathever->functioname();
You can't load a controller from a controller in CI - unless you use HMVC or something.
You should think about your architecture a bit. If you need to call a controller method from another controller, then you should probably abstract that code out to a helper or library and call it from both controllers.
UPDATE
After reading your question again, I realize that your end goal is not necessarily HMVC, but URI manipulation. Correct me if I'm wrong, but it seems like you're trying to accomplish URLs with the first section being the method name and leave out the controller name altogether.
If this is the case, you'd get a cleaner solution by getting creative with your routes.
For a really basic example, say you have two controllers, controller1 and controller2. Controller1 has a method method_1 - and controller2 has a method method_2.
You can set up routes like this:
$route['method_1'] = "controller1/method_1";
$route['method_2'] = "controller2/method_2";
Then, you can call method 1 with a URL like http://site.com/method_1 and method 2 with http://site.com/method_2.
Albeit, this is a hard-coded, very basic, example - but it could get you to where you need to be if all you need to do is remove the controller from the URL.
You could also go with remapping your controllers.
From the docs: "If your controller contains a function named _remap(), it will always get called regardless of what your URI contains.":
public function _remap($method)
{
if ($method == 'some_method')
{
$this->$method();
}
else
{
$this->default_method();
}
}
you cannot call a controller method from another controller directly
my solution is to use inheritances and extend your controller from the library controller
class Controller1 extends CI_Controller {
public function index() {
// some codes here
}
public function methodA(){
// code here
}
}
in your controller we call it Mycontoller it will extends Controller1
include_once (dirname(__FILE__) . "/controller1.php");
class Mycontroller extends Controller1 {
public function __construct() {
parent::__construct();
}
public function methodB(){
// codes....
}
}
and you can call methodA from mycontroller
http://example.com/mycontroller/methodA
http://example.com/mycontroller/methodB
this solution worked for me
I had a similar problem. I wanted to have two controllers:
homepage.php - public facing homepage
home.php - home screen once a user was logged in
and I wanted them both to read from 'mydomain.com'
I was able to accomplish this by setting 'hompepage' as the default controller in my routes config and adding a remap function to homepage.php
function _remap()
{
if(user_is_logged_in())
{
require_once(APPPATH.'controllers/home.php');
$oHome = new Home();
$oHome->index();
}
else
{
$this->index();
}
}
I was having session file not found error while tried various ways, finally achieved like this. Made the function as static (which I want to call in the another controller), and called like
require_once('Welcome.php');
Welcome::hello();
While the methods above might work, here is a very good method.
Extend the core controller with a MY controller, then extend this MY controller for all your other controllers. For example, you could have:
class MY_Controller extends CI_Controller {
public function is_logged()
{
//Your code here
}
public function logout()
{
//Your code here
}
}
Then your other controllers could then extend this as follows:
class Another_Controller extends MY_Controller {
public function show_home()
{
if (!$this->is_logged()) {
return false;
}
}
public function logout()
{
$this->logout();
}
}
I came here because I needed to create a {{ render() }} function in Twig, to simulate Symfony2's behaviour. Rendering controllers from view is really cool to display independant widgets or ajax-reloadable stuffs.
Even if you're not a Twig user, you can still take this helper and use it as you want in your views to render a controller, using <?php echo twig_render('welcome/index', $param1, $param2, $_); ?>. This will echo everything your controller outputted.
Here it is:
helpers/twig_helper.php
<?php
if (!function_exists('twig_render'))
{
function twig_render()
{
$args = func_get_args();
$route = array_shift($args);
$controller = APPPATH . 'controllers/' . substr($route, 0, strrpos($route, '/'));
$explode = explode('/', $route);
if (count($explode) < 2)
{
show_error("twig_render: A twig route is made from format: path/to/controller/action.");
}
if (!is_file($controller . '.php'))
{
show_error("twig_render: Controller not found: {$controller}");
}
if (!is_readable($controller . '.php'))
{
show_error("twig_render: Controller not readable: {$controller}");
}
require_once($controller . '.php');
$class = ucfirst(reset(array_slice($explode, count($explode) - 2, 1)));
if (!class_exists($class))
{
show_error("twig_render: Controller file exists, but class not found inside: {$class}");
}
$object = new $class();
if (!($object instanceof CI_Controller))
{
show_error("twig_render: Class {$class} is not an instance of CI_Controller");
}
$method = $explode[count($explode) - 1];
if (!method_exists($object, $method))
{
show_error("twig_render: Controller method not found: {$method}");
}
if (!is_callable(array($object, $method)))
{
show_error("twig_render: Controller method not visible: {$method}");
}
call_user_func_array(array($object, $method), $args);
$ci = &get_instance();
return $ci->output->get_output();
}
}
Specific for Twig users (adapt this code to your Twig implementation):
libraries/Twig.php
$this->_twig_env->addFunction('render', new Twig_Function_Function('twig_render'));
Usage
{{ render('welcome/index', param1, param2, ...) }}
Create a helper using the code I created belows and name it controller_helper.php.
Autoload your helper in the autoload.php file under config.
From your method call controller('name') to load the controller.
Note that name is the filename of the controller.
This method will append '_controller' to your controller 'name'. To call a method in the controller just run $this->name_controller->method(); after you load the controller as described above.
<?php
if(!function_exists('controller'))
{
function controller($name)
{
$filename = realpath(__dir__ . '/../controllers/'.$name.'.php');
if(file_exists($filename))
{
require_once $filename;
$class = ucfirst($name);
if(class_exists($class))
{
$ci =& get_instance();
if(!isset($ci->{$name.'_controller'}))
{
$ci->{$name.'_controller'} = new $class();
}
}
}
}
}
?>
There are many ways by which you can access one controller into another.
class Test1 extends CI_controller
{
function testfunction(){
return 1;
}
}
Then create another class, and include first Class in it, and extend it with your class.
include 'Test1.php';
class Test extends Test1
{
function myfunction(){
$this->test();
echo 1;
}
}
You can call Model from a Controller so put your functions inside a Model and call it from the controller . Worked for me.
(codeignitor 3)

Can't access property in abstract class via sub class

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.

Zend ViewHelper Sub Functions

I want to add more than one function to a ViewHelper. Usually there is one function named like the class and like the file name.
How can I add several functions into one ViewHelper?
E.g. like this:
class Zend_View_Helper_MyMenuHelper extends Zend_View_Helper_Abstract
{
public function Horizontal($parameter)
{
return "...";
}
}
echo $this->MyMenuHelper()->Horizontal($parameter);
Alex was on the right path, but missed something in his answer: the actual myMenuHelper() method has to return the view helper itself, for this to work:
class Zend_View_Helper_MyMenuHelper extends Zend_View_Helper_Abstract
{
public function myMenuHelper()
{
return $this;
}
public function horizontal() { ... }
// more methods...
}
And then, as mentioned:
echo $this->myMenuHelper()->horizontal();
Sometimes you don't want to pass through the main method of a view helper, although it's not that bad for some kinds of logic. In that case, use getHelper():
class Zend_View_Helper_MyMenuHelper extends Zend_View_Helper_Abstract
{
public function myMenuHelper()
{
// some logic, maybe the main one
}
public function horizontal()
{
// some other logic
}
}
The following examples bypass myMenuHelper() completely:
// in controller
$this->view->getHelper('MyMenuHelper')->horizontal();
// in view
$this->getHelper('MyMenuHelper')->horizontal();`
In some cases for example, I populate the view helper with some internal data in the controller, the call its main method directly in the view, which acts on that data.
// in controller
$this->view->getHelper('MyMenuHelper')->storeData($someArray);
// in view
$this->myMenuHelper(); // iterates over $someArray
try to start the function name with a lower case letter
class Zend_View_Helper_MyMenuHelper extends Zend_View_Helper_Abstract
{
public function horizontal($parameter)
{
return "...";
}
}
in the view:
echo $this->myMenuHelper()->horizontal($parameter);

Categories