Validate single rows only when they are present Laravel - laravel

So i have a model named Customer.
The db the Customer looks like this:
id, name, lastName, personal, address, zip, location, phones, emails updated_at, created_at
Emails and Phones is special rows because they are store as an json object example
['john#doe.com', 'some#othermail.com', 'more#mails.com']
I use the Customer Model to store the validation rules and custom messages like this
<?php
class Customer extends BaseModel
{
public function validationRules()
{
return array(
'name' => 'required|max:255',
'lastName' =>'max:255',
'personal'=> 'integer',
'location' => 'max:255',
'address' => 'max:255',
'zip' => 'required|integer',
'phones' => 'betweenOrArray:8,10|required_without:emails',
'emails' => 'emailOrArray'
);
}
public function validationMessages()
{
// returns Validation Messages (its too much to write down)
}
}
The OrArray Rule is found here https://stackoverflow.com/a/18163546/1430587
I call them through my controller like this
public function store()
{
$customer = new Customer;
$messages = $customer->validationMessages();
$rules = $customer->validationRules();
$input['name'] = Input::get('name');
$input['lastName'] = Input::get('lastName');
$input['personal'] = preg_replace("/[^0-9]/", "", Input::get('personal'));
$input['location'] = Input::get('location');
$input['address'] = Input::get('address');
$input['zip'] = Input::get('zip');
$input['emails'] = Input::get('emails');
$input['phones'] = Input::get('phones');
foreach($input['phones'] as $i => $value)
{
$input['phones'][$i] = preg_replace("/[^0-9]/", "", $value);
}
$validator = Validator::make($input, $rules, $messages);
}
This all works just fine, but I want to be able to PUT/PATCH request to update a single row.
But the validationRules has Required on certain fields so when its not present i cant update that single row. Without getting an error that the other fields (witch I'm not posting) is required.
How would I best approach this ?

You should get that instance of the model that represent the row you want to edit, that's why the resource controller's update method has a parameter that is the resource you want to edit.
public function update($resourceId) {
$customer = Customer::where('id', '=', $resourceId);
}
Now this customer has all the attributes you set before, so you can access them like:
$customer->name;
$customer->lastName;
So when you valide the values you can use the existing values in your validator where the input is empty:
$input['name'] = (Input::get('name')) ? (Input::get('name')) : $customer->name;
Or a prettier solution with the elvis operator:
$input['name'] = (Input::get('name')) ?: $customer->name;

I came up with another solution to this problem that works very well and its much cleaner.
$customer = Customer::find($id);
$input = Input::except('_method', '_token');
$customer->fill($input);

Related

CodeIgniter 3 code does not add data to database into 2 different tables (user_info & phone_info)

The problem is when I entered a new name no data is added. A similar thing happen when I entered an already existing name. Still, no data is added to the database. I am still new to CodeIgniter and not entirely sure my query builder inside the model is correct or not.
In the Model, I check if the name already exists insert data only into the phone_info table. IF name does not exist I insert data into user_info and phone_info.
Controller:
public function addData()
{
$name = $this->input->post('name');
$contact_num = $this->input->post('contact_num');
if($name == '') {
$result['message'] = "Please enter contact name";
} elseif($contact_num == '') {
$result['message'] = "Please enter contact number";
} else {
$result['message'] = "";
$data = array(
'name' => $name,
'contact_num' => $contact_num
);
$this->m->addData($data);
}
echo json_encode($result);
}
Model:
public function addData($data)
{
if(mysqli_num_rows($data['name']) > 0) {
$user = $this->db->get_where('user_info', array('name' => $data['name']))->result_array();
$user_id = $user['id'];
$phone_info = array(
'contact_num' => $data['contact_num'],
'user_id' => $user_id
);
$this->db->insert('phone_info',$phone_info);
} else {
$user_info = array(
'name' => $data['name']
);
$this->db->insert('user_info', $user_info);
$user = $this->db->get_where('user_info', array('name' => $data['name']))->result_array();
$user_id = $user['id'];
$phone_info = array(
'contact_num' => $data['contact_num'],
'user_id' => $user_id
);
$this->db->insert('phone_info', $phone_info);
}
}
DB-Table user_info:
DB-Table phone_info:
Extend and change your model to this:
public function findByTitle($name)
{
$this->db->where('name', $name);
return $this->result();
}
public function addData($data)
{
if(count($this->findByTitle($data['name'])) > 0) {
//.. your code
} else {
//.. your code
}
}
Explanation:
This:
if(mysqli_num_rows($data['name']) > 0)
..is not working to find database entries by name. To do this you can use codeigniters built in model functions and benefit from the MVC Pattern features, that CodeIgniter comes with.
I wrapped the actual findByName in a function so you can adapt this to other logic and use it elswehere later on. This function uses the query() method.
Read more about CodeIgniters Model Queries in the documentation.
Sidenote: mysqli_num_rows is used to iterate find results recieved by mysqli_query. This is very basic sql querying and you do not need that in a MVC-Framework like CodeIgniter. If you every appear to need write a manual sql-query, even then you should use CodeIgniters RawQuery methods.

Laravel 6 validate multiple columns

Using the following code to create and update records with Laravel 6, how to add a unique validation that combines 'iso' and 'division' columns must unique, for both update and create? Also how to update the 'updated_at' column automatically without 'touch' it?
public function upsert(Request $request)
{
$this->authorize('manage','App\Admin\Division');
$this->validate($request,[
'records.iso' => 'required|regex:/^[A-Z]{2}$/',
'records.division' => 'required|min:2|max:3',
'records.remarks' => 'min:2|max:255|string|nullable',
],
[ 'records.iso.required'=>'Country iso Requied',
'records.iso.regex'=>'Country iso format : AA',
'records.division.required'=>'Division Code must entered',
'records.division.min'=>'Division Code minimum 2 characters',
'records.division.max'=>'Division Code maximum 3 characters',
],
);
$validatedData = $request->records;
if($validatedData['id']){ // have id, old record -> update
Division::where('id', $validatedData['id'])->update($validatedData);
//*******/ To correct updated_at no auto updated
$results = Division::find($validatedData['id']);
$results->touch();
//****** */ End of correction
}else{ // no id -> create record
$validatedData['auth'] = Auth::id();
$results = Division::create($validatedData);
}
return ['divisions' => $results];
}
All the validation rules apply for each field, i think you can do a foreach to workaround and compact the code, something like:
foreach ($records as $key => $record) {
$fields['record'.$key] = 'iso', 'division';
$rules['record'.$key] = 'required|min:2';
}
$validator = Validator::make($fields, $rules);
In the rules array you can put all the shared rules, but you have to create the other complex validation rules after this, so i think that your validation now is just fine.
And with the updated_at field, use the updateOrCreate
$results = Division::updateOrCreate(
['id', $validatedData['id'],
[$validatedData]
);
Adapt the above example for your code.

Put other function on FormRequest in Laravel

I'm building a Laravel 6 application, and I am concerned about "best practices." I have one controller named CustomerController. In my controller, I want to update the Customer model, so I will have a function like the following.
public function update(UpdateCustomer $request, Customer $customer){
//
}
UpdateCustomer is my form request and where I will do the validation. In my update() method, I have classic validation.
public function rules()
{
$validationArray = [];
$validationArray['customer.name'] = 'string|required';
$validationArray['customer.vat'] = 'string|required';
$validationArray['customer.email'] = 'email|required';
return $validationArray;
}
Now I have to do some particular validation other the classic.
Let's assume that I have more data in my model, and I don't want these values to be changed.
For example, I have the following: address, cap, locality. I have a second method on the UpdateCustomer request that I can validate.
public function validateForDataCantChange()
{
$data = $this->input("customer");
$customer = $this->route("customerID");
$validator = Validator::make([], []); // Empty data and rules fields
$arrayDataThatCantChange = [
'address' => $data['address'] ?? NULL,
'cap' => $data['cap'] ?? NULL,
'locality' => $data['locality'] ?? NULL
];
foreach ($arrayDataThatCantChange as $key => $v) {
if ($customer->{$key} !== $v) {
$validator->errors()->add($key, __("messages.the field :field can't be changed", ['field' => $key]));
}
}
if ($validator->errors()->any()) {
throw new ValidationException($validator);
}
}
And then in my controller, I've added the following.
public function update(UpdateCustomer $request, Customer $customer){
$request->validateForDataCantChange();
}
Is this a bad practice? Should I create a new FormRequest? How, in this case (two form requests), can I use two different requests for a single controller?
For the little effort required, I'd personally create a new form request.
If you wish to use the same form request you can do the following:
public function rules()
{
$rules = [
'title' => 'required:unique:posts'
];
// when editing i.e. /posts/2/edit
if ($id = $this->segment(2)) {
$rules['title'] .= ",$id";
}
return $rules;
}
However, I always use a separate class for each action.

Create Relationship inside the create function

I have a model that has a one to many relationship to the versions of the description.
In my Controller
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
]);
$tag->update([
'content' => $request->get('description')
]);
In my Model:
public function setContentAttribute(string $value)
{
$this->versions()->create([
'user_id' => \Auth::id(),
'value' => $value
]);
}
So I can't put content directly as an attribute in the create method because there is no Model right now.
But is it possible to overwrite the create Method?
When I try to overwrite something like this in my Model it will do an infinity loop
public static function create($attr) {
return parent::create($attr);
}
So my question is if it is possible to have something like this:
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
'content' => $request->get('content')
]);
and in the Model:
public static function create($attr) {
$value = $attr['content'];
$attr['content'] = null;
$object = parent::create($attr);
$object->content = $value;
$object->save();
return $object;
}
Update
I didn't overwrite the create method but called it customCreate. So there is no infinity loop anymore and I can pass all variables to the customCreate function that handles the relationships for me.
Solution
After reading the changes from 5.3 to 5.4 it turns out that the create method was moved so you don't have to call parent::create() anymore.
The final solution is:
public static function create($attr) {
$content = $attr['content'];
unset($attr['content']);
$element = static::query()->create($attr);
$element->content = $content;
$element->save();
return $element;
}
I don't see why not and you could probably implement a more general approach? Eg. checking if set{property}Attribute() method exists, if it does - use it to assign a value, if it doesn't - use mass assigning.
Something like:
public static function create($attr) {
$indirect = collect($attr)->filter(function($value, $property) {
return method_exists(self::class, 'set' . camel_case($property) . 'Attribute');
});
$entity = parent::create(array_diff_key($attr, $indirect->toArray()));
$indirect->each(function($value, $property) use ($entity) {
$entity->{$property} = $value;
});
$entity->save();
return $entity;
}
I haven't really tested it but it should work. I use something like this in one of my Symfony apps.

codeigniter datamapper get_rules not called after save()

I'm using Datamapper ORM for CodeIgniter I have rules 'serialized' and get_rules 'unserialized' for a field in my model. This field will store serialized data and when I retrieve back, get_rules will unserialize it.
However, after calling save(), I'm trying to re-access the field, but it still return serialized string, instead of array.
Is there any way to re-call or refresh my object so that the get_rules is called again and the field now return array?
Here's my model:
class User extends DataMapper{
public $validation = array(
'password' => array(
'label' => 'Password',
'rules' => array('encrypt')
),
'preferences' => array(
'rules' => array('serialize'),
'get_rules'=> array('unserialize')
)
);
function __construct($id = NULL)
{
parent::__construct($id);
}
function post_model_init($from_cache = FALSE)
{
}
public function _encrypt($field)
{
if (!empty($this->{$field}))
{
$this->{$field} = md5($this->{$field});
}
}
}
Datamapper ORM, afaik, will only use the get_rules when actually performing a get(). A few things you could try:
Given the following
$a = new Fruit();
$a->name = 'grapes';
$a->colors = serialize(array("purple","green"));
$a->save();
1. Create a new datamapper object and re-fetch:
$b = new Fruit();
$b->where('id', $a->id)->get();
$colors = $b->colors;
2. unserialize() the field yourself...
$colors = unserialize($a->colors);
3. You might even be able to use get_clone()
//not tested...
$b = $a->get_clone();
$colors = $b->colors;
This has been fixed here: https://bitbucket.org/wanwizard/datamapper/commits/db6ad5f2e10650b0c00c8ef9b7176d49a8e85163
Get the latest Datamapper library from bitbucket.

Resources