Basic MVC in concrete5
In concrete5, any page shown to a visitor is either an instance of a page type (this is the most common - typical examples include "Left Sidebar", "About", "Three Column", etc...) or a single page. These are one-off pages that typically contain functionality (e.g. "Dashboard", "Login", "User Profile", etc...). These single pages, and even page type instances, can have controllers attached to them to allow a separation between the presentation layer of the single page or page type template, and the functionality bound to it.
While they have an imposing name, controllers are really just PHP classes with functions that are automatically run at certain times. This makes them ideal for handling form submissions and page rendering. Controllers allow messy business logic and PHP functions to be kept away from the actual presentation layer, which makes code easier to read and more maintanable.
Single Pages do not require controllers, but in most cases, a single page without a controller would be better served by having one.
Setting up a form to require a controller
Let's say we have a search form in our "my_profile.php" example that's lets us enter a user's name, and get information about them back. This is a very simple example, but hopefully it will show the way single pages and controllers can help make web development easier.
<form method="post"
action="<?php echo $this->action('search_user')?>">
<input type="text" name="uName" value="" />
<input type="submit" value="Search" />
</form>
This is a simple form. But notice the action: by using the View::action() method we're submitting the form back to our current page, and setting the task to be run to search_user. This function will live within the controller, and be automatically run when this page is submitted.
Note: If we wanted to submit the page to a different single page, but still use a task, we could do so by specifying the action as
<?php echo $this->url('/path/to/second/single/page', 'second_search_user')?>
Finally, View::url() can take a variable number of arguments. The first one is always the view to which the URL will link, the second is always the function that will be run within that controller, but all arguments after that will be passed to that function. For example, let's say I have a single page named "calculator" that I want to at:
http://www.yoursite.com/index.php/calculator/ And I want to run the "multiply" method on calculator, and the multiple method takes two numbers:
public function multiply($num1, $num2) {
$this->set('answer', $num1 * $num2);
}
If I wanted to multiply 5 * 10, I would write
<a href="<?php echo $this->url('/calculator', 'multiply', 5, 10)?>">Multiply Here!</a>
Creating a Single Page Controller
Controllers are even easier to create than single pages. Simply add the correct PHP class, correctly named, to the controllers/ directory in your web root, and the controller will be available.
Create an empty file named controllers/my_profile.php Open the file in a text editor, and paste the following information into the file: Paste:
<?php
class MyProfileController extends Controller {
public function search_user() {
print 'I am running!';
}
}
The class must be named correctly, or else it won't be run. You must name your controllers the full name of their directory, plus filename, plus "Controller", and capitalize any instance where a directory occurs or an underscore occurs. That sounds confusing but hopefully gets easier to understand with some examples.
/controllers/my_profile.php = MyProfileController /controllers/dashboard/sitemap.php = DashboardSitemapController /controllers/cart/checkout/my_test.php = CartCheckoutMyTestController
All of these classes must extend the Controller class. Don't worry about loading this class - it's already loaded by the time your controller is called.
Finally, like views, controllers can be nested in directories. If your controller is nested in a directory but needs to refer to the view for that directory, name this file "controller.php."
For example, if you have a single page at
/single_pages/cart/view.php
You'd place your controller at
/controllers/cart/controller.php
and name it
CartController
Passing data from views to controllers, and back again
Notice the search_user function in the controller? This function simply prints out a line of text - and in our example above, whenever you submit the form, this line of text will be printed out at the top of the page, because search_user is automatically running.
Of course, this isn't that useful - since we'll never want to print data out at this particular spot on the page. Instead, we need a way to pass data, like strings, numbers, and objects, from the controller into the view itself. We also need a good way to get data from the form into the controller.
Let's change our search_user function so it looks like this:
public function search_user() {
$uName = $this->post('uName');
$this->set('uNamePosted', $uName);
}
And add the line
<?php echo $uNamePosted; ?>
Above our form.
Now, when we post the form, we should see the name of the user that we posted in the text field show up above our form, because the first line ($this->post()) grabs the data from the post, and the second set() line passes it back to the view. Whatever you pass as the first parameter to set() automatically becomes a PHP variable in your view, with whatever information you place in the second parameter. This information can be a a simple string, or a complex PHP object, or anything in between.
Only displaying data when a method gets run
If you only want the $uNamePosted variable to be displayed in the page when the search_user method is run, change this:
<?php echo $uNamePosted; ?>
To this:
<?php
if ($this->controller->getTask() == 'search_user') {
echo $uNamePosted;
}
?>
These lines query the controller for the particular task that's being run. When the page is just being shown, the task is "view" by default. But when the form is submitted, we've setup that form to call the "search_user" task, and that information is available from within the $this->controller object, which maps directly to the controller being used on that page. The $this->controller object should be available in every view, all the time.
Using Controllers with Page Types Instead of Single Pages
You'll notice I've been using the word "view" instead of single page throughout this tutorial. That's because view corresponds to any concrete5 page, and single pages are very specific pages. That's the beauty of controllers: they can be used on regular concrete5 page types as well as single pages.
Let's say you've got a page type you're using throughout your site, named "event," and a file named "event.php" within your active theme. But on this event template, you've got a form asking people to RSVP for this event. You can use controllers to separate the logic of that form from the presentation.
Make the form within your event template submit to action('rsvp')?>. You should recognize that this will submit the form back to the current page, and run the rsvp() function within the page type's controller.
Create the controller file here: /controllers/page_types/event.php. Notice, page type controllers live one level below the root. They are given the same filename as the template in the theme.
Place the following code within your page type's controller.
The code:
<?php
class EventPageTypeController extends Controller {
public function rsvp() { }
}
Whatever you place within rsvp() will automatically be run when that form is submitted. (Note: for this to work, you will have to make sure your page has a valid path.)