I've created an Employer model in my Laravel 4 application and, in Employer.php I have created the following function to validate user input before saving it to the database:
public static function validate($input)
{
$validator = Validator::make($input, static::$rules);
if ($validator->fails() {
return $validator;
}
return true;
}
This works fine when I'm creating a new record in the database, because I am passing in values for all the rules where I have specified a particular field is required.
However, there are certain fields in the database I don't want the user to edit after they have been created (for example, business_name). On the controller's edit method I create a form and omit those fields from the form. But validation fails because business_name is required by the $rules.
As a temporary work around, I tried just creating a hidden field in the edit form and populating it with the business_name. However, this is also required to be unique and fails when I PATCH my form to the update method!
Any advice? Is there any way I can specify which validation rules should be applied depending on the method calling it? Or should I create a new method in Employer.php specifically to validate on the update method?
You could use the required_without validation rule. Since an newly instantiated model doesn't have an id field yet, you can require some fields only when id is not present. This should work:
public static $rules = array(
'business_name' => 'required_without:id'
);
http://laravel.com/docs/validation#rule-required-without
Related
I'm working on a user-generated content blog that allows a user to go through the whole upload process before being prompted to sign up. Basic flow: fill out form to pick username/basic info->upload blog post->prompt to sign up with email/password. The purpose of reversing the normal flow is to increase the UX and conversion rate and avoid a wall in the beginning.
Instead of migrating, I've just created the tables manually in PHPmyAdmin. I have 3 relational models: Usermeta->hasOne(App\Mopdels\Post), Post->belongsTo(App\Models\Usermeta), and User->belongsTo(App\Models\Usermeta).
What I'm having trouble with is once the user has created a username and submits the first form to the usermeta table, and then submits the second form to upload their blog post to the post table, it doesn't seem to be attaching the usermeta.id to posts.usermeta_id linking them together. I must be missing something or not attaching it correctly. Here's my StoryController:
<?php
namespace App\Controllers\Story;
use App\Models\Post;
use App\Models\User;
use App\Models\Usermeta;
use App\Controllers\Controller;
use Respect\Validation\Validator as v;
class StoryUploadController extends Controller
{
public function guidance($request, $response)
{
return $this->view->render($response, 'storyupload/guidance.twig');
}
//set up our the Upload Story class so the user can upload their story
//render the view 'uploadstory.twig'
public function getStoryUpload($request, $response)
{
return $this->view->render($response, 'storyupload/upload.twig');
}
// This method is called when the user submits the final form
public function postStoryUpload($request, $response, $id)
{
//set up our validation rules for our complete sign up form
$validation = $this->validator->validate($request, [
'title' => v::stringType()->notEmpty()->length(1, 80),
'body' => v::stringType()->notEmpty()->length(1, 2500),
]);
//if validation fails, stay on story upload page
if ($validation->failed()) {
return $response->withRedirect($this->router>pathFor('storyupload.upload'));
}
$user = Usermeta::find($id)->first();
//We can use our Post Model to send the form data to the database
$post = Post::create([
'title' => $request->getParam('title'),
'body' => $request->getParam('body'),
'category' => $request->getParam('category'),
'files' => $request->getParam('img_path'),
'usermeta_id' => usermeta()->attach($user->id),
]);
//after submit, redirect to completesignup page
return $response->withRedirect($this->router->pathFor('auth.completesignup'));
}
}
I continue to get the error 'usermeta_id cannot be null' so it's definitely not pulling the id from the usermeta table correctly.
I've used the create() method to send the usermeta data to the table in my Auth controller.
Would it be better to have all of my form submissions in the Auth controller and what is the proper way using my example to make sure that my posts.usermeta_id is linked to my usermeta.id?
The usermeta form is taken care of by my Auth Controller:
//render the view 'signup.twig'
public function getSignUp($request, $response)
{
return $this->view->render($response, 'auth/signup.twig');
}
// This method is called when the user submits the form
public function postSignUp($request, $response)
{
$validation = $this->validator->validate($request, [
'name' => v::notEmpty()->alpha(),
'username' => v::noWhitespace()->notEmpty()->UsernameAvailable(),
'city' => v::notEmpty()->alpha(),
'country' => v::notEmpty()->alpha(),
]);
//if validation fails, stay on signup page
if ($validation->failed()) {
return $response->withRedirect($this->router->pathFor('auth.signup'));
}
$usermeta = Usermeta::create([
'name' => $request->getParam('name'),
'username' => $request->getParam('username'),
'city' => $request->getParam('city'),
'country' => $request->getParam('country'),
'share_location' => $request->getParam('share_location'),
]);
//after submit, redirect to storyupload/guidance
return $response->withRedirect($this->router>pathFor('storyupload.guidance'));
}
I wrote quite a bit here. To jump directly to what I believe will solve your problem, see the "Your Issue" section. The rest is here as an educational exercise.
A Quick Intro to Laravel Relations
As you probably already know, "relations" in Laravel are virtual concepts that are derived from the hard data in the database. Because they are virtual, there is some overlap in the definition of relations.
When you say "Usermeta has one Post" - what this means is that the posts table will have a usermeta_id field.
When you say "Post belongs to Usermeta" - what this means is that the posts table will have a usermeta_id field.
Notice that these two relations map to the exact same field in the exact same table. Declaring one relation will declare the other by simple congruence. "Usermeta has one Post" and "Post belongs to Usermeta" are identical relations.
A Tweak to Your Relations
There's one other relation that share this same schema (the posts table have a usermeta_id field). That is "Usermeta has many Posts". The difference here is not in how the relations are stored to the database, but in how Laravel interprets the relations and in what queries Laravel will run.
When you say "Usermeta has one Post", Laravel will scan the database for the first Post with a matching usermeta_id and return that as an instance of the Usermeta model.
When you say "Usermeta has many Posts", Laravel will scan the database for all matching usermeta_ids and return them as a Collection of Usermeta models. You likely want this second behavior -- otherwise users won't be able to make a second post after they sign up.
Setting the usermeta_id Field
Laravel allows you to set database fields directly through a relationship. See their documentation on inserting related models for details.
Because many relationships are just ciphers for the same underlying schema, there's no need to insert or update a related model both ways. For instance, suppose we had the following two models:
class User extends Eloquent {
public function posts() {
return $this->hasMany("App\Post");
}
}
class Post extends Eloquent {
public function user() {
return $this->belongsTo("App\User");
}
}
In this case, the following two lines of code are identical and you only need to use one of them:
$post->user()->associate($user);
$user->posts()->save($post);
Both of these will have the same effect (setting the user_id field on the posts table)
The reason I mention this is that it looks like you're trying to double-dip in your code. You're using attach() (conceivably to set the usermeta_id) and you're also setting the usermeta_id directly. I've added a side-note on the attach method below - as I don't believe it's the right method, anyway.
To use Laravel's relations, you would want code like the following to set this field:
public function postStoryUpload($request, $response, $id)
{
//set up our validation rules for our complete sign up form
$validation = $this->validator->validate($request, [
'title' => v::stringType()->notEmpty()->length(1, 80),
'body' => v::stringType()->notEmpty()->length(1, 2500),
]);
//if validation fails, stay on story upload page
if ($validation->failed()) {
return $response->withRedirect($this->router>pathFor('storyupload.upload'));
}
$user = Usermeta::find($id)->first();
//We can use our Post Model to send the form data to the database
$post = Post::create([
'title' => $request->getParam('title'),
'body' => $request->getParam('body'),
'category' => $request->getParam('category'),
'files' => $request->getParam('img_path'),
]);
// Set the usermeta_id field
$post->usermeta()->associate($user);
// Save the model so we write changes to the database
$post->save();
//after submit, redirect to completesignup page
return $response->withRedirect($this->router->pathFor('auth.completesignup'));
}
Manually Setting the usermeta_id Field
Instead of using Laravel's relations to set this field, you can set the field manually. This can sometimes be cleaner, but it's less explicit and can lead to minor bugs if you aren't careful. To do this, you need to treat the usermeta_id field like any other field on your model.
$post->usermeta_id = $user->id;
This also works when mass assigning attributes using fill or create like so:
$post = \App\Post::create([
'title' => $title,
'body' => $body,
'usermeta_id' => $user->id
]);
$post->fill([
'title' => $title,
'body' => $body,
'usermeta_id' => $user->id
]);
Note that when manually setting the usermeta_id like this, you do not need to use any relationship methods. The following code is redundant:
$post->usermeta_id = $user->id;
$post->usermeta()->associate($user);
Your Issue (I Believe)
There's a caveat to mass assignment, however. Per the Laravel documentation, mass assignment requires you to fill out the model's fillable or guarded attributes.
This is one of the most common bugs, if not the most common bug, in any Laravel code - and it doesn't throw an obvious error so it's easy to miss. Consider the following model:
class Post extends Eloquent {
private $fillable = ["title", "body"];
}
If you attempt to mass assign the usermeta_id field like so:
$post = \App\Post::create([
'title' => $title,
'body' => $body,
'usermeta_id' => $user->id
]);
Then it will silently fail. No error is thrown and the Post is created but the usermeta_id field will be NULL - because it's not mass assignable. This is fixed by updating your model like so:
class Post extends Eloquent {
private $fillable = ["title", "body", "usermeta_id"];
}
I will repeat again, as I did above, that if using mass assignment like this you do not not need to use the associate or save relationship methods. This would be redundant. Therefore you can just set usermeta_id directly to $user->id without any of the usermeta()->associate() shenanigans.
The Bugs I Mentioned
I mentioned that manually setting the field like this can cause bugs. So let's actually discuss what some of those bugs are now instead of glossing over them.
If you update the relationship field manually, Laravel will be unaware that the two models are related until it reloads the model from the database. Consider the following two chunks of code:
$post = new Post();
$post->usermeta_id = $user->id;
dd( $post->usermeta->name );
$post = new Post();
$post->usermeta()->associate($user);
dd( $post->usermeta->name );
The first code block will fail, throwing the error "cannot read attribute of null object" -- because as far as Laravel is aware, $post->usermeta is NULL. You set $post->usermeta_id, but you didn't set $post->usermeta.
The second code block will work as expected, because by running the associate function it sets both usermeta_id and usermeta.
95% of the time this doesn't really cause any issues, however. If you're using an asynchronous API call to save the post and then a separate asynchronous API call to read the post at a later time, then Laravel will read the post from the database and properly set up the relation automatically when we sees the usermeta_id field is filled out.
Side-note On the attach() Method
Laravel uses different methods for saving different types of relations - because the different relations imply different underlying database fields.
associate: This sets the *_id field on the current model's table. For instance: $post->user()->associate($user) will set the user_id on the posts table
save: This sets the *_id field on the other model's table. For instance: $post->comments()->save($comment) will set the post_id on the comments table
attach: This sets both *_id fields on a linking table for many to many relationships. For instance, if you had a tag system then $post->tags()->attach($tag) would set post_id and tag_id on the post_tags table
It can be a bit tricky to remember which of these three functions you need. In general, there's a direct mapping from relation to function:
hasOne, hasMany --> save
belongsTo --> associate
belongsToMany --> attach
I have a Course Model that have many fields like this :
course_id
title
description
creator
start_date
end_date
reg_start_date
reg_end_date
picture
lesson_count
cost
status
active
teacher
created_at
updated_at
deleted_at
And I have a Form to edit a specified Model. action attribute of the edit form tag is referenced to course.update route.
In the edit Form,in addition to fields with same names of above Model properties, there are many other form fields that not related to Course Model (and used for manyTomany relations or other operations)
Now in public update method , when I want to use Eloquent update() method , Since the number of irrelevant field names are many, I must to use except() method for incoming request. like this :
public
function update (StoreCourseRequest $request, $id)
{
$data = $request->except(['search_node', '_token', 'start_date_picker', 'end_date_picker', 'reg_start_date_picker', 'reg_end_date_picker', 'orgLevels', 'courseCats','allLessonsTable_length']);
$course = Course::findOrFail($id);
$course->update($data);
$course->org_levels()->sync($request->get('orgLevels'));
$course->course_categories()->sync($request->get('courseCats'));
$result = ['success' => true];
return $result;
}
As you see on usage of $request->except() method, I passed many field names to it to filter only proper attributes for use in $course->update($data);.
Now my Question is that Are there any way that we can get only same name model attributes from a field name?
If I understand your question correctly you are trying to avoid having to use the except() method for incoming requests, correct?
If that is the case, you can just skip it altogether and pass the entire request to the update() method as it will only update matching fields (provided they are listed as "fillable" in the method class). This process is called "mass-assignment".
Just getting to grips with Laravel 4.2 and eloquent. I've been watching the Laravel from Scratch casts on laracasts.com, particularly the lessons on validation and the follow up refactoring. The examples used throughout those lessons deal with a relatively basic user model whereby there are only 2 fields, username and password. My user model contains many more fields and my registration form asks for the user to re-enter/confirm the password they have entered.
It seems to be recommended that the process of validating user input should be done within the model, which makes total sense. So just like that tutorial I have gone ahead and added an isValid method to my model to validate user input on my registration form. I fill my user model based on the input like this:
$input = Input::all();
if (!$this->user->fill($input)->isValid()) {
return Redirect::back()->withInput()->withErrors($this->user->errors);
}
So I've written my rules and got the validation working and I am now ready to save the user's input to the database. However, since I've filled my model with the entire user input, the user model instance now contains an attribute of confirm_password and calling $user->save(); gives me an error (Since I don't have this field in my database table). In addition, since I have just passed in the user input to validate, the password there is not hashed either.
What would be the best approach to take with regards to validating user input VS having the model actually represent the database table? I know there are ways I could get around all this by doing things like moving the validation outside the model and perhaps just let the model store the validations rules etc. but I can looking for advice on the best practice.
Thanks
You may remove it before saving, for example:
$input = Input::all();
if (!$this->user->fill($input)->isValid()) {
return Redirect::back()->withInput()->withErrors($this->user->errors);
}
else {
unset($this->user->attributes['confirm_password']);
$this->user->save();
}
This may work but not the proper way for doing it. You may also use a saving event like:
// Goes in to your model
protected static function boot()
{
parent::boot();
static::saving(function($model) {
unset($model->attributes['confirm_password']);
});
}
Since you are validationg inside your model then you may trigger the validation on saving event, like:
protected static function boot()
{
parent::boot();
static::saving(function($model) {
if($model->isValid()) {
unset($model->attributes['confirm_password']);
return true;
}
return false;
});
}
There are nicer ways to accomplish this same thing.
Restrict your Input values. You can pass Input::all() to your validator and still do this.
$input = Input::only('username', 'password');
// – OR –
$input = Input::except('confirm_password');
Add $fillable to your User model.
class User extends Eloquent {
protected $fillable = array('id', 'name', 'email', 'password');
}
Then you can populate the database from the Input and only the columns in the fillable array will be populated. Make sure you have good validation rules if you try this.
$user = User::create(Input::all());
This will accomplish what you are trying to do without unsetting Input values or adding Model events.
PROBLEM 1:
When I try to save() any Yii Model, it updates all fields in the row.
The problem is: When I try to save model users, even if has no PASSWORD to update, it get the database value(already hashed) and hash again.
How can I do to YII only update fields that I want?
Code:
$user = Users::model()->findByAttributes(array('username'=>$this->username));
$user->ip = $_SERVER['REMOTE_ADDR'];
$user->save();
Users.php (Model):
public function beforeSave() {
if (!empty($this->password))
$this->password=$this->hashPassword($this->password);
return true;
}
PROBLEM 2:
I have an API that can create USERS.
API Tutorial: http://www.yiiframework.com/wiki/175/how-to-create-a-rest-api/
When I have crypter_password in the database, instead password, I got the error: Parameter password is not allowed for model Users, because the API validate parameters using $model->hasAttribute().
How can I fix the API actionCreate to allow custom parameters?
According to Yii's doc: http://www.yiiframework.com/doc/api/1.1/CActiveRecord#save-detail
public boolean save(boolean $runValidation=true, array $attributes=NULL)
$attributes - array - list of attributes that need to be saved. Defaults to null, meaning all attributes that are loaded from DB will be saved.
You can pass in an array of fields that you want to save.
Eventhough the other answers listed here are not wrong, they are definitely not really developer friendly and it's extremely easy to forget to add the attributes to the save line.
Here is a developer friendly way of working.
In your model, add the following attribute:
private $_aAttributesBackup;
In this variable, we will store an exact copy of the current model. To do this, the following afterFind method needs to be added:
public function afterFind()
{
$this->_aAttributesBackup = $this->attributes;
}
Almost there. At this point, the model will store all of his attributes in the attributesBackup field which makes it easier to compare. To make it easier, we also need a method that will check if the specified attribute has a backup value. We do this by adding the following code into our model:
public function getOriginalAttribute($sAttribute)
{
if ($this->_aAttributesBackup)
{
return $this->_aAttributesBackup[$sAttribute];
}
return NULL;
}
Now, how about checking if the password has been changed? Simple, by adding the following beforeSave code:
public function beforeSave()
{
if ($this->getOriginalAttribute('password') != $this->password)
{
$this->password = sha1($this->password);
}
return parent::beforeSave();
}
Et voila. Now everytime you execute the code $Model->save(); the system will check if the password has been changed, If the password is changed, it will hash it again, if it is not changed, it won't be hashed again.
Save () inserts a row into the database table if its isNewRecord property is true. Otherwise, it will update the corresponding row in the table (usually the case if the record is obtained using one of those 'find' methods.)
What you have to do is update specific field so you can use SaveAttributes and it accepts the array of string values that have been updated for example demo code is as follow
$user = Users::model()->findByAttributes(array('username'=>$this->username));
$user->ip = $_SERVER['REMOTE_ADDR'];
$user->SaveAttributes(array('ip'));
I am developing a registration form in YII. In my form there is a radio option to choose register as Mode1 or register as Mode2. If user chooses mode1, data's should be entered to table1 or it should entered to table2.
In YII each model deals with one table. Here my form deals with two tables.
So how to handle such a form to validate and enter data's to table in YII?
The easiest way is to create one model for the form (assuming they have the same fields?)
This class would extend CFormModel (in the example below I refer to this model as GlobalFormModel)
This model would have the same attributes as the other two models, as well as one new attribute called mode
When the form is submitted, in the controller you can handle it based on which mode and validate it against the correct model, eg:
$model = new GlobalFormModel
if(isset($_POST['GlobalFormModel'])){
$model->attributes = $_POST['GlobalFormModel'];
if ($model->mode == 1){
$newmodel = new FormOne;
$newmodel->attributes = $model->attributes;
} else {
$newmodel = new FormTwo;
$newmodel->attributes = $model->attributes;
}
... // validate and save $newmodel
}
$this->render("yourview",array("model"=>$model));
Where FormOne is the model associated with the first table, and FormTwo is associated with the second table. First you create a new instance of the GlobalFormModel (which is passed to the view). You check if the form has been submitted (you could validate it here or after loading one of the two models, that is your choice). You check the mode, and then load the correct model.