I am working on creating my own very simple MVC and I am brainstorming ways to go from the controller to the view. Which involves sending variables from a class to just a plain old PHP page.
I am sure that this has been covered before, but I wanted to see what kind of ideas people could come up with.
//this file would be /controller/my_controller.php
class My_Controller{
function someFunction(){
$var = 'Hello World';
//how do we get var to our view file in the document root?
//cool_view.php
}
}
Some kind of hashtable is a good way to do that. Return your variables as association array which will fill all the gaps in your view.
Store your variables as a property in your controller object, then extract them when rendering
class My_Controller {
protected $locals = array();
function index() {
$this->locals['var'] = 'Hello World';
}
protected function render() {
ob_start();
extract($this->locals);
include 'YOUR_VIEW_FILE.php';
return ob_get_clean();
}
}
You can define those magic __get and __set methods to make it prettier
$this->var = 'test';
I'm also developing my own simple MVC and the most simple way to do it is ...
class My_Controller
{
function someFunction() {
$view_vars['name'] = 'John';
$view = new View('template_filename.php', $view_vars);
}
}
View class
class View
{
public function __construct($template, $vars) {
include($template);
}
}
template_filename.php
Hello, <?php echo $vars['name'];?>
I highly recommend you to take a look at PHP Savant http://phpsavant.com/docs/
I'd checkout Zend_View and how it accomplished view rendering.
You can get the source of View and AbstractView on github - unfortunaly I don't find the current repository (in svn) that easy to browse.
Essentially the view variables are contained in a View object (which your controller would have access to), then the template (plain old php document) is rendered inside that object. That method allows the template access to $this.
It would be something like:
<?php
class View
{
public function render()
{
ob_start();
include($this->_viewTemplate); //the included file can now access $this
return ob_get_clean();
}
}
?>
So in your controller:
<?php
class Controller
{
public function someAction()
{
$this->view->something = 'somevalue';
}
}
?>
And your template:
<p><?php echo $this->something;?></p>
In my opinion this pattern allows you much flexibility with the view.
I created my own MVC for the free PHP course I'm conducting for a handful of people wanting to get better at PHP.
By far the best way to do this is to use the Command + Factory pattern.
E.g.
interface ControllerCommand
{
public function execute($action);
}
In each controller:
class UserController implements ControllerCommand
{
public function execute($action)
{
if ($action == 'login')
{
$data['view_file'] = 'views/home.tpl.php';
}
else if ($action == 'edit_profile')
{
$data['view_file'] = 'views/profile.tpl.php';
$data['registration_status'] = $this->editProfile();
}
return $data;
}
}
From your main front controller:
$data = ControllerCommandFactory::execute($action);
if (!is_null($data)) { extract($data); }
/* We know the view_file is safe, since we explicitly set it above. */
require $view_file;
The point is that every Controllercommand class has an execute function and that returns its view and any data for that view.
For the complete MVC, you can access the open source app by emailing me at theodore[at]phpexperts.pro.
Related
Im using phalcon 2.0.0 and i am trying to call a function with in another function but from the same class like shown below, for some reason i get a blank page. And when i comment the calling of 2nd function from first, the page loads properly.
<?php
use Phalcon\Mvc\User\Component;
class Testhelper extends Component {
public function f1($data) {
$tmp = $this->f2($data);
return $tmp;
}
public function f2($data) {
return '5'; // just testing
}
}
And btw im accessing the f1 function by the volt function extender like this
$compiler->addFunction('customfunc', function($resolvedArgs, $exprArgs) {
return 'Testhelper ::f1('.$resolvedArgs.')';
});
if someone could help me, it would be deeply appreciated.
Thanks guys
You are trying to call TestHelper f1() statically in Volt, where your class does not expose that function as a static.
You can change your code like this:
<?php
use Phalcon\Mvc\User\Component;
class Testhelper extends Component
{
public static function f1($data)
{
$tmp = self::f2($data);
return $tmp;
}
public static function f2($data)
{
return '5'; // just testing
}
}
and your Volt function will work. However you have to bare in mind that because you are calling things statically you won't have immediate access to all the di container services that the Component offers like so:
$this->session
$this->db
You will need to modify your code to pick the di container using the getDefault()
Another option is to use the code as you have right now, but register the TestHelper in your di container like so:
$di->set(
'test_helper',
function () {
return new TestHelper();
}
);
and then your volt function will need to change to:
$compiler->addFunction(
'customfunc',
function ($resolvedArgs, $exprArgs) {
return '$this->test_helper->f1('.$resolvedArgs.')';
}
);
My current implementation:
class SomeController extends AppController
{
function someaction()
{
$d['text'] = "ahoy!";
$this->render("someactionView", $d);
}
}
And in AppController:
function render($file, $data = "")
{
require "views/" . $file . ".php";
}
And the $data will be available in the views file. Is this a correct implementation? Are there any fallacies with this implementation?
And the $data will be available in the views file. Is this a correct
implementation? Are there any fallacies with this implementation?
Basically you do implement it like the most frameworks do. There's a couple of problems with that:
A controller takes an input and sends an output (which breaks the Single-Responsibility Principle)
A view is tightly coupled to HTML. Because of this, you cannot re-use the same view for another stuff, like XML, JSON.
If you do require "views/" . $file . ".php"; in render() method - you again tighly couple it. What if you change the location of views? Then you would have to slightly rewrite your method. This approach merely kills reuse-ability.
To refresh your basic knowledge:
Controller (also known as Editor)
Serves only singular purpose. It changes model state - that is, it should take an input that comes from $_POST, $_GET, $_FILES, $_COOKIE. In controller only variable assignment should be done and nothing more.
class Controller
{
public function indexAction()
{
$this->view->setVar('age', $this->request->getPostParam('age'));
$this->view->setVar('user', $this->request->getPostParam('user'));
//...
}
}
View
A view has a direct access to a model. In order to make make views more re-usable and maintainable you'd better pass required things as function parameters (or via setters)
class View
{
public function render($templateFile, array $vars = array())
{
ob_start();
extract($vars);
require($templateFile);
return ob_get_clean();
}
}
How the view should be initialized and how the variables should be passed to it?
First of all - a view should be instantiated outside MVC-triad. Since a controller writes either to view or model - you'd pass variables to view via controller.
$model = new Model();
$view = new View($model);
$controller = new Controller($view);
// This will assign variables to view
$controller->indexAction();
echo $view->render();
Note : In real world scenario, a model isn't a class, but abstraction layer. I call it Model for demonstration purposes.
IMO the render() method belongs to the view and not to the controller. The code should look like this:
Controller:
class SomeController extends AppController
{
function someaction()
{
$d['text'] = "ahoy!";
$view = new SomeActionView();
$view->assign('data', $d);
echo $view->render();
}
}
View Base Class:
class View
{
protected $data;
function render($template) {
ob_start();
// you can access $this->data in template
require "views/" . $template . ".php";
$str = ob_get_contents();
ob_end_clean();
return $str;
}
function assign($key, $val) {
$this->data[$key] = $val;
}
}
Extend View class
class SomeActionView extends View
{
public function render($template = 'someActionTemplate') {
return parent::render($template);
}
}
Is this a correct implementation? Are there any fallacies with this implementation?
Short answer: no and several.
First of all, what you have there is no a view. It's just a dumb php template. Views in MVC are instance, that contain the UI logic for the application. They pull information from model layer and, based on information they receive, create a response. This response can be simple text, JSON document, a HTML page assembled from multiple templates or simply a HTTP header.
As for controller, it's only task is to alter the state of model layer and (on rare occasions) the current view. Controllers do not initialize the views nor do the populate templates.
I have this function on my View Page, which truncates some data to present it on my table.
function truncate($mytext) {
//Number of characters to show
$chars = 100;
$mytext = substr($mytext,0,$chars);
$mytext = substr($mytext,0,strrpos($mytext,' '));
return $mytext;
}
I set a local variable for my dynamic text:
$mydescription = $value['PROBLEM_DESCRIPTION']
On the same page, I have this:
echo '<td><p>' .truncate($mydescription). '; ?></p><</td>
And it works perfect, so my question is how can apply this on an MVC architecture using Codeigniter?
If somebody has an idea let me know, thanks!!!
You should at least define this function as a method of your model (where you take this description from).
class TextModel extends CI_Model {
...
public function getShortDescription(){
$chars = 100;
$mytext = substr($this->mytext,0,$chars);
$mytext = substr($mytext,0,strrpos($mytext,' '));
return $mytext;
}
}
And the in your controller smth along the lines of:
class Blog extends Controller {
function index()
{
$this->load->model('TextModel');
$data['short_description'] = $this->TextModel->getShortDescription();
$this->load->view('blogview', $data);
}
}
And at last, in your view:
echo '<td><p>'.$short_description.'</p></td>';
I'm not familiar with CodeIgniter, but I guess any data manipulation should be done in models, thus you will keep your controllers 'thin' and won't violate MVC paradigm.
Since you already have a working code, all you need to decide is where in the MVC paradigm of CI should this be transferred to. I would suggest putting this inside a helper. Helpers in CI are just what the name suggests, a simple php file with a bunch of functions that help you to get certain tasks done. Suppose you put this inside a file called 'utility.php' You could then include this file wherever you need using
$this->load->helper('utility');
and then call the truncate function.
Now, if you have a bunch of related tasks which you want to neatly organize in a class based structure you could create your own CI library. Suppose you created a library named 'truncate', then similar to helpers these are loaded as
$this->load->library('truncate');
Personally, for a bunch of utilities I would just put them all into a helper instead of extending core CI classes or creating a custom library.
You could do it in the controller:
class Something extends CI_Controller{
public function index(){
$data['mytext'] = $this->_truncate('some text to truncate');
//truncated text will be available in the view as $mytext
$this->load->view('some_view', $data);
}
private function _truncate($text = NULL){
if($text){
$chars = 100;
$mytext = substr($text,0,$chars);
$mytext = substr($text,0,strrpos($text,' '));
return $mytext;
}
}
}
Edit:
You are calling db stuff in your view which is entirely not Codeigniter MVC.
This is what it might look like in MVC:
Controller
class Something extends CI_Controller{
public function index(){
$test_text = $this->my_model->get_text();
$data['test_text'] = $this->_truncate($test_text);
//truncated text will be available in the view as $test_text
$this->load->view('some_view', $data);
}
private function _truncate($text = NULL){
if($text){
$chars = 100;
$mytext = substr($mytext,0,$chars);
$mytext = substr($mytext,0,strrpos($mytext,' '));
return $mytext;
}
}
}
Model
class My_Model extends CI_Model{
public function get_text(){
//$this->db->get_where....or other db functions
return "testing text... this is only a test";
}
}
View
<html>
<body>
<b><?php echo $test_text; ?></b>
</body>
The current answers explain that you can add the method to the model and also the controller.
Functionally, this is no problem, as you said the function works perfectly wherever you place it.
What happens when you need to call the same method in another controller? Or with a different model?
It is most likley you will end up with some code duplication.
As a possible soultion, you could create a library of classes that allow you to keep all you string
manipulation in one place. Simply add more methods to your class...
// example/libary/path/TextFormatter.php
class TextFormatter
{
protected $_text = '';
public function __construct($text)
{
$this->_text = $text;
return $this;
}
public function truncate($maxChars = 100)
{
$truncated = substr($this->_text, 0, $maxChars);
$truncated = substr($truncated, 0, strrpos($truncated,' '));
return $truncated;
}
public function stripHtml()
{
///.....
}
public function doSomethingElseWithText()
{
}
}
Then in the controller...
// Controller.php
class SomeController extends Controller
{
public function index()
{
$formatter = new TextFormatter('Example text string');
$data['formattedText'] = $formatter->truncate();
}
}
The only caveat is the TextFormatter class needs to be autoloaded/callable from wherever you use it.
Controller: create a controller truncate.php.
<?php
Class Truncate extends CI_Controller {
function __construct() {
parent::__construct();
}
function truncate_text($mytext) {
//Number of characters to show
$chars = 100;
if(empty($mytext))
echo '$mytext is empty.';
$mytext = substr($mytext,0,$chars);
$data['mytext'] = substr($mytext,0,strrpos($mytext,' '));
$this->load->view('truncate_view',$data);
}
View: create a view file truncate_view.php
<?php
echo '<td><p>'.$mytext.'</p></td>';
//Write your other view code if any are present.
?>
I've got a bunch of functions that I want to move into a class. They're currently split into a couple of fairly long files. I'd prefer not to have one 2500 line file, but as far as I can tell, you can't use include to split a class up into multiple files. In theory, I could group the functions in different classes, but they're closely related enough that I feel like they belong together, and splitting them will reduce some of the utility that I'm hoping to get from moving away from a procedural approach (with shared properties, rather than a bunch of parameters that are in nearly every function).
I know this is a bit vague, but any suggestions/pointers? If it matters, this is for a prototype, so ease of code management takes precedence over security and performance.
UPDATE: Let me see if I can remove some of the vagueness:
This class/set of functions outputs the html for a complex form. There are many different sections and variations within each section, depending on about 5 or 6 parameters, which are currently passed into the functions. I was hoping to define the parameters once as properties of the class and then have access to them from within all of the section-creation methods. If I use sub-classes, the values of those properties won't be initialized properly, hence the desire for one class. (Hmm... unless I define them as static. I may have just answered my own question. I'll have to look to see if there's any reason that wouldn't work.)
I've currently got a mess of functions like:
get_section_A ($type='foo', $mode='bar', $read_only=false, $values_array=array()) {
if ($this->type == 'foo') { }
else ($this->type == 'foo') { }
}
So I was initially imagining something like:
class MyForm {
public $type; // or maybe they'd be private or
public $mode; // I'd use getters and setters
public $read_only; // let's not get distracted by that :)
public $values_array;
// etc.
function __constructor ($type='foo', $mode='bar', $read_only=false, $values_array=array()) {
$this->type = $type;
// etc.
}
function get_sections () {
$result = $this->get_section_A();
$result .= $this->get_section_B();
$result .= $this->get_section_C();
}
function get_section_A() {
if ($this->type == 'foo') { }
else { }
}
function get_section_B() {}
function get_section_C() {}
// etc. for 2500 lines
}
Now I'm thinking something like:
// container class file
class MyForm {
static $type
static $mode
static $read_only
static $values_array
// etc.
function __constructor ($type='foo', $mode='bar', $read_only=false, $values_array=array()) {
MyForm::$type = $type;
// etc.
}
function get_sections () {
$result = new SectionA();
$result .= new SectionB();
$result .= new SectionC();
}
}
// section A file
class SectionA extends MyForm {
function __constructor() {
if (MyForm::$type == 'foo') { }
else { }
}
function __toString() {
// return string representation of section
}
}
// etc.
Or probably I need an abstract class of FormSection where the properties live.
Any other ideas/approaches?
I'd split them up into as many classes as you want (or as many that make sense) and then define an autoloader to obviate inclusion headaches.
EDIT
Ok, after seeing more of your code - I think you're approaching subclasses wrong. You have lots of if statements against $type, which signals to me that that is what the polymorphism should be based on.
abstract class MyForm
{
protected
$mode
, $read_only
, $values
;
public function __construct( $mode, $read_only=false, array $values = array() )
{
$this->mode = $mode;
$this->read_only = (boolean)$read_only;
$this->values = $values;
}
abstract function get_section_A();
abstract function get_section_B();
abstract function get_section_C();
// final only if you don't want subclasses to override
final public function get_sections()
{
return $this->get_section_A()
. $this->get_section_B()
. $this->get_section_C()
;
}
}
class FooForm extends MyForm
{
public function get_section_A()
{
// whatever
}
public function get_section_B()
{
// whatever
}
public function get_section_C()
{
// whatever
}
}
Usually I do something like this:
class one
{
public function __get($key)
{
// require __DIR__ / $key . php
// instanciate the sub class
}
public function mainMethod()
{
}
}
class one_subOne extends one
{
public function otherMethod()
{
}
}
class one_subTwo extends one
{
public function anotherMethod()
{
}
}
$one->mainMethod();
$one->subOne->otherMethod();
$one->subTwo->anotherMethod();
As far as building the view is concerned, you might like to try the CompositeView pattern.
Here's a small example of how it could look in PHP. Pretend, for the sake of this example, that View::$html is encapsulated in a Template class that can load html from disk and allows you to inject variables, handles output escaping, etc.
interface IView {
public function display();
}
class View implements IView {
public $html = '';
public function display() {
echo $this->html;
}
}
class CompositeView implements IView {
private $views;
public function addPartial(IView $view) {
$this->views[] = $view;
}
public function display() {
foreach ($this->views as $view) {
$view->display();
}
}
}
The reason for the IView interface is to allow you to build composite views with other composite views.
So now consider a form with three parts: header, body and footer.
class HeaderView extends View {
public function __construct() {
$this->html .= "<h1>Hi</h1>\n";
}
}
class BodyView extends View {
public function __construct() {
$this->html .= "<p>Hi there.</p>\n";
}
}
class FooterView extends View {
public function __construct() {
$this->html .= "<h3>© 2012</h3>\n";
}
}
(Again, you wouldn't just write HTML into that public variable and handle output escaping yourself. You'd likely reference a template filename and register your data via the template's interface.)
Then, to put it all together you would go:
$view = new CompositeView();
// here you would make decisions about which pieces to include, based
// on your business logic. see note below.
$view->addPartial(new HeaderView());
$view->addPartial(new BodyView());
$view->addPartial(new FooterView());
$view->display();
So now your views can be composed and the fragments reused, but you can easily make a mess with the code that builds them, especially if you have a lot of conditions and many different possible outcomes (which it sounds like you do.) In that case, the Strategy pattern will probably be of some help.
If you haven't already read UncleBob's SOLID article, do it before anything else! At least the Single Responsibility Principle. I would also recommend reading Refactoring to Patterns by Joshua Kerievsky at some point.
If you want to do OOP, separate the concerns and encapsulate them into appropriate classes. Combine them either by extending them or by composition or better aggregation. Remove any duplicate code. Dont repeat yourself.
In your case, separate the stuff that is about any Form from the stuff that is about your specific form. The code that can be used for any Form is the code you want to place into a generic Form class. You can reuse this in later projects. For an example of a very complex Form class, check out Zend_Form.
Anything in your code related to the/a specific form gets into a class of it's own that extends the generic form. Assuming from the type property given in your code, you might end up with multiple special purpose form classes (instead of one-type-fits-all-form), which will likely eliminate the complexity from the getSection methods and make your code a lot easier to maintain because you can concentrate on what a specific type of form is supposed to look like and do.
Lastly, if you got code in there that fetches data for the form from within the form or is otherwise not directly related to form building, remove it and make it into a separate class. Remember, you want to separate concerns and your form classes' concern is to build a form, not get it's data or something. Data is something you will want to pass to the form through the constructor or a dedicated setter.
They are all in different files, which means that they were different enough to group by file. Just take the same logic when building them into classes. I have a Page object that deals with building the page. Technically the HTML for my page header is part of the page, but I separate it into a Page_HTML class for maintaining my sanity and not creating gigantic classes.
Also, I tend to make the sub_classes, like Page_HTML in this case, static, instead of instantiating it. That way I can access the $this variables in the Page class, but still group it into another class.
class Page
{
function buildPage($content)
{
$content = Page_HTML::getHeader() . $content;
}
}
class Page_HTML
{
function getHeader()
{
}
}
This class/set of functions outputs
the html for a complex form.
Why not remove PHP from the equation? It seems you're using PHP to organize views which can be done easily with the filesystem. Just write the views in HTML with as little PHP as possible. Then use PHP to map requests to views. I'm assuming you're processing forms with PHP, and you could continue to do so. However, your classes will become much smaller because they're only accepting, verifying and presumably saving input.
I have a PHP web application built with CodeIgniter MVC framework. I wish to test various controller classes. I'm using Toast for unit testing. My controllers have no state, everything they process is either saved into session or passed to view to display. Creating a mock session object and testing whether that works properly is straightforward (just create a mock object and inject it with $controller->session = $mock).
What I don't know, is how to work with views. In CodeIgniter, views are loaded as:
$this->load->view($view_name, $vars, $return);
Since I don't want to alter CI code, I though I could create a mock Loader and replace the original. And here lies the problem, I cannot find a way to derive a new class from CI_Loader.
If I don't include the system/libraries/Loader.php file, the class CI_Loader is undefined and I cannot inherit from it:
class Loader_mock extends CI_Loader
If I do include the file (using require_once), I get the error:
Cannot redeclare class CI_Loader
Looks like CI code itself does not use require_once from whatever reason.
Does anyone here have experience with unit testing CodeIgniter powered applications?
Edit: I tried to inject a real loader object at run-time into a mock class, and redirect all calls and variables with __call, __set, __get, __isset and __unset. But, it does not seem to work (I don't get any errors though, just no output, i.e. blank page from Toast). Here's the code:
class Loader_mock
{
public $real_loader;
public $varijable = array();
public function Loader_mock($real)
{
$this->real_loader = $real;
}
public function __call($name, $arguments)
{
return $this->real_loader->$name($arguments);
}
public function __set($name, $value)
{
return $this->real_loader->$name = $value;
}
public function __isset($name)
{
return isset($this->real_loader->$name);
}
public function __unset($name)
{
unset($this->loader->$name);
}
public function __get($name)
{
return $this->real_loader->$name;
}
public function view($view, $vars = array(), $return = FALSE)
{
$varijable = $vars;
}
}
Alternatively, you could do this:
$CI =& get_instance();
$CI = load_class('Loader');
class MockLoader extends CI_Loader
{
function __construct()
{
parent::__construct();
}
}
Then in your controller do $this->load = new MockLoader().
My current solution is to alter the CodeIgniter code to use require_once instead of require. Here's the patch I'm going to send to CI developers in case someone needs to do the same until they accept it:
diff --git a/system/codeigniter/Common.php b/system/codeigniter/Common.php
--- a/system/codeigniter/Common.php
+++ b/system/codeigniter/Common.php
## -100,20 +100,20 ## function &load_class($class, $instantiate = TRUE)
// folder we'll load the native class from the system/libraries folder.
if (file_exists(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT))
{
- require(BASEPATH.'libraries/'.$class.EXT);
- require(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
+ require_once(BASEPATH.'libraries/'.$class.EXT);
+ require_once(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
$is_subclass = TRUE;
}
else
{
if (file_exists(APPPATH.'libraries/'.$class.EXT))
{
- require(APPPATH.'libraries/'.$class.EXT);
+ require_once(APPPATH.'libraries/'.$class.EXT);
$is_subclass = FALSE;
}
else
{
- require(BASEPATH.'libraries/'.$class.EXT);
+ require_once(BASEPATH.'libraries/'.$class.EXT);
$is_subclass = FALSE;
}
}
I can't help you much with the testing, but I can help you extend the CI library.
You can create your own MY_Loader class inside /application/libraries/MY_Loader.php.
<?php
class MY_Loader extends CI_Loader {
function view($view, $vars = array(), $return = FALSE) {
echo 'My custom code goes here';
}
}
CodeIgniter will see this automatically. Just put in the functions you want to replace in the original library. Everything else will use the original.
For more info check out the CI manual page for creating core system classes.
I'm impressed by the code you are trying to use.
So now I'm wondering how the 'Hooks' class of CodeIgniter could be of any help to your problem?
http://codeigniter.com/user_guide/general/hooks.html
Kind regards,
Rein Groot
The controller should not contain domain logic, so unit tests make no sense here.
Instead I would test the controllers and views with acceptance tests.