Why Laravel accessor fires on insert? - laravel

I'm not sure how they work, I was thinking that accessor fires when you access the attribute, however when I try to insert new record my accessor fires. I store in my database only image_name by removing URL and I use an accessor to include my URL route when I retrieve my image name. I found out that my accessor fires on insert and change my input:
ImageRepository.php
public function save($imageData)
{
$this->model->owner()->associate(auth()->user());
$this->model->type = $imageData->type;
$this->model->image_name = $imageData->image_url;
$this->model->save();
return $this->model;
}
Image.php
public function getImageNameAttribute($value)
{
$filePath = 'my_path';
// check if method was hit
logger()->info('getImageNameAttribute: '.$value);
// return default image if not found
if ($value == '' || $value == null || Str::contains($value, 'no-image.png') || !File::exists(public_path($filePath))) {
return asset('img/no-image.png');
} else {
return asset($filePath);
}
}
Input for $imageData->image_url:
https://some-random-image-url/image-name.jpg
Inserted data:
img/no-image.png
Excepted output (I have a function that extracts the name from URL):
image-name.jpg
When I check the log, I get the message:
getImageNameAttribute: https://some-random-image-url/image-name.jpg
Can someone explain to me if I'm doing something wrong or this is working as pretended?

I did solve the problem, I miss checked. Actually inserted record in my database is correct:
image-name.jpg
But I was checking dd() output after the insert and the value from there was not correct:
img/no-image.png
Because when dd() fires, the accessor is also fired (as we are retrieving data) and as the image was not downloaded it was actually showing correct data.

Related

Laravel check if updateOrCreate performed an update

I have the following code in my controller:
for($i=0; $i<$number_of_tourists; $i++) {
$tourist = Tourist::updateOrCreate([
'doc_number' => $request['doc_number'][$I]
],
$tourist_to_update);
}
Each time updateOrCreate runs, it does 1 of 3 things:
Updates the model instance; OR
Creates and saves a new one; OR
Leaves everything unchanged (if model with such values already exists)
I need to check if updateOrCreate has done the first one (updated) and then execute some code.
How can I do it?
You can figure it out like this:
$tourist = Tourist::updateOrCreate([...]);
if(!$tourist->wasRecentlyCreated && $tourist->wasChanged()){
// updateOrCreate performed an update
}
if(!$tourist->wasRecentlyCreated && !$tourist->wasChanged()){
// updateOrCreate performed nothing, row did not change
}
if($tourist->wasRecentlyCreated){
// updateOrCreate performed create
}
Remarks
From Laravel 5.5 upwards you can check if updates have actually taken place with the wasChanged and isDirty method.
isDirty() is true if model attribute has been changed and not saved.
wasChanged() is true if model attribute has been changed and saved.
There is also a property (not method!) wasRecentlyCreated to check if user was created or not.
$user = factory(\App\User::class)->create();
$user->wasRecentlyCreated; // true
$user->wasChanged(); // false
$user->isDirty(); // false
$user = \App\User::find($user->id);
$user->wasRecentlyCreated; // false
$user->wasChanged(); // false
$user->isDirty(); // false
$user->firstname = 'Max';
$user->wasChanged(); // false
$user->isDirty(); // true
$user->save();
$user->wasChanged(); // true
$user->isDirty(); // false
//You can also check if a specific attribute was changed:
$user->wasChanged('firstname');
$user->isDirty('firstname');
You can checkout the link to the laravel's documentation for wasChanged and isDirty methods.
https://laravel.com/docs/8.x/eloquent#examining-attribute-changes or
https://laravel.com/docs/9.x/eloquent#examining-attribute-changes
It is pretty easy to determine if the function resulted in an update or an insert (check the wasRecentlyCreated property). However, when using that function, it is less easy to determine if the update actually happened (if the model exists but is not dirty, no update will be performed). I would suggest not using that function, and splitting out the functionality yourself.
This is the function definition:
public function updateOrCreate(array $attributes, array $values = [])
{
$instance = $this->firstOrNew($attributes);
$instance->fill($values)->save();
return $instance;
}
To integrate this into your code, I'd suggest something like:
for ($i=0; $i<$number_of_tourists; $i++) {
$tourist = Tourist::firstOrNew(['doc_number' => $request['doc_number'][$i]]);
$tourist->fill($tourist_to_update);
// if the record exists and the fill changed data, update will be performed
$updated = $tourist->exists && $tourist->isDirty();
// save the tourist (insert or update)
$tourist->save();
if ($updated) {
// extra code
}
}
Okay so I couldn't find a good answer for my scenario.
I was using: $this->created_at == $this->updated_at however I would sometimes update the record later in the request, which meant that 20% of the time the created_at and updated_at were about 1ms out.
To combat this I created something a little more relaxed which allows an extra second between creation and modification.
public function getRecentlyCreatedAttribute()
{
return $this->wasRecentlyCreated || $this->created_at == $this->updated_at || $this->created_at->diffInSeconds($this->updated_at) <= 1;
}
I can now call $this->recentlyCreated which will return true if there is a small difference in time (1 second).
Tbh this is the second time I've needed this in a project, I'm posting as I just ended up googling it and coming back to this thread looking for the same answer.
If someone has a more elegant solution, hmu.
#patricus below presented a working way to solve the problem.
though #TheFallen here gave a solution which uses Eloquent Events and seems more elegant:
Laravel Eloquent Events - implement to save model if Updated
The model attribute 'wasRecentlyCreated' would only be 'true' if it has just been created.
There is property named 'changes' in model (it is an array), that determines whether the model has been updated with new values or it has been saved as is without making any changes to its attribute.
Check the following code snippet:
// Case 1 : Model Created
if ($model->wasRecentlyCreated) {
} else { // Case 2 : Model Updated
if (count($model->changes)) { // model has been assigned new values to one of its attributes and saved successfully
} else { // model has NOT been assigned new values to one of its attributes and saved as is
}
}

Laravel $request with variable returns null

I am writing an update methoda for my api. I neede to update requested fields.
So ı am trying to get only requested fields and update them. However, the code below returns me null even thoug I cast $fill to string.
foreach ($fillableFields as $fill){
$customerId->$fill = $request->get("$fill") ;
edit- 1
My code is like below ;
$fillableFields = array('lastname','firstname');
when I dd($fill) it returns me null .
You forgot to call the save() method to update your object :
foreach ($fillableFields as $fill){
$customerId->$fill = $request->get($fill);
$customerId->save();
}
Your method should be.
public function yourMethod(Request $request)
{
try {
foreach ($fillableFields as $fill){
$customerId->$fill = $request->get($fill) ;
}
}
} catch (\Exception $ex) {
}
}
Best way to get request parameters is provided at laravel's official documentation as below.
You can get request parameter based on your specific need.
By using below statment you can get only those perameter you have specified in only attribute as array eg : username, password.
$input = $request->only(['username', 'password']);
If you wantto get all fields excluding particular field than you can define as below with except parameter. eg: by using below it will allow to get all the fields except credit_card.
$input = $request->except('credit_card');
For more information on request parameter please refer official doc url : https://laravel.com/docs/5.4/requests

Loading page dynamically from database via id in controller

I am trying to load a page dynamically based on the database results however I have no idea how to implement this into codeigniter.
I have got a controller:
function history()
{
//here is code that gets all rows in database where uid = myid
}
Now in the view for this controller I would like to have a link for each of these rows that will open say website.com/page/history?fid=myuniquestring however where I am getting is stuck is how exactly I can load up this page and have the controller get the string. And then do a database query and load a different view if the string exsists, and also retrieve that string.
So something like:
function history$somestring()
{
if($somestring){
//I will load a different view and pass $somestring into it
} else {
//here is code that gets all rows in database where uid = myid
}
}
What I don't understand is how I can detect if $somestring is at the end of the url for this controller and then be able to work with it if it exists.
Any help/advice greatly appreciated.
For example, if your url is :
http://base_url/controller/history/1
Say, 1 be the id, then you retrieve the id as follows:
function history(){
if( $this->uri->segment(3) ){ #if you get an id in the third segment of the url
// load your page here
$id = $this->uri->segment(3); #get the id from the url and load the page
}else{
//here is code that gets all rows in database where uid = myid and load the listing view
}
}
You should generate urls like website.com/page/history/myuniquestring and then declare controller action as:
function history($somestring)
{
if($somestring){
//I will load a different view and pass $somestring into it
} else {
//here is code that gets all rows in database where uid = myid
}
}
There are a lot of ways you can just expect this from your URI segments, I'm going to give a very generic example. Below, we have a controller function that takes two optional arguments from the given URI, a string, and an ID:
public function history($string = NULL, $uid = NULL)
{
$viewData = array('uid' => NULL, 'string' => NULL);
$viewName = 'default';
if ($string !== NULL) {
$vieData['string'] = $string;
$viewName = 'test_one';
}
if ($uid !== NULL) {
$viewData['uid'] = $uid;
}
$this->load->view($viewName, $viewData);
}
The actual URL would be something like:
example.com/history/somestring/123
You then know clearly both in your controller and view which, if any were set (perhaps you need to load a model and do a query if a string is passed, etc.
You could also do this in an if / else if / else block if that made more sense, I couldn't quite tell what you were trying to put together from your example. Just be careful to deal with none, one or both values being passed.
The more efficient version of that function is:
public function history($string = NULL, $uid = NULL)
{
if ($string !== NULL):
$viewName = 'test_one';
// load a model? do a query?
else:
$viewName = 'default';
endif;
// Make sure to also deal with neither being set - this is just example code
$this->load->view($viewName, array('string' => $string, 'uid' => $uid));
}
The expanded version just does a simpler job at illustrating how segments work. You can also examine the given URI directly using the CI URI Class (segment() being the most common method). Using that to see if a given segment was passed, you don't have to set default arguments in the controller method.
As I said, a bunch of ways of going about it :)

View Same user_id

//Anyone can help to create a view data with same id? it is a multiple viewing.
this is my Controller. i dont khow apply in Model and View
function Get_Pitch($id){
$this->load->model('users_model');
$data['query'] = $id;
$this->load->view('view_pitch', $data);
}
Example this is my url "http://localhost/SMS_System/home/sample/102"
in my database is
id=1 name=erwin user_id=102
id=2 name=flores user_id=102
id=3 name=sample user_id=202
how to view the same user_id?
First of all with what you've supplied your URL won't work, you aren't following the normal conventions for CI so it won't know where to look. I am assuming your controller is called sample then you need to tell the application which function you're calling in that controller, finally URL names should be lower case so I changed that, so your URL should read:
"http://localhost/SMS_System/home/sample/get_pitch/102"
Also you need to get your data from a model, you loaded the model then didn't use it. The line after loading the model calls a function from that model and passes it the id you got from your url. Notice the if not isset on the id, this ensures that if someone goes to that page without the id segment there are no errors thrown from the model having a missing parameter, it will just return nothing, that is handled in the view.
Controller:
function get_pitch($id){
//the following line gets the id based on the segment it's in in the URL
$id=$this->uri_segment(3);
if(!isset($id))
{
$id = 0;
}
$this->load->model('users_model');
$data['query'] = $this->users_model->getUserData($id);
$this->load->view('view_pitch', $data);
}
Your model takes the id passed from the controller and uses that to retrieve the data from the database. I normally create the array I am going to return as an empty array and handle that in the view, this makes sure you get no errors if the query fails. The data then returns to the controller in the last line and is passed to the view in your load view call.
Model:
function getUserData($id)
{
$this->db->where('id',$id);
$result = $this->db->get('users') //assuming the table is named users
$data = array(); //create empty array so we aren't returning nothing if the query fails
if ($result->num_rows()==1) //only return data if we get only one result
{
$data = $result->result_array();
}
return $data;
}
Your view then takes the data it received from the model via the controller and displays it if present, if the data is not present it displays an error stating the user does not exist.
View:
if(isset($query['id']))
{
echo $query['id']; //the variable is the array we created inside the $data variable in the controller.
echo $query['name'];
echo $query['user_id'];
} else {
echo 'That user does not exist';
}

How to use form_validation library rules for AJAX requests

One of my forms uses AJAX to send data. Since my page never reloads because of AJAX, is there a way I can still make use of form_validation to validate and output which fields are wrong? The url my form sends to using jquery is contact/ajax_send.
The entire AJAX works fine except I haven't inserted any validation yet.
Since you are using ajax to send the data, what you can do is, simply add the form_validation code calls before inserting into the database.
If there are any validation errors, you can either return the error messages as json response to the front end to display the error messages.
The form validation library assigns all errors that occurred to a private array called _error_array, but does not expose it or provide documentation on it (notice the first underscore?). Just return a json encoded object of the errors in the controller:
echo json_encode($this->form_validation->_error_array);
If you wish, you can extend CodeIgniter's form validation library, perhaps returning FALSE instead of an empty array... or whatever you see fit:
/* libraries/MY_Form_validation.php */
<?php
class MY_Form_validation extends CI_Form_validation
{
function __construct($config = array())
{
parent::__construct($config);
}
function error_array()
{
if (count($this->_error_array) === 0) return FALSE;
else return $this->_error_array;
}
}
Now, drop the initial underscore in the controller:
echo json_encode($this->form_validation->error_array);
Then decode and display errors on the client.
I found a method, thanks in part to Jordan's answer. This returns an array containing the names of the fields which have errors.
// library/MY_Form_validation.php
class MY_Form_validation extends CI_Form_validation {
public function get_field_data(){
return count($this->_field_data) ? $this->_field_data : FALSE;
}
}
// Controller file
$field_data = $this->form_validation->get_field_data();
foreach($field_data as $key=>$val){
if($key == '__proto__') break;
foreach($val as $k=>$v){
if($k == 'error' && !empty($v)) $errors[] = $key;
}
}
$return = array('success'=>FALSE, 'errors'=>$errors);
The above code checks the error key whether it's empty or not. Empty values mean that it passed the CI validation while none empty values would contain the string you see when you use validation_errors(). Since I'm after those fields which have errors, I only needed to see which values are not empty disregardig those which are.

Resources