I have a question about Laravel 4 and Validation concept.
I have my mysql table and my model Calass..
We can suppose that we have a field nullable...
If this field is not null how can I validate it? (for example I would an integer value < of my maxvalue)
Unless you add the 'required' rule, validation will always treat fields as optional. Thus, a rule like numeric|max:9000 will be valid if the field is null or empty string, but if it is provided it has to be a number that is smaller than 9000.
To prevent null or empty string from being saved into the database (MySQL will convert this to 0 for an integer field), you can set up a mutator for the attribute. In your model:
public function setNumberAttribute($value)
{
if ($value !== null && is_numeric($value)) {
$this->attributes['number'] = (int) $value;
}
}
http://laravel.com/docs/eloquent#accessors-and-mutators
you can create your validation into your model
Ex:
class User extends Eloquent {
protected $table = 'users';
public $timestamps = false;
public static function validate($input) {
$rules = array(
# place-holder for validation rules
);
# validation code
}
}
for more description about validation laravel book
Related
Laravel populate non-null fields while creating without a default value.
In users migration I added $table->string('username').
What is the best way to auto-populate this?
Using boot/booted method within User model:
protected static function booted () {
if (auth()->user()) {
self::creating(function ($model) {
$model->username = self::generateUsername(self::name);
});
}
}
This doesn't work because the username column is not null and has no default value. I could add nullable or default value as something but that seems like a wrong thing to do.
Ofc, I can add on create as well
User::create($request->validated() + ['username'=>User::generateUsername($request->name)])
But I want to leave it to auto-populate.
Now I am not sure is creating method run on the start of creating or end..
public function setNameAttribute($value)
{
$this->attributes['name'] = $value;
$this->attributes['username'] = $this->generateUsername($value);
}
This solved it :D
In my Yii2 project, I have related models. Example: A model Customer would have an attribute address_id that relates to another model Address. In the Customer model have the exist validator that checks that the row exists in the address table.
Typically, on create or update, this validation is ignored if address = null. My problem is that sometimes FE would send the address = 0 indicating the absence of an address.
In this case, I need to not only ignore validation, but to set address = null. This can be done beforeSave of course, but I'm trying to check if there's some built-in way through which I can do this
You can use the filter validator for normalization of an input data. For example:
class Customer extends ActiveRecord
{
public function rules()
{
return [
['address_id', 'filter', 'filter' => function ($value) {
return $value == 0 ? null : $value;
}],
// other validators
];
}
}
After creating model, when I try to get his attributes, i get only fields in database that are filled.
----------------------------------------------
DB: | id | shopID | name | bottleID | capacity |
----------------------------------------------
| 1 | 8 | Cola | 3 | |
----------------------------------------------
In this case I need capacity attribute too, as empty string
public function getDrinkData(Request $request)
{
$drink = Drink::where('shopId', $request->session()->get('shopId'))->first();
if($drink) {
$drink = $drink->attributesToArray();
}
else {
$drink = Drink::firstOrNew(['shopId' => $request->session()->get('shopId')]);
$drink = $drink->attributesToArray(); // i want to get even empty fields
}
return view('shop.drink')->(['drink' => $drink])
}
But for later usage (in view) I need to have all attributes, including empty ones. I know that this code works as it should, but I don't know how to change it to detect all attributes.
The model attributes are populated by reading the data from the database. When using firstOrNew() and a record doesn't exist, it makes a new instance of the model object without reading from the database, so the only attributes it has will be the ones manually assigned. Additionally, since there is no record in the database yet, you can't just re-read the database to get the missing data.
In this case, you can use Schema::getColumnListing($tableName) to get an array of all the columns in the table. With that information, you can create a base array that has all the column names as keys, and null for all the values, and then merge in the values of your Drink object.
public function getDrinkData(Request $request)
{
// firstOrNew will query using attributes, so no need for two queries
$drink = Drink::firstOrNew(['shopId' => $request->session()->get('shopId')]);
// if an existing record was found
if($drink->exists) {
$drink = $drink->attributesToArray();
}
// otherwise a new model instance was instantiated
else {
// get the column names for the table
$columns = Schema::getColumnListing($drink->getTable());
// create array where column names are keys, and values are null
$columns = array_fill_keys($columns, null);
// merge the populated values into the base array
$drink = array_merge($columns, $drink->attributesToArray());
}
return view('shop.drink')->(['drink' => $drink])
}
If you were using firstOrCreate(), then a new record is created when one doesn't exist. Since there a record in the database now, you could simply re-read the record from the database to populated all of the model attributes. For example:
public function getDrinkData(Request $request)
{
$drink = Drink::firstOrCreate(['shopId' => $request->session()->get('shopId')]);
// If it was just created, refresh the model to get all the attributes.
if ($drink->wasRecentlyCreated) {
$drink = $drink->fresh();
}
return view('shop.drink')->(['drink' => $drink->attributesToArray()])
}
What if you were to explicitly declare all the fields you want back.
An example of something I do that is a bit more basic as I'm not using where clauses and just getting all from Request object. I still think this could be helpful to someone out there.
public function getDrinkData(Request $request)
{
// This will only give back the columns/attributes that have data.
// NULL values will be omitted.
//$drink = $request->all();
// However by declaring all the attributes I want I can get back
// columns even if the value is null. Additional filtering can be
// added on if you still want/need to massage the data.
$drink = $request->all([
'id',
'shopID',
'name',
'bottleID',
'capacity'
]);
//...
return view('shop.drink')->(['drink' => $drink])
}
You should add a $fillable array on your eloquent model
protected $fillable = ['name', 'email', 'password'];
make sure to put the name of all the fields you need or you can use "*" to select all.
If you already have that, you can use the ->toArray() method that will get all attributes including the empty ones.
I'm using the same thing for my Post model and it works great with all fields including empty ones.
My model looks like this:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = ["*"];
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Do you need to set the $drink variable again after checking the $drink variable?
you can check the following code?
public function getDrinkData(Request $request)
{
$drink = Drink::where('shopId', $request->session()->get('shopId'))->first();
if(!count($drink)>0) {
$drink = Drink::firstOrNew(['shopId' => $request->session()->get('shopId')]);
}
return view('shop.drink')->(['drink' => $drink]); or
return view('shop.drink',compact('drink'));
}
hope it will help. :) :)
You can hack by overriding attributesToArray method
class Drink extends Model
{
public function attributesToArray()
{
$arr = parent::attributesToArray();
if ( !array_key_exist('capacity', $arr) ) {
$arr['capacity'] = '';
}
return $arr;
}
}
or toArray method
class Drink extends Model
{
public function toArray()
{
$arr = parent::toArray();
if ( !array_key_exist('capacity', $arr) ) {
$arr['capacity'] = '';
}
return $arr;
}
}
then
$dirnk->toArray(); // here capacity will be presented even it null in db
I have a model leads_contents_interactions for the (simplified) table:
CREATE TABLE `leads_contents_interactions` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`lead_content_id` bigint(20) unsigned DEFAULT NULL,
`created_on` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=59 DEFAULT CHARSET=utf8;
I would like to select these and in addition to the id, lead_content_id, and created_on columns, I would also like it to return a column is_new where that is something like this:
SELECT
id,
lead_content_id,
created_on,
IF(created_on > DATE_SUB(NOW(),INTERVAL 1 DAY), 1, 0) AS is_new
FROM leads_contents_interactions;
Now I am aware I can do this with PHQL, but the leads_contents_interactions would ideally not be queried directly, I want this extra column to be returned when it is queried naturally like:
$leads = $user->getRelated(
'leads',
array(
'Lead.deleted_by IS NULL',
'limit'=>1000
)
);
foreach($leads as $lead) {
foreach($lead->interactions as $interaction) {
echo $interaction->id."\t".$interaction->is_new.PHP_EOL;
}
}
Model for Lead (simplified)
class Lead extends PersendlyModelAbstract {
public function initialize() {
// A lead has multiple interactions, `contents`, through the weak entity `leads_contents`
$this->hasManyToMany(
'id',
'LeadsContents',
'lead_id',
'id',
'LeadsContentsInteractions',
'lead_content_id',
array('alias' => 'interactions')
);
}
}
Model for LeadsContents (simplified)
class LeadsContents extends PersendlyModelAbstract {
public function initialize() {
$this->belongsTo('lead_id', 'Lead', 'id', array('alias' => 'lead'));
$this->belongsTo('content_id', 'Content', 'id', array('alias' => 'content'));
$this->hasMany('id', 'LeadsContentsInteractions', 'lead_content_id');
}
}
Model for LeadsContentsInteractions (simplified)
class LeadsContentsInteractions extends PersendlyModelAbstract {
public function initialize() {
$this->belongsTo('lead_content_id', 'LeadsContents', 'id', array('alias' => 'lead_content'));
}
}
If you are wanting to add a column that doesn't exist on the table, but exists as a business rule (created_on > DATE_SUB(NOW(),INTERVAL 1 DAY), 1, 0) then you need to add that rule in the afterFetch method of the model itself:
http://docs.phalconphp.com/en/latest/reference/models.html#initializing-preparing-fetched-records
class LeadsContentsInteractions extends PersendlyModelAbstract
{
public $isNew;
public function afterFetch()
{
$this->isNew = INSERT BUSINESS LOGIC HERE
}
}
It should however be noted, that if you then use the method toArray() on the record set, that it will only use the columns that exist on the table itself.
http://forum.phalconphp.com/discussion/498/afterfetch-
Overriding toArray() Method for Virtual Fields.
In response to what David Duncan said:
It should however be noted, that if you then use the method toArray()
on the record set, that it will only use the columns that exist on the
table itself.
And to circumvent such Phalcon 'limitation', I created the following method override.
Step 1
Basically, create a BaseModel.php and put the next code there.
/**
* Method override.
*
* This method is inherited from Model::toArray()
* https://docs.phalconphp.com/en/3.2/api/Phalcon_Mvc_Model
*
* We override it here to circumvent a Phalcon limitation:
* https://stackoverflow.com/a/27626808/466395
*
* Basically, the limitation consists that, when one adds 'virtual fields' to a model (for example,
* by way of callback methods like afterFetch()), then using toArray() on that model only returns
* the fields in the database table but not the virtual fields.
*
* #access public
* #param array $columns As per the Model::toArray() method.
* #return array The data of the model, including any custom virtual fields.
*/
public function toArray($columns = null) {
// calls the regular toArray() method
$data = parent::toArray($columns);
// then gets the model's virtual fields, if any
$virtual_fields = [];
if (!empty($this->list_virtual_fields)) {
// iterates, to get the virtual field's name, value, and getter
foreach ($this->list_virtual_fields as $name) {
$getter_name = 'get' . \Phalcon\Text::camelize($name);
$virtual_fields[$name] = $this->{$getter_name}();
}
}
// merges the model's database data with its virtual fields
$data = array_merge($data, $virtual_fields);
return $data;
}
Step 2
Then, in any of your app models, define the list of virtual fields that will be included in the method override above. For example:
public $list_virtual_fields = [
'status_label'
];
You should also define class properties, setters, and getters for those virtual fields. Just an example:
protected $status_label;
public function getStatusLabel() {
return $this->status_label;
}
public function setStatusLabel(string $status_label) {
$this->status_label = $status_label;
return $this;
}
Step 3
Finally, set the value of virtual fields throughout your app. An example:
public function afterFetch() {
$this->setStatusLabel('pending');
}
Note that my code uses getters and setters. You could change that if you wish.
I don't have any rules for the input fields, (name,place, position) so my model's rules function returns an empty array, but then empty values are getting saved into the database table.
public function rules()
{
return array();
}
Also when I omit rules() function from my model
$model->save()
returns true but DB table get inserted with empty values.
so how can I omit rules() function from my model class ?
So you should set all of them as safe attribute in rules.
public function rules(){
return array
array('id, name, /*list attribute here*/', 'safe')
);
}
You have to define safe attribute in rules.
public function rules(){
return array(
array('id, name, place, postion', 'safe')
);
}
Key Point - Massive Assignment will only be made for fields which have passed some explicit validation rule.