New to CodeIgniter and new to Ocular so bear with me.
I used to code in the following way when running form validation (where the index() method contains the form loading code):
if ($this->form_validation->run() == FALSE)
{
$this->index();
}
else
{
$this->load->view('view_name', $data);
}
However I'm now trying to use the Ocular Template Library and the above code no longer works see example below:
if ($this->form_validation->run() == FALSE)
{
$this->index();
}
else
{
$this->template->set($data);
$this->template->render();
}
The above code does not run through the index method as it used to without Ocular and I was wondering what the best practice is to correct this or even if my previous code was best practice?
Regards,
Numan1617
Sometimes it's hard to determine best practice with Codeigniter because it's convention-less nature, so all I can really tell you is how I've found best in my experience...
I assume you're form view is being served up via index() and then you're submitting your form to this (seperate) controller function that validates and processes the form data, and re-displays the view with errors if there was a problem...
I would clean this up by consolidating it all into a single function...
public function form()
{
//if this is a post request
if ($_POST)
{
//check the validation
if ($this->form_validation->run())
{
//process it
//and then redirect if you want to send to a "success" page
redirect('uri/to/success');
}
else
{
//load up $data values to re-display form
}
}
//load up any $data values needed for standard view
$this->load->view('view', $data);
// or $this->template stuff...
}
It always seemed to me to be a bad route to go down calling controller functions internally, ex. $this->index()
Related
I'm relatively new to CodeIgniter and this may be an obvious answer, but I am unable to find anything in the documentation.
I know how to use form_validation->run() to validate a form and run some code when it is submitted successfully. What if I wanted to do something with a form that did not require any sort of validation? Is there a form->run() equivalent that returns true when a user submits the form? Something like:
page.php
public function update($id)
{
$this->load->helper('form');
if($this->form->run())
{
// do some stuff after user has submitted form
}
}
I don't think there exists such method, but you can do it manually
For example:
if ( ! isset($_POST['something'])) // OR $this->input->post('something');
{
return false //maybe?
}
else
{
//$something = $_POST['something'];
return true //maybe?
}
A statement like this should work. You just need to check if any post data exists.
if ($_POST)
{
// do something
}
Well I have set validationErrors for login in my UsersController:
public function login() {
if ($this->request->is('post')) {
$this->User->set($this->request->data);
if ($this->User->validates() && $this->Auth->login()) {
$this->set('ui', $this->Auth->user('id'));
$this->Session->setFlash(__('Loged in!'), 'flash_success');
$this->redirect($this->Auth->redirect());
} else {
$errors = $this->User->validationErrors;
}
}
}
Now how can I use $error in my view or as an element to be listed above my form?
Plz help I have searched a lot, but the answers were for old CakePHP, and I am using CakePHP 2.3.8.
Validation errors are available in the view automatically
There is no action required to get validation errors in the view, as they are a property of the view class. They can be inspected simply with:
debug($this->validationErrors);
In the view.
But you probably don't need to access them
Note however that it's not normal to need to look at this property directly. Using the form helper errors are displayed automatically, or you can generate errors individually
if ($this->Form->isFieldError('email')) {
echo $this->Form->error('email');
}
How do I get the CodeIgniter form validation to validate the $_SESSION if there is no passed form data? I tried manually setting the $_REQUEST variable, but it doesn't seem to work.
i.e. I have a function search in the controller which validates the form input passed, and either returns you to the previous page with errors, or else moves you onto the next page. But I want this function to also work if you previously filled out this page, and the info is stored in the $_SESSION variable.
function search () {
$this->load->library("form_validation");
$this->form_validation->set_rules("flightID", "Flight Time", "required|callback_validFlightID");
$this->form_validation->set_rules("time", "Flight Time", "required|callback_validFlightTime");
$this->setRequest(array("flightID", "time"));
// adding session check allows for inter-view navigation
if ($this->form_validation->run()) {
// some application logic here
$this->load->view("seats", $data);
} else {
$this->logger->log($_REQUEST, "request");
// redirect back to index
$this->index();
}
}
function setRequest () {
// make sure none of the parameters are set in the request
foreach ($vars as $k) {
if (isset($_REQUEST[$k])) {
return;
}
}
foreach ($vars as $k) {
if (isset($_SESSION[$k])) {
$_REQUEST[$k] = $_SESSION[$k];
}
}
}
You can store the form post info in a session using the following codeigniter functions
$formdata = array(
'flightID' => $this->input->post('flightID'),
'time' => $this->input->post('time')
);
$this->session->set_userdata($formdata);
and the information can be retrieved with the following
$this->session->userdata('flightID')
$this->session->userdata('time')
form_validation works directly with $_POST, so use that instead of $_REQUEST.
What you're trying to do is setting Post values manually which is not natively
supported by CodeIgniter. So what we're doing first is extending the core.
Create a new file (MY_Input.php) and paste the following contents into it:
class MY_Input extends CI_Input{
function set_post($key, $value)
{
$_POST[$key] = $value;
}
}
That's a very basic implementation of your purpose but it's enough to test around. You might want to extend it to make it fit your needs (f.e. allowing the input of arrays).
So now. In your controller you can check if something has been posted by a user. If not you'll be just setting the post variable manually with your new method of the Input class.
class Some_Controller extends CI_Controller{
public function index()
{
// The user hasn't filled out a field?
if(!$this->input->post('a_key'))
{
// Let's set the postfield to the value of a session key
$this->input->set_post('a_key', $this->session->userdata('mystoredkey'));
}
}
}
After having set your postfield manually, it can be handled by the form validation library as it is meant to be.
That should be your way to go :)
You can really do some pretty things if you're not afraid of hacking the core. Many people are, don't be one of them!
Happy coding
I'm developing a basic web app using the MVC architecture. I'm building my own to try and fully understand how the MVC arch works so this doubles as a learning exercise.
I am using the Aura Router classes to map my URLs to controllers and action so that something like mysite.com/login will be mapped to LoginController and if I submit a form to mysite.com/login/login it will map it to LoginController->doAction('login').
Example of a controller looks like this:
class LoginController implements iController {
public function doAction( PDO $dbh, $action ) {
switch( $action ) {
case 'login':
//login here
$user = new User();
$user_id = FALSE;
if( $user_id = $user->login( $_POST['email'], $_POST['password'] ) ) {
//save user id to session
}
else {
$results = array( 'errors' => array( 'invalid' ) );
MembershipFunc::redirect( '/login', $results );
}
break;
case 'logout':
//logout
break;
default:
break;
}
}
}
The problem I am facing is that to avoid people from refreshing the page and resubmitting the data I like to forward the user back to the login page if the login failed. Currently if I don't forward them then they would appear on the page mysite.com/login/login and I don't think it's particularly clean. It's okay for my simple login form because an error could redirect to mysite.com/login?error=email,password&email=user#domain.com but in the case of a huge form then I would get a huge URL query which is really gross.
I have scoured for good resources with basic (yet useful) PHP code examples on how MVC works and I've struggled to find anything particularly useful. Should I avoid mapping my URL structures to an action and instead opt for putting a POST field called "action" instead? Should I somehow build a system where I pass this data through an array in the SESSION data?
Any advice?
Note: I would just like to specify that I know this question doesn't fit in the SO culture because it might not have a right/wrong answer but I find SO always has the best/most knowledgeable user-base. If anyone has tips as to where I should direct these questions I would really appreciate it so I don't add clutter!
In my MVC URLs look like:
index.php?c=Controller&m=ControllerMethod&d=slash/sepparated/list/of/stuff
Data (d=) is exploded on the slashes and passed as an array to every controller method.
Autoloading (via spl_autoload_register()) is used to call the class (c=) and then the method in that class (m=).
Also, it sounds like you're either not setting the ACTION on your form or you're deliberately setting the ACTION to GET. As a general rule, ACTION should be POST to keep the URLs sane. Except search forms. Those can be GET with various advantages.
Re-directing to prevent a resubmitted form on refresh is your best option (probably only option). But in my MVC index.php?c=user&m=login handles both the login page and the login action.
Example
class login extends Controller {
public function login($data) {
if(empty($_POST)) {
$this->view = "login.tpl";
return TRUE;
}
$res = $this->model->auth();
if($res !== TRUE) {
$_POST = NULL;
$this->errorState = 1;
$this->errorMsg = "Invalid login details";
$this->login();
return FALSE;
}
Core::setMessage('success', 'user', 'login', '2', 'Logged in successfully');
$home = new home(); //whatever the main controller is
$home->index($data);
//alternatively you can redirect
header("Location: index.php?c=home&m=index);
return TRUE;
}
}
Does this make sense or have I completely missed the mark?
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