PHP OO: Best strategy for dealing with form posts? - php

We have built custom forms and at this stage are built very well with a lot of thought. Each class has its own responsibility and so far the basic principle of OOP concepts have been used i.e. inheritance, polymorphism, encapsulation, etc. Each element of the form is an object and all objects are collected and initiated on the fly.
Now weve got to the processing part of the forms we at a loss for a strategy to deal with this and would like to ask if anyone has any pointers please?
Is there an alternative, say for instance the creation of a class that could be responsible for checking if the form has been submit and methods to gather post data, validate, etc or do people still do the old way like a quick conditional in the client to check if form submit:
if(isset($_POST["var"])
{
//process form
} else {
//show form
}
And would it be best to use a separate action page to process?
Bascially what I dont want to do is have to write some awful code thats not reusable or make use of OOP concepts. We would like to achieve something WITHOUT USING ANY FRAMEWORKS.

I would try to go with structure like this :
// public function __construct( ValidatorInterface $validator )
$form = new Form( new Validator );
// public function add_field( $name , array $rules = array() )
$form->add_field(
'name',
// that would be $_POST['name'] , and in template file <?php echo $name ?>
array(
'required' => 'Full Name is required'
// for validator to execute is_required()
));
$form->add_field(
'email' ,
array(
'required' => 'Email Address is required',
'email' => 'A valid email address is required'
// for validator to execute is_email()
));
$form->add_field( 'country' );
//name of submit button
$for->add_submitter( 'submit' );
// etc
$page = new Template();
$page->use_file( 'contact.php' );
if( $form->is_submitted() )
{
// gathers all the $_POST's from registered fields
$form->collect();
if ($form->is_valid() )
{
$page->use_file( 'done.html' );
// do some stuff
}
else
{
// public function populate( TemplateInterface $template )
// assigns field values and error messages to the template
$form->populate( $page );
}
}
echo $page->render();
And the Template class based upon this code : http://codeangel.org/articles/simple-php-template-engine.html
update
Implementation for method that registers new fields
public function add_field( $name , array $rules = array() )
{
if ( !array_key_exists( $name , $this->_fields ))
{
$this->_fields[ $name ] = array();
}
$this->_fields[ $name ]['rules'] = $rules;
}
Implementation for method that finds values of all the registered fields
public function collect()
{
$field_names = array_keys( $this->_fields );
foreach ( $field_names as $name )
{
$this->_fields[ $name ]['value'] = $this->_collect_value( $name );
}
}
protected function _collect_value($name)
{
$value = null;
if ( isset( $_POST[ $name ] ) )
{
$value = $_POST[$name];
}
$value = trim( $value );
if ( empty( $value ) ){
$value = null;
}
return $value;
}
The data collection is pretty simple process.
And on the is_valid() method call if get the Validator instance from local variable and performs actions on each registered form field ( mostly with helpfule php filter_var() function.

You're all missing very important concept. You don't want to show form, you want to ask user for some input. You actually want something like this:
$rules = array(
"name" => array("not_empty"),
"email" => array("not_empty", "email")
);
$callback = array($this, "handle_valid_post_data");
$ui->request_input($rules, $callback);
Sadly, no framework does this so you have to implement it yourself.

Related

CodeIgniter 4 - Validation Custom Rule Function Quandry

In my CI4 learning, I have started by trying to simulate user sign in functionality. I have a Controller, two Views (not shown here, but really simply pages- one a pretty much just single form, and the other one a “blank” success HTML page), a set of custom rules in the Validation.php file, and a CustomRule.php file with the first of the methods that will implement all my custom rules (which, ultimately, I’d like to have all set in the Validation.php file). For lack of a better idea, I’ve stuck the CustomRules.php file in the app\Config\ folder.
Here is my problem:
For the life of me, I can’t figure out how to get the Validation service to pass additional parameters (from the form) to my custom rules function called ‘user_validated’. The CI4 documentation describes what the custom function needs to cater for when accepting additional parameters, but not how to trigger the Validation service to pass these additional parameters to one’s custom function… so although ‘user_validated’ is called, only ‘user_email_offered’ is ever passed as in as a string- nothing else goes in, from what I can tell. How do I get around this?
I have tried inserting < $validation->setRuleGroup('user_signin'); > before the call to validate, but found that I could move the setting of the rule group into the call to validate, using: $validationResult = $this->validate('user_signin'), which seemed to do the same, and which doesn't seem to work without the rule-group as a parameter (?). This still doesn't seem to be what triggers the additional data to be passed to the custom rule's method.
Extracts from my hack are appended below.
I’d be very grateful one of you knowledgeable folk could please point me in the right direction.
In app\Controllers\SignupTest.php:
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
class SignupTest extends BaseController
{
public function index() { // redirection from the default to signup(), signin(), ...
return $this->signup();
}
public function signup() {
helper(['form']);
$validation = \Config\Services::validation();
if ($this->request->getPost()) { // still TBD: any different to using $this->request->getGetPost() ?
$validationResult = $this->validate('user_signin'); // set the rules to use: 'user_signin', 'user_signup'
if (!$validationResult) {
$validationErrors = $validation->getErrors();
return view('SignupTestView', $validationErrors); // redisplay simple html form view with list of validation errors
} else {
return view('SignupTestViewSuccess'); // display view to show success
}
} else {
return view('SignupTestView'); // initial display, in the event of there being no POST data
}
}
}
In \app\Config\CustomRules.php:
<?php
namespace Config;
use App\Models\UserModel;
//--------------------------------------------------------------------
// Custom Rule Functions
//--------------------------------------------------------------------
class CustomRules
{
public function user_validated(string $str, string $fields = NULL, array $data = NULL, string &$error = NULL) : bool{
$user_email_offered = $str;
$user_password_offered = ''; // to be extracted using $fields = explode(',', $fields), but $fields is never provided in the call to this user_validated method
if (($user_email_offered !== NULL) && ($user_password_offered !== NULL)) {
$usermodel = new UserModel(); // intended to create a UserEntity to permit connectivity to the database
$user_found = $usermodel->find($user_email_offered); // we're going to assume that user_email is unique (which is a rule configured in the database table)
if ($user_found === NULL) { // check if user exists before doing the more involved checks in the else-if section below, which may throw exceptions if there's nothing to compare (?)
...
}
}
In \app\Config\Validation.php:
?php
namespace Config;
class Validation
{
//--------------------------------------------------------------------
// Setup
//--------------------------------------------------------------------
/**
* Stores the classes that contain the
* rules that are available.
*
* #var array
*/
public $ruleSets = [
\CodeIgniter\Validation\Rules::class,
\CodeIgniter\Validation\FormatRules::class,
\CodeIgniter\Validation\FileRules::class,
\CodeIgniter\Validation\CreditCardRules::class,
\Config\CustomRules::class,
];
/**
* Specifies the views that are used to display the
* errors.
*
* #var array
*/
public $templates = [
'list' => 'CodeIgniter\Validation\Views\list',
'single' => 'CodeIgniter\Validation\Views\single',
];
//--------------------------------------------------------------------
// Custom Rules
//--------------------------------------------------------------------
/* configurable limits for validation rules array below*/
const user_email_min_lenth = 9;
const user_email_max_lenth = 50;
const user_password_min_lenth = 6;
const user_password_max_lenth = 25;
public $user_signin = [
'user_email' => [
'label' => 'e-mail address',
'rules' => 'trim|required|valid_email|user_validated', // user_validated is custom rule, that will have a custom error message
'errors' => [
'required' => 'You must provide an {field}',
'valid_email' => 'Please enter a valid {field}',
]
],
'user_password' => [
'label' => 'password',
'rules' => 'trim|required',
'errors' => [
'required' => 'Enter a {field} to sign in',
'user_password_check' => 'No such user/{field} combination found',
]
Calling custom rule with parameters should be exactly the same as calling CI4's regular rules. Let's get for example "required_without". You use it like in this example:
$validation->setRule('username', 'Username', 'required_without[id,email]');
And the function is declared as so:
public function required_without($str = null, string $fields, array $data): bool
{
$fields = explode(',', $fields);
//...
}
where $str - this is your main field, $fields - string, packing a comma-separated array.
As for Grouping rules, you do not need to group rules to be able to use custom rules with parameters.
If you have only 2 fields to test against you can go a bit cheaper, which will not be perfect but still works:
Function:
public function myrule(string $mainfield, string $fieldtotestwith): bool
{
//doing stuff
}
Validating rule:
$validation->setRule('somemainfield', 'Something', 'myrule[somesecondfield]');

Grid Field not showing entries [SilverStripe]

I am using the MultiForm module to submit a long form with SilverStripe. The logic for this form is in 'CampaignBriefForm.php' whereas the gridfield CMS field is being added in 'CampaignBriefPage.php'. I have a Data Object for a CampaignBriefLead which is what the form creates.
Campaign Brief Page
private static $has_many = array(
'CampaignBriefLeads' => 'CampaignBriefLead'
);
public function CampaignBriefForm() {
return new CampaignBriefForm($this, 'CampaignBriefForm');
}
Campaign Brief Lead (DO)
private static $has_one = array( "Page" => "CampaignBriefPage" );
As you can see the Campaign Brief page has the correct relationship with the Data Object and also you can see the the form itself (done in a sepearate file) is correctly returning (as it's being saved in the DB). For some reason however, the gridfield will not show me what is in the database for that Data Object. The grid field code is as follows.
$fields = parent::getCMSFields();
$contactConfig = GridFieldConfig_RelationEditor::create();
$contactConfig->getComponentByType('GridFieldDataColumns')->setDisplayFields(
array(
'CompanyName' => 'Company Name',
'StartDate' => 'Start Date',
'Duration' => 'Duration',
'WebsiteURL' => 'Website',
'Budget' => 'Budget'
));
$contactGrid = new GridField(
'CampaignBrief',
'Campaign Enquiries',
$this->CampaignBriefLeads(),
$contactConfig
);
$fields->addFieldToTab("Root.Enquiries", $contactGrid);
To me this all looks correct and should work but for some reason it is not working.
Note
The link existing option on the gridfield allows me to link one of the entries from the DO with the gridfield weirdly?? So it saves one entry but I have to do it manually, this tells me it can see the DB but won't pull for some reason.
For reviewing reasons, here is the code for the multiform where the campaign brief lead is actually saved to the DB after the form is submitted.
public function finish($data, $form) {
parent::finish($data, $form);
$steps = DataObject::get(
'MultiFormStep',
"SessionID = {$this->session->ID}"
);
$enquiry = new CampaignBriefLead();
foreach($steps as $step) {
$data = $step->loadData();
foreach($data as $key => $value) {
if($key == 'url' || $key == 'MultiFormSessionID' || $key == 'action_finish') {
continue;
}
if(isset($data[$key])) {
$enquiry->$key = $data[$key];
error_log($data[$key]);
}
}
}
$enquiry->write();
$this->controller->redirect('/campaign-brief/');
}
If you need anything more let me know. Thanks.
I would take a guess that the CampaignBriefLead PageID is not being set on your form submission.
Check the CampaignBriefLead table in your database and check the PageID column. If it is blank, null or 0 for each row then it is not being set.
One way to fix this problem for any new submission is to set the PageID for the $enquiry:
public function finish($data, $form) {
// ...
$enquiry = new CampaignBriefLead();
if ($campaignBriefPage = CampaignBriefPage::get()->first()) {
$enquiry->PageID = $campaignBriefPage->ID;
}
// ...
}
For the existing entries you will need to update the entries to have the correct PageID.

Drupal Form API : How to pass additional data back to the form after it is been submitted

I have created a form in Drupal using its API. The theme is overridden by a template. In the template, I want to show errors where the form exists, I do not want to show the error using drupal's form_set_error() because it shows the error in a fixed default area of the page.
This is my implementation of hook_form()
function newsletter_box($form, &$form_state)
{
$form = array();
$form["newsletter-email"] = array(
"#type" => "textfield",
"#maxlength" => 32,
"#title" => "Your Email"
);
$form['send-email'] = array(
"#type" => "submit",
"#value" => t("Join Our Newsletter!")
);
return $form;
}
And this is my implementation of hook_submit()
function newsletter_box_submit($form, &$form_state)
{
if( !filter_var($form_state['values']["newsletter-email"], FILTER_VALIDATE_EMAIL) )
{
$form_state['build_info']['args'] = ['form-error' => t("Email not formatted correctly.") ];
// NOTE: here is the place where the error is set, rather I use to set a new variable, but this variable is not available in my template. Why?
}
$form_state['rebuild'] = TRUE;
drupal_form_submit("newsletter_box", $form_state);
}
Suggest adding the variable by implementing a hook_preprocess, and add the error message into form rather than form_state.
Example:
// Set in newsletter_box_submit or newsletter_box_validate
$form['#form_error'] = t("Email not formatted correctly.");
/**
* Implements hook_preprocess_HOOK().
*/
function [module_name]_preprocess_newsletter_box(&$vars) {
$form = $vars['form'];
$error_message = $form['#form_error'];
// Use $error_message in template.
}
It feels like a hack though, strongly recommend theming the form the Drupal way.

Laravel 3 Validation of a single input form

I am using spectacular Laravel Framework but i have a validation issue that i cannot solve in any way trying to validate a single email field.
My form input is:
{{ Form::email('email', Input::old('email'), array('id' => 'email', 'class' => 'span4', 'placeholder' => Lang::line('newsletter.email')->get($lang), 'novalidate' => 'novalidate')) }}
My controller
public function post_newsletter() {
$email = Input::get('email');
$v = Newsletter::validator($email, Newsletter::$rules);
if($v !== true)
{ ... }
else
{ ... }
}
and my model
class Newsletter extends Eloquent {
/**
* Newsletter validation rules
*/
public static $rules = array(
'email' => 'required|email|unique:newsletters'
);
/**
* Input validation
*/
public static function validator($attributes, $rules) {
$v = Validator::make($attributes, $rules);
return $v->fails() ? $v : true;
}
}
I ve done this so many times with success with much complicated forms but now even if i enter a valid email i get an error message about required fied etc Am I missing something? Maybe is the fact that i try to validate just one single field? I really don't get it why this happens.
I believe this might be because you're not passing in an array of attributes (or an array of data, basically).
Try using the compact function to generate an array like this.
array('email' => $email);
Here is how you should do it.
$v = Newsletter::validator(compact('email'), Newsletter::$rules);

Perform validation only on create using php-activerecord

I am creating a User Model using Codeigniter and php-activerecord and the wiki says I can use 'on' => 'create' to have a validation only run when a new record is created, like this,
static $validates_presence_of = array(
array('title', 'message' => 'cannot be blank on a book!', 'on' => 'create')
);
It also states that we have access to "save", "update" and "delete"...
None of these are working for me though and I can figure out why, here is my code,
// Validations
static $validates_presence_of = array(
array('email', 'message' => 'Please enter a valid email address.'),
array('password', 'message' => 'Password must be provided.', 'on' => 'create')
);
I want to set it up like this so that when a user updates their profile, they can leave their password blank to keep their current one.
I would appreciate any help or guidance! Thanks!
The reason for this is most likely because it's not been implemented.
Relevant classes are lib/Model.php and lib/Validations.php
From a purely abstract standpoint, you would need to track the mode of operation between save and create. To do this, I created a public property (public $validation_mode) within lib/Model.php and set that property to 'create' or 'save' in private methods Model::insert() and Model::update() respectively. These values match the 'on' property you are trying to use.
Then within lib/Validations.php, I modified the following methods:
Validations::validates_presence_of()
public function validates_presence_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['blank'], 'on' => 'save'));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$this->record->add_on_blank($options[0], $options['message'], $options);
}
}
Errors::add_on_blank()
public function add_on_blank($attribute, $msg, $options = array())
{
if (!$msg)
$msg = self::$DEFAULT_ERROR_MESSAGES['blank'];
if (($value = $this->model->$attribute) === '' || $value === null)
{
if(array_key_exists('on', $options))
{
if($options['on'] == $this->model->validation_mode)
{
$this->add($attribute, $msg);
}
} else {
$this->add($attribute, $msg);
}
}
}
What this does basically is passes ALL the $options specified in your model (including the 'on' property) down to the Errors::add_on_blank() method where it now has enough information to differentiate between 'on' => 'create' and the default ('on' => 'save'). Using the public $validation_mode property from the Model class ($this->model->validation_mode), we can determine what the current mode of operation is and whether or not we wish to continue adding the error message or skip it this time around.
Obviously you would want to document any changes you make and test thoroughly. According to the documentation, all validation methods supposedly support this "common option" along side allow_null, allow_blank but again, if it's not implemented, you will have to make it happen yourself by making these necessary changes.
should be call the validation method like this:
#example
$your_obj = new Instace();
if($your_obj->is_valid()) {
// if all is correct your logical code
}
else {
// your logical to show the error messages
}
//doesnt work if you write
if(!$your_obj->is_valid())
//the other way you must be use the next method
if($your_obj->is_invalid())
I'm find a answer for your question without edit library.
Add the before_validation callback and add in this callback a validation rule. It works for me.
static $before_validation_on_create = array('before_validation_on_create');
static $validates_presence_of = array(
array('login'),
);
public function before_validation_on_create() {
self::$validates_presence_of[] = array('password');
}

Categories