Redirection PHP inside MVC - php

I am developing a personal framework purely on PHP.
Let's say I am in a method inside a controller and I want to redirect to another page how would I do that? At least conceptually.

If your developing MVC You should have an input class and an output class (I/O), you should create a function called redirect within the output class and build the new url from your base url like so:
public function redirect($controller,$method = "index",$args = array())
{
global $core; /* Guess Obviously */
$location = $core->config->base_url . "/" . $controller . "/" . $method . "/" . implode("/",$args);
/*
* Use #header to redirect the page:
*/
header("Location: " . $location);
exit;
}
This way within your controller you can simply use the input class do your redirect for you.
class MyController extends BaseController
{
public function login()
{
if($this->library->session->exists("user_logged_in") === false)
{
$this->library->output->redirect("MyController","login",array("from:login"));
}
}
/*
..More Here
*/
}

header("Location: http://domain.com/folder/page.html", 301);
exit();
This code must be the first output of the script. You can not perform redirection after generating any output to the client. Once you have sent the redirection to the client, you can exit the script because any additional output generated would not be seen by the user.

Related

Route class doesn't working properly

I'm re-writing an application previously written in CodeIgniter framework, my customer want have an independent app and a pure php code. Anyway don't tell me not to reinvent the wheel because I already know that my client is wrong. We come to the problem.
I'm looking for a simple Route class that allow me to call any files from any location. I found this simple and powerfull class, this is the repository.
I've implemented it in my project, copy the route.php file inside the index location and change my .htaccess as the documentation says. Instead of all, this is the structure of my project:
/ PUBLIC_HTML
/ application
/ controllers
/backend.php
/user.php
/ helpers
/ models
/ views
/backend
/backend.php
/calendar.php
/user
/users.php
/panel.php
/ assets
/ files used by frontend...
/ system
/ configuration
/ constant
/ .htaccess
/ index.php
/ route.php
when the applicationi is started from the index.php the configuration file is included for establish the connection with the database. In the same configuration file I've imported the route.php. Now my index.php page is very simple, like this:
// Check if the session is set
if(isset($_SESSION['user_info']['id_roles']))
{
switch($_SESSION['user_info']['id_roles'])
{
case 1: //Admin
$route->add('/application/controllers/backend', 'index');
$route->submit();
break;
case 2: //Provider
$route->add('/application/controllers/backend');
$route->submit();
break;
case 3: //Customer
$route->add('/application/controllers/appointments');
$route->submit();
break;
}
}
else
{
// Session isn't set, so I redirect user to login page
header('Location: application/views/user/login.php');
exit; // stop
}
so if the session is set I redirect the user type to the correct location, against, if isn't set I show the login page. The login page simply valorize the session variable, if the response is success the user is redirected again to the index page.
The problem now is that, for example when the admin is logged (so case 1), the route class doesn't valorize the $uri, a bit example:
public function submit()
{
$uri = isset($_REQUEST['uri']) ? $_REQUEST['uri'] : '/';
$uri = trim($uri, $this->_trim);
$replacementValues = array();
// Iterate on the list of URI
foreach($this->_listUri as $listKey => $listUri)
{
// Looking for a match..
if(preg_match("#^$listUri$#", $uri))
{
// Replace the values
$realUri = explode('/', $uri);
$fakeUri = explode('/', $listUri);
// Get value with .+ with real URI value
foreach($fakeUri as $key => $value)
{
if ($value == '.+')
{
$replacementValues[] = $realUri[$key];
}
}
// Pass array arguments..
call_user_func_array($this->_listCall[$listKey], $replacementValues);
}
}
}
check the full class here.
the $uri variable should be valorized with the current uri of the server but I tried with a var_dump and I get an empty value.Then the match condition is never invoked, and the correct file isn't displayed. I don't know why, I just want to understand why it is not working, I'm probably doing something wrong, someone can help me understand?
Completing the example of the admin redirect, I want to show only what is contained in the backend.php, which should be loaded from the route.
<?php
session_start();
class Backend
{
// Construct of class
public function __construct()
{
}
// Display the main backend page
public function index($appointment_hash = '')
{
$_SESSION['user_info']['hash'] = $appointment_hash;
$_SESSION['user_info']['dest_url'] = SystemConfiguration::$base_url . "backend";
// some content..
}
...
So how you can see, I simply want call the index function of the backend controller when I call ->add() for add the url of the controller to call, and ->submit() to perform the operation.
What am I doing wrong?
UPDATE - Router request task
First I updated the stack of my application.
I think at this point it's best to ask your expert advice on which OpenSource Router allow me to implement this tasks:
1. Import controller
Import all controllers that are contained in my folder called controllers. Once you imported I will simply call the instance of the router, and call up a specific function of the controller loaded. Example:
$router->backend->index();
where index(); It represents the function of controller called backend.
This must be done in my entire application. Also I would make sure that we can bring up the function also via the URL, in particular, if I insert this url:
localhost/application/controllers/backend/index
I can call the same function simply referring url.
2. Requests ajax
 
Delivery My Router must be able to run ajax requests from javascript, especially if I use this code:
$('#login-form').submit(function(event)
{
var postUrl = GlobalVariables.baseUrl + 'user/ajax_check_login';
var postData =
{
'username': $('#username').val(),
'password': $('#password').val()
};
$('.alert').addClass('hidden');
$.post(postUrl, postData, function(response)
{
I want to call the user function ajax_check_login.
contained in the controller user, imagine GlobalVariables.baseUrl, what is... How can we think is the url of the base application that can obviously vary.
Note that my Controller function return a json format.
3. Load view
in my application there is the view, which are saved in .php, but containing html file, an example of view (previously written in CodeIgniter) pul you find here.
I want to be able to call a view and show the new user html markup. I also need to call also more views at the same instant, for example at times I divide the body into:
header, body, footer
To simplify the understanding of what $this refers to in a view, since a view is "loaded" by a controller method, the view is still run in the same scope as that method, meaning $this can have a different context depending on which class loaded it.
For example:
class Controller1 extends CI_Controller {}
In any view file loaded in this example controller, $this refers specifically to the Controller1 class, which can access CI_Controller public and protected properties/methods as well (like the Loader or Input classes, which are assigned to the load and input properties of CI_Controller) since it extends that class.
Controllers are still just plain old PHP classes. If I were to do this:
class Controller1 extends CI_Controller {
$this->foobar = 'Hello';
}
class Controller2 extends CI_Controller {
$this->foobar = 'World';
}
...if we load the same view file in any method of either of these controllers, using $this->foobar in that view file will return a different value.
But this for now it's not important, I just want to be as clear as possible.
I start a bount and lose all my rep, but I really want to get help in this and learn.
You need to look at the index.php provided with the Router as an example. You'll see how to set the routes:
you always have to have 2 arguments: 1. uri, 2. function
according to the example the function has to be not the function name 'index', but a function body function(){...}. Maybe reference would work as well.
Routing IMHO should be not dependant on session (though it could be, but that's not the usual way to do)
instead of $router->backend->index();, I will have a common block of code at the end of the file so you don't have to copy&paste the code many times.
I'll show you with backend in your way, and then with appointments how could you make it general. So you should make your routes something like:
<?php
session_start();
include 'route.php';
$phpClass = false;
$view = false;
$func = false;
$route = new Route();
if(isset($_SESSION['user_info']) && isset($_SESSION['user_info']['id_roles'])) {
$route->add('/application/controllers/backend', function(){
echo 'You are now at backend page, and the role is ';
switch($_SESSION['user_info']['id_roles']) {
case 1: echo 'Admin'; break;
case 2: echo 'Provider'; break;
case 3: echo 'Customer'; break;
}
include 'backend.php';
$backend = new Backend();
$backend->index(/* I don't know what 'hash' could be */);
});
// more general case:
$route->add('/application/controllers/appointments', function(){
// we only set the global $phpClass variable, and the rest is common, see below
global $phpClass, $func;
$phpClass = 'Appointements';
$func = 'index'; // default is index, if it wasn't given on the url
});
$route->add('/application/controllers/appointments/add', function(){
// we only set the global $phpClass variable, and the rest is common, see below
global $phpClass, $func;
$phpClass = 'Appointements';
$func = 'add';
});
$route->add('/application/controllers/appointments/delete', function(){
// we only set the global $phpClass variable, and the rest is common, see below
global $phpClass, $func;
$phpClass = 'Appointements';
$func = 'delete';
});
$route->add('/application/controllers/foo', function(){
global $phpClass;
$phpClass = 'Foo';
});
$route->add('/application/controllers/bar', function(){
global $phpClass;
$phpClass = 'Bar';
});
$route->add('/application/views/bar', function(){
global $phpClass, $view;
$phpClass = 'View';
$func = 'show';
$view = 'bar.php';
});
$route->submit();
} else {
// Session isn't set, so I redirect user to login page
header('Location: /application/views/user/login.php');
exit; // stop
}
if ($phpClass === false || $func === false) {
die("You have to have both controller and function un the url");
}
// if we got here it means we're in the common case
// include the necessary controller. If you want you can
// include all of them at the top of the php and remove this line
include 'application/controllers/' . strtolower($phpClass) . '.php';
$controller = new $phpClass();
// this is instead of `$router->backend->index();`:
$controller->$func(/*$hash*/);
// I don't know what '$hash' could be, maybe javascript could send it via ajax
?>
controllers/view.php:
class View {
public function show() {
global $view;
include 'application/views/' . $view;
}
// here you'll need to have all the things that any of
// your views access as $this->bar :
$config = new stdClass(...);
$array = array();
function get_lang() {global $lang; return $lang;}
//...
}
Example of json response in controllers/user.php:
class User {
public function logged_in() {
$username = isset($_SESSION) && isset($_SESSION['username']) ? $_SESSION['username'] : false;
$response = array(
'success' => $username !== false ? 'OK' : 'ERROR',
'username' => $username
);
echo json_encode($response);
}
}

Where in my MVC PHP app do I put this build function?

I'm trying to convert my PHP app into more of an MVC app. I don't have much experience with MVC and I don't fully understand some of/all of the concepts or how to do it with PHP, so I need some help understanding where a particular function goes.
This function returns some HTML depending on if the user is logged in.
public function buildLoggedInMessage() {
if ($this->User->isLoggedIn()) {
return ' You are logged in as <strong>'.$this->User->getUsername().'</strong> (logout)';
} else {
return ' Login';
}
}
My initial thought was to place this function in my "controller" because it asks the User model if they are logged in (which checks the database record), however it "builds" some HTML, so maybe it should be in the view. Should I move it?
I will eventually move the HTML from the function into a template, so ignore the inline HTML.
Would the function be more suitable in the view if it was like this:
public function buildLoggedInMessage() {
if ($this->Controller->isLoggedIn()) {
return ' You are logged in as <strong>'.$this->User->getUsername().'</strong> (logout)';
} else {
return ' Login';
}
}
and the controller asks the model if the user is logged in?
Thanks.
I think view should not contain any business logic. Views should focus on presenting stuff, so your second solution is bad practice.
More than that, since views focus on the presentation and models handle most of the business logic, controllers should do only the necessary things to link views and models, which means fetch data from model and just insert the data into the view.
so this line of code make no sense because it means you implement business logic in controller:
$this->Controller->isLoggedIn()
Now let's see your first solution.
public function buildLoggedInMessage() {
if ($this->User->isLoggedIn()) {
return ' You are logged in as <strong>'.$this->User->getUsername().'</strong> (logout)';
} else {
return ' Login';
}
}
This function 'return' htmls rather than 'echo' htmls. So who is calling this function? and who will 'echo' the string from this function? I would say this is not a complete controller.
In modern web MVC, there's always some kind of 'router' handle the http requests and execute some instructions related to that. Since you wanna implement MVC pattern, you need to implement that 'router' first.
For example, you can create a 'Member' class which has a 'check' method to achieve the functionality you want.
class Member{
public function check() {
if ($this->User->isLoggedIn()) {
echo ' You are logged in as <strong>'.$this->User->getUsername().'</strong> (logout)';
} else {
echo ' Login';
}
}
}
And you need to implement the router class to handle http requests like 'http://myweb.com/member/check'.
The router code would be something like this:
$url_segments = explode('/', $_SERVER['REQUEST_URI']);
if (count($url_segments) == 4){
$controller_name = $url_segments[2];
$method_name = $url_segments[3];
}
else if (count($url_segments) == 3){
$controller_name = $url_segments[2];
$method_name = $this->default_method;
}
else if (count($url_segments) == 2){
$controller_name = $this->default_controller;
$method_name = $this->default_method;
}
$this->current_controller = $controller_name;
$this->current_method = $method_name;
require BASEPATH . '/controller/' . $controller_name . '.php';
$class_name = ucfirst($controller_name);
$controller = new $class_name($method_name);
call_user_func( array( $controller, $method_name ) );
Create a MVC framework is not an easy work.
I create a simple MVC framework for educational purpose.
https://github.com/howtomakeaturn/PigFramework
Check the index.php file, and you will know what I mean router and controller.
I don't think that the point of MVC is to put HTML in a controller, if I were you I'd send some data back and make an if else statement in my view based on the send data. To make good use of an MVC you first need to understand what it is or does, so I'd recommend searching for a tutorial.
put this function in the controller from where you are calling a login function after if a user authenticated then it will set the session or flash data i.e $this->session->set_flashdata('success', 'you are loged in as $username');
else
redirect('login');

How to prevent POST methods from being accessed via GET in Codeigniter?

I am using Codeigniter . I have the below method - create_client when the user submits a form with all details filled in .
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Admin extends CI_Controller {
public function create_client()
{
// catch all the form data here
//process form data
}
}
The function was designed for accepting a form submit . But if someone tries to access admin/create_client ( GET ) , he can directly execute the function as well . Since there is no form data via the GET statement , this leads to error .
How do I prevent the method being accessed via GET . One solution is to put some checks in the method -
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// do things here
} else {
return false;
}
But I have a lot of such methods and I dont want to alter all such methods . Is there an easy way ? Say for example specifying in the Routes config that this method is a POST function and cant be accessed via GET ?
Try remap:
public function _remap($method, $params = array())
{
switch($method) {
case 'post_method':
case 'post_method2':
if (! $_SERVER['REQUEST_METHOD'] === 'POST')
// return error
}
if (method_exists($this, $method))
return call_user_func_array(array($this, $method), $params);
show_404();
}

Is a POST controller suitable in a php mvc?

I am creating a custom MVC style framework from scratch and am at the point where I need to implement the code to control what happens on POST.
At the moment I have a main index.php which acts as a controller and passes data to other controllers such as:
profilecontroller.class.php
forumcontroller.class.php
At the moment I see two options as to where the POST controllers can go ..
First Approach
Firstly for site wide posts such as login that can occur on any page I would use something like this in the very first index.php to redirect all POST to a specific POST controller that then sends the data to a model to be processed:
if($_POST)
//post controller, works on specific form id's
Alternate Approach
The other option I see would be to build the POST identifier into the model construction sections but I don't think this would be very manageable/wise as they'd always be checked and resulting in more loaded code?
Are there any good/simple examples out there?
I'm creating my mvc to be as light as possible so that's my reason for going from scratch.
In a RESTful setup, you would normally have a controller for an object, say news, and then actions such as add, edit, delete etc.
Within your actions, you should then assert what HTTP method should be used to access the method, if one should be. For example:
<?php
class NewsController extends AbstractController {
public function save() {
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
header('HTTP/1.1 405 Method Not Allowed');
die('Please use POST.');
}
// carry on knowing we're working with a POST request
}
}
Creating a separate controller for POST requests would, as you say, quickly becoming unruly and unmanageable.
If you're looking for a way of handling requests for different HTTP methods within different controller actions, then maybe check out ToroPHP. It's a lightweight (single file) router, where you map a request to a class that's referred to as a handler, and then that handler has methods for different HTTP methods. A quick example:
<?php
require 'lib/torophp/toro.php';
require 'classes/handlers/HomeHandler.php';
$toro = new ToroApplication(array(
array('/', 'HomeHandler')
));
$toro->serve();
And then your HomeHandler would look as follows:
<?php
class HomeHandler {
public function get() {
echo 'Hello, world!';
}
public function post() {
echo 'Try performing a GET request for the home page, buddy.';
}
// and so on...
}
Hope that helps.
This is my default Controller :
<?php
Class Controller_Home{
public $Registery = null;
final public function __construct($Registery){ $this->Registery = $Registery; }
final public function Init($Method=null){
# Quelle action on fait ?
if($Method){
$Split = explode('_', $Method);
$MethodName = 'Action';
foreach($Split as $Splitted){
$MethodName.= '_'.ucfirst($Splitted);
}
if(method_exists($this, $MethodName)){
$this->$MethodName();
} else {
echo '404';
die;
}
} else {
$this->Action_Default();
}
}
final public function Action_Default(){
$this->Registery->Import('Library.Account');
var_dump($this->Registery->Account);
echo 'Default Home';
}
}
As you can see, once you are in Action_Default, you can do whatever you want based on $_GET, $_POST, whatever you want ...
So with this code :
website.com/home/bob/ will use function Action_Bob inside the controller Home (Home::Action_Bob) ... if you see $_POST just put inside Action_Bob this
public function Action_Bob(){
if($_POST){
$this->Action_Bob_Post();
}
// continue
}

How to create modular MVC components in Zend Framework

I've been having problems created modular reusable components in my Zend Framework app. In this case I'm not referring to Zend Framework modules but rather the ability to have a reusable MVC widgety thing if you like. The problems I'm having may be very particular to my implementation, but I'm completely happy to throw it out and start again if someone can point me in the right direction. Anyway, specifics and code will hopefully explain things better and even if what I'm doing is not the best way it should show what I'm trying to achieve:
A simple example is a Mailing List sign up form. I want to include this on several pages of the site which use different Controllers and this presents a few problems in how to process the data and return relevant messages. I don't want to do either of the following as they really smell:
Create a base controller with the form processing in and extend (Bad)
Duplicate form processing code in relevant controllers (Even worse!)
The clean way to go feels to me to create a new Controller to process the mailing list form data, use a View Helper to easily output the form and relevant markup into the desired pages and then redirect back to the page where signup occurred once the form has been processed. However, I'd like to use the form validation provided by Zend_Form, which means I'd need to pass the form object back to the view helper somehow if validation fails but in the same request. I'm currently doing this by setting it as a variable on the view and then forwarding back to the previous page rather than redirecting, which is ok(ish). If validation is ok then I'd prefer to use a redirect back to the original page. I'm having trouble doing this though as I'd like to pass messages back to the component about the state of signup. Normally I'd use the FlashMessenger Action Helper, I could namespace it in this case so messages didn't clash with other page data, but I can't access it from within a View Helper. So currently I'm forwarding in this case too. I'd much prefer a redirect to prevent form resubmissions if a user refreshes the page and to keep the URL clean. I realise I essentially want to have a mini MVC dispatch process within a page and I think that's what the action stack is for? I really don't know much about this though and any pointers would be greatly appreciated. Here's my current code:
Controller:
<?php
class MailingListController extends Zend_Controller_Action {
public function insertAction() {
$request = $this->getRequest();
$returnTo = $request->getParam('return_to');
if(!$request->isPost() || (!isset($returnTo) || empty($returnTo))) {
$this->_redirect('/');
}
$mailingList = new Model_MailingList();
$form = new Form_MailingList();
$returnTo = explode('/', $returnTo);
if($form->isValid($_POST)) {
$emailAddress = $form->getValue('email_address');
$mailingList->addEmailAddress($emailAddress);
$this->view->mailingListMessages = $mailingList->getMessages();
$this->view->mailingListForm = "";
}
else {
$this->view->mailingListForm = $form;
}
$this->_forward($returnTo[2], $returnTo[1], $returnTo[0]);
}
}
return_to is a string containing the current URI (module/controller/action), which is generated in the View Helper. I'd prefer to redirect inside the $form->isValid($_POST) block.
View Helper:
<?php
class Zend_View_Helper_MailingList extends Zend_View_Helper_Abstract {
public function mailingList($form, $messages = "") {
if(!isset($form)) {
$request = Zend_Controller_Front::getInstance()->getRequest();
$currentPage = $request->getModuleName() . '/' . $request->getControllerName() . '/' . $request->getActionName();
$form = new Form_MailingList();
$form->setAction('/mailing-list/insert');
$form->setCurrentPage($currentPage);
}
$html = '<div class="mailingList"><h2>Join Our Mailing List</h2>' . $form;
$html .= $messages;
$html .= '</div>';
return $html;
}
}
Getting an instance of the Front Controller in the View Helper isn't ideal but I'd prefer to encapsulate as much as possible.
If I have a form object where validation has failed I can pass it back into the helper to output with error messages. If I have some messages to render I can also pass them into the helper.
In my view scripts I'm using the helper like so:
<?=$this->mailingList($this->mailingListForm, $this->mailingListMessages);?>
If neither mailingListForm or mailingListMessages has been set on the view by MailingListController, it will output a new form with no messages.
Any help is greatly appreciated!
Using ajax seems to be an optimal way. View Action Helper is used only for the first load of the mailing form.
Controller
class MailingListController extends Zend_Controller_Action {
public function insertAction() {
$request = $this->getRequest();
$form = new Form_MailingList();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
$mailingList = new Model_MailingList();
$emailAddress = $form->getValue('email_address');
$mailingList->addEmailAddress($emailAddress);
$form = $mailingList->getMessages();
}
}
$this->view->form = $form;
}
}
view script insert.phtml
<?php echo $this->form; ?>
Form class
class Form_MailingList extends Zend_Form {
public function init() {
//among other things
$this->setAttrib('id', 'mailing-list-form');
$this->setAction('/mailing-list/insert');
}
}
View Helper
class Zend_View_Helper_MailingList extends Zend_View_Helper_Abstract {
public function mailingList() {
$this->view->headScript()->appendFile('/js/mailing-list.js');
return '<div id="mailing-list-wrap">' . $this->view->action('insert', 'mailing-list') . '</div>';
}
}
JS file mailing-list.js
$(document).ready(function() {
$('#mailing-list-form').submit(function() {
var formAction = $(this).attr('action');
var formData = $(this).serialize();
$.post(formAction, formData, function(data) {
//response going in form's parent container
$(this).parent().html(data);
});
return false;
});
});
I think the way you've done it is pretty close to what I would do. If you set aside the requirement of wanting to display the Zend_Form error messages in the page, then what you do instead is:
The view helper just displays the form (it doesn't need to take the form object or messages as parameters)
The form submits to your other controller as it does now
The mailing list controller redirects (instead of forwarding) back to the return URL on success
The mailing list controller redisplays the form on its own, along with errors on failure
This makes everything much simpler, the only issue is that if there are any validation errors then the user loses their context and gets a plain old page with the form on instead of where they were. You can then address this (either now or at a later date) by changing the form to submit via. Ajax instead, and rendering the errors via. JS. But this would be a fair amount of work.
OK, I've come up with a solution that I feel happier about and solves some of the problems I was facing. Hopefully, this might help someone out who's facing similar issues. The only downside now is that I'm referencing the Model inside the View Helper. Not loose coupling I know but I've seen this done several times before and it's even recommended in the ZF docs as a way to avoid using the 'action' view helper (which will create a new MVC dispatch loop). On the whole, I think the DRYness and encapsulation is worth it, there's probably some other suitable lingo too.
In order to be able to use a redirect back from my MailingListController but maintain the messages from my model and any form validation errors I need to store them in the session. For messages I'd normally use the FlashMessenger action helper, but as getting hold of this in a View Helper is not best practice, it won't handle my form errors and all it's really doing is saving stuff to the session anyway it's unnecessary. I can implement my own session storage in the Model_MailingList, which I can also use for the form errors. I can then repopulate the form with the errors after the redirect and print out any relevant messages. Anyway, here's the code:
Controller:
<?php
class MailingListController extends Zend_Controller_Action {
public function insertAction() {
$request = $this->getRequest();
$returnTo = $request->getParam('return_to');
if(!$request->isPost() || (!isset($returnTo) || empty($returnTo))) {
$this->_redirect('/');
}
$mailingList = new Model_MailingList();
$form = new Form_MailingList();
if($form->isValid($_POST)) {
$emailAddress = $form->getValue('email_address');
$mailingList->addEmailAddress($emailAddress);
}
else {
$mailingList->setFormErrors($form->getMessages());
}
$redirect = rtrim($request->getBaseUrl(), '/') . $returnTo;
$this->_redirect($redirect);
}
}
I've added a method to my Model_MailingList class; setFormErrors($errors) that I pass the error messages from the form if it fails validation. This saves the error array to the session.
I normally use a base model class that has addMessage and getMessages methods. These just access a protected array of messages. In my Model_MailingList I override these methods to store the messages in the session instead. In the addEmailAddress($emailAddress) method I'm already calling addMessage to say whether inserting the email address to the db has been successful.
Model:
<?php
class Model_MailingList extends Thinkjam_Model_DbAbstract {
private $_session;
public function __construct() {
$this->_session = new Zend_Session_Namespace(__CLASS__);
}
public function setFormErrors($errors) {
$this->_session->formErrors = $errors;
}
public function getFormErrors() {
$errors = array();
if(isset($this->_session->formErrors)) {
$errors = $this->_session->formErrors;
unset($this->_session->formErrors);
}
return $errors;
}
// override addMessage and getMessages
protected function addMessage($message) {
if(!isset($this->_session->messages)) {
$this->_session->messages = array();
}
$this->_session->messages[] = $message;
}
public function getMessages() {
if(isset($this->_session->messages)) {
$this->_messages = $this->_session->messages;
unset($this->_session->messages);
}
return $this->_messages;
}
…
public function addEmailAddress($emailAddress) {
...
// I call this if db insert was successful:
$this->addMessage("Thank you. You have been successfully added to the mailing list.")
}
}
I now don't need to pass any params to the view helper as it can query it's state from the Model directly. $this->view->messenger is just another view helper that converts an array to an unordered list.
View Helper:
<?php
class Zend_View_Helper_MailingList extends Zend_View_Helper_Abstract {
private $_mailingList;
public function MailingList() {
$this->_mailingList = new Model_MailingList();
return $this;
}
public function getForm() {
$request = Zend_Controller_Front::getInstance()->getRequest();
$currentPage = '/' . $request->getModuleName() . '/' . $request->getControllerName() . '/' . $request->getActionName();
$form = new Form_MailingList();
$form->setAction('/mailing-list/insert');
$form->setCurrentPage($currentPage);
$form->setErrors($this->_mailingList->getFormErrors());
$html = '<div class="mailingList"><h2>Join Our Mailing List</h2>' . $form;
$html .= $this->view->messenger($this->_mailingList->getMessages());
$html .= '</div>';
return $html;
}
}
Then in the Form_MailingList class I just need to add an additional method to repopulate the error messages. Although getMessages() is a method of Zend_Form there doesn't appear to be any corresponding setMessages(). You can do this on a Zend_Form_Element however, so I've added the following function to the Form_MailingList class:
Form:
<?php
class Form_MailingList extends Thinkjam_Form_Abstract {
...
public function setErrors(array $errors) {
foreach($errors as $key => $value) {
$this->getElement($key)->setErrors($value);
}
}
}
I can now add a signup form on any page of my site using the MailingList view helper:
<?=$this->MailingList()->getForm();?>
I realise a lot of the problems I was facing was down to a very specific set of circumstances, but hopefully this can help some other people out in some way!
Cheers,
Alex

Categories