Laravel 4 - Model not being saved - laravel-4

Having this code:
class SearchIndex extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'searchindex';
//no timestamps
public $timestamps = false;
//mass-assignment
public $fillable = array('url', 'content', 'version', 'digest');
public $guarded = array();
public static function updateIndex($url, $version, $content)
{
self::retrieveSearchEntry($url, $version)->updateContent($content)->save();
}
public static function retrieveSearchEntry($url, $version)
{
try
{
return self::withoutContent()->where('url', $url)->where('version', $version)->firstOrFail(array('url', 'version', 'digest'));
}
catch(\Illuminate\Database\Eloquent\ModelNotFoundException $e)
{
return new SearchIndex(array('url' => $url, 'version' => $version));
}
}
public function updateContent($content)
{
$hash = md5($content);
if (!isset($this->digest) || ($this->digest != $hash))
{
$this->content = $content;
$this->digest = $hash;
}
return $this;
}
public static function search($content)
{
}
}
If I call updateIndex providing a new combination of url+version, an entry is created.
If I call updateIndex providing an existing pair of url+version, the entry is not updated with the new content. I'm seeing it has something to do with the fact that I'm omitting the 'content' field (reason: is huge, and I want to set it, not get it, in that function).
question: How can I not-get the content field, but be able to set it when saving?

Solved. Reason: I was not selecting the "id" field when doing the custom selection in firstOrFail(). In that way, id was null and the generated SQL tried to update (since it was an existent object) "where id is NULL", which would affect 0 rows.

Related

Hiding attributes in model in CodeIgniter 4

I have a CodeIgniter 4 Model.
When I return data from it with find()/findAll(), it returns all the attributes that are present in the model.
$userModel->findAll();
I'd like to hide some of them, for example, I don't want to return the dates (created_at, updated_at).
I tried to create an Entity object and return it, but it also returns everything.
Basically what I'd like to do is to have functionality like in Laravel, where you have a $hidden protected array in the Model.
I've been experimenting with the afterFind callback, but I don't like the solution. Mainly because there is a difference in the $data array when you use find() vs findAll()
protected $afterFind = ['prepareOutput'];
protected function prepareOutput(array $data) {
return $data;
}
$data['data'] is the actual data when using find()
$data['data'] is an array of arrays when using findAll();
Which kinda makes sense, but then I have to be sure to modify the data according to the method used.
Is there something I am missing in the documentation? Can this be done in a simpler way?
So I came up with this, but I'm still open to better and nicer solutions.
First I created my own Model and added the logic to hide some attributes:
<?php
namespace App\Models;
use CodeIgniter\Model;
class MyBaseModel extends Model {
protected $hidden = [];
public function prepareOutput(array $data) {
// if the hidden array is empty, we just return the original dta
if (sizeof($this->hidden) == 0) return $data;
// if no data was found we return the original data to ensure the right structure
if (!$data['data']) return $data;
$resultData = [];
// We want the found data to be an array, so we can loop through it.
// find() and first() return only one data item, not an array
if (($data['method'] == 'find') || ($data['method'] == 'first')) {
$data['data'] = [$data['data']];
}
if ($data['data']) {
foreach ($data['data'] as $dataItem) {
foreach ($this->hidden as $attributeToHide) {
// here we hide the unwanted attributes, but we need to check if the return type of the model is an array or an object/entity
if (is_array($dataItem)) {
unset($dataItem[$attributeToHide]);
} else {
unset($dataItem->{$attributeToHide});
}
}
array_push($resultData, $dataItem);
}
}
// return the right data structure depending on the method used
if (($data['method'] == 'find') || ($data['method'] == 'first')) {
return ['data' => $resultData[0]];
} else {
return ['data' => $resultData];
}
}
}
Now I can extend my existing models and fill in which attributes I want to hide:
<?php namespace App\Models;
use App\Entities\User;
class UserModel extends MyBaseModel {
protected $table = 'users';
protected $primaryKey = 'id';
protected $returnType = User::class;
protected $useSoftDeletes = false;
protected $allowedFields = ['email', 'password', 'first_name', 'last_name'];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $allowCallbacks = true;
protected $afterFind = ['prepareOutput'];
protected $hidden = ['created_at', 'updated_at'];
}

laravel update() is not working on some models

I am trying to update the database record but Laravel update() function is not working. I have fillable array in the model. but still, it is not working.
The Property Model:
class Property extends Model
{
use HasFactory;
protected $table = 'properties';
protected $primaryKey = 'proID';
public $timestamps = false;
protected $fillable = [ 'proID', 'ProStatus', 'ProPurpose', 'ProType', 'SubType', 'basePrice', 'unitPrice', 'Width', 'Length', 'LandArea','PropertyNumber', 'water', 'electricity', 'gas', 'severage', 'fk_Street', 'createdBy', 'delete'];
public function streets(){
return $this->belongsTo(Street::class,'fk_Street');
}
public function hasInstallments(){
return $this->hasMany(Installments::class,'proID');
}
The PropertyController:
public function destroy($id)
{
$property = Property::find($id);
$property->delete = true;
if($property->save()){
return response()->json(['success'=>true]);
}
}
the $property->update() always returns true but record does not update in database.
The method update() is for mass update wich require an array of attributes and bypass mutators.
public function destroy($id)
{
$property = Property::find($id);
$property->update(['delete' => 1]);
}
You might want to use save() instead
public function destroy($id)
{
$property = Property::find($id);
$property->delete = 1;
$property->save();
}
Both will update the record, you'll need to implement your method's return logic on top of this code but as for updating the record, I think you get the idea.
Your property table primary key is "proID"
public function destroy($id)
{
$property = Property::where('proID', $id)->first();
if($property->update(['delete' => 1])) {
return response()->json(['success' => true]);
}
}

Laravel - How to delete record based on condition

In my Laravel-5.8, I have these three (3) models:
Parameter
class Parameter extends Model
{
protected $table = 'parameters';
protected $primaryKey = 'id';
protected $fillable = [
'max_score',
'min_score',
'identity_id',
];
public function identity()
{
return $this->belongsTo('App\Models\Identity','identity_id');
}
}
Identity
class Identity extends Model
{
protected $table = 'identity';
protected $fillable = [
'id',
'name',
];
public function goals()
{
return $this->hasMany('App\Models\Goal');
}
public function parameter()
{
return $this->hasOne(Parameter::class, 'identity_id');
}
}
Goal
class Goal extends Model
{
protected $table = 'goals';
protected $fillable = [
'id',
'identity_id',
'title',
];
public function identity()
{
return $this->belongsTo('App\Models\Identity','identity_id');
}
}
From the model, Identity has a foreign key (identity_id) in Parameter, also Identity has foreign key (identity_id) in Goal.
I have this controller in Identity:
public function destroy($id)
{
try
{
$identity = Identity::findOrFail($id);
$identity->delete();
Session::flash('success', 'Record deleted successfully.');
return redirect()->back();
}
catch (Exception $exception) {
Session::flash('error', 'Record delete failed!.');
return redirect()->back();
}
}
I want the user to delete Identity Record based on these conditions:
As the user tries to delete Identity, the application should check Parameter table and also delete the record where identity_id foreign key exist.
Secondly, if record with identity_id exists in Goal table, the application should prevent the delete.
How do I adjust public function destroy($id) to achieve this?
you can add a function like "deleteAll()" in your 'Identify' Model and delete parameter using relation like this:
class Identity extends Model
{
protected $table = 'identity';
protected $fillable = [
'id',
'name',
];
public function goals()
{
return $this->hasMany('App\Models\Goal');
}
public function parameter()
{
return $this->hasOne(Parameter::class, 'identity_id');
}
public function deleteAll(){
if(!$this->goals){
$this->parameter->delete();
return parent::delete();
}
return;
}
}
First in your migrations in the identity you need to do like
$table->foreign('identity_id')->references('id')->on('parameters')->onDelete('cascade');
So when you delete the Identity the related Parameter will be deleted automatically on database level.
To prevent delete (your mentioned 2 case), you need to check like this
$identity = Identity::with('goals')->findOrFail($id);
if($identity->goals){
// you can throw some error here
}

Undefined property in laravel framework accessor calling belongsTo

I need your help.
I'm working with Laravel Framework and I have a trouble with a belongsTo relationship.
My project have to tables, address book and delivery types, the columns in tables are:
address_book
id
name
mail
delivery_1
deliverytype_id_1
delivery_2
deliverytype_id_2
...
delivery_types
id
name
...
The code of delivery types model is this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class DeliveryType extends Model
{
protected $table = 'delivery_types';
protected $guarded = ['id'];
}
This is the address book model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AddressBook extends Model
{
protected $table = 'address_book'; // table
protected $guarded = ['id']; // primary key
protected $appends = ['delivery', 'actions']; // accessors
protected $delivery = '';
public function del1() {
return $this->belongsTo('App\DeliveryType', 'deliverytype_id_1', 'id')->withDefault();
}
public function del2() {
return $this->belongsTo('App\DeliveryType', 'deliverytype_id_2', 'id');
}
/**
* Accessor: get the actions column information.
*
* #return string
*/
public function getActionsAttribute() {
$actions = '<a href='. route('admin.addressbook.show', $this->id) .'>'.
'show<i class="livicon" data-name="info" data-size="18" data-loop="true" data-c="#428BCA" data-hc="#428BCA" title="view contact"></i></a>';
return $actions;
}
/**
* Accessor: get the deliveries information.
*
* #return string
*/
public function getDeliveryAttribute () {
$deliveries = [
['val' => $this->delivery_1, 'type' => $this->del1()->name], //row error
['val' => $this->delivery_2, 'type' => $this->del2()->name]
];
foreach($deliveries as $delivery) {
$this->delivery = (strlen($delivery['val']) > 0) ?
$this->appendString($this->delivery, '<strong>'.$delivery['type'].'</strong> '.$delivery['val']) :
$this->delivery;
}
return $this->delivery;
}
protected function appendString(string $str, string $val) {
return (strlen($str) > 0) ? $str.'<br>'.$val : $val;
}
In the html page the data is loaded through ajax call to the controller function. This is the code of function:
public function data(Request $request) {
// Init data
$this->addressbooks = AddressBook::get(
['address_book.id',
'address_book.name',
'address_book.email',
'address_book.delivery_1',
'address_book.delivery_2');
// Return json array
header("Content-Type: application/json");
return $this->addressbooks;
}
When the page call the function through ajax, the framework return the error "Undefined property" in the accessor getDeliveryAttribute, where I try to call relationship belongs to about delivery type ID and its reference in the delivery types table.
What I'm doing wrong? Thanks in advance to those who can help me.
Here is how I would write the AddressBook model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
//use Illuminate\Support\Facades\Log;
class AddressBook extends Model
{
protected $table = 'address_book'; // table
//protected $guarded = ['id']; // primary key
// always load these accessors
protected $appends = [
'delivery',
'actions',
];
protected $mail = '';
// No need for $delInfo, use $this->delivery (same data)
// protected $delInfo = '';
public function category() {
/*
You can use `AddressBookCategory::class` instead of `'App\DeliveryType'`
http://php.net/manual/en/migration55.new-features.php#migration55.new-features.class-name
Because models are in the same napespace, we don't need to write \App\AddressBookCategory
*/
return $this->belongsTo(AddressBookCategory::class, 'address_book_category_id', 'id');
}
public function deliveryType1() {
return $this->belongsTo(DeliveryType::class, 'deliverytype_id_1', 'id')
/*
Specify empty string for the name, so that when we access
$this->deliveryType1->name it's equal ''
*/
->withDefault(['name' => '']);
}
public function deliveryType2() {
return $this->belongsTo(DeliveryType::class, 'deliverytype_id_2', 'id')
->withDefault(['name' => '']);
}
/**
* Accessor: get the actions column information.
*
* Access by using: $this->action
*
* #return string
*/
public function getActionsAttribute() {
// moved this into multi line, easier to read
return '<a href='. route('admin.addressbook.show', $this->id) .'>'
.'show'
.'<i class="livicon" data-name="info" data-size="18" data-loop="true" data-c="#428BCA" data-hc="#428BCA" title="view contact"></i>'
.'</a>';
}
/**
* Accessor: get the deliveries information
*
* Access by using: $this->delivery
*
* #return string
*/
public function getDeliveryAttribute () {
// I've updated logic here, it should be easier to read...
$delivery = [];
if ( ! empty($this->delivery_1) ) {
$delivery[] = '<strong>'.$this->deliveryType1->name.'</strong> '.$this->delivery_1;
}
if ( ! empty($this->delivery_2) ) {
$delivery[] = '<strong>'.$this->deliveryType2->name.'</strong> '.$this->delivery_2;
}
// join array elements with a string `<br>`
return implode('<br>', $delivery);
}
}

Access Model Property In Laravel Mutators

I want to convert created_at dates to Persian date. So I implemented getCreatedAtAttribute function to do that. Because I just want to convert dates in special situations, I declared $convert_dates property in the model with default value as false.
class Posts extends Model {
public $convert_dates = false;
/**
* Always capitalize the first name when we retrieve it
*/
public function getCreatedAtAttribute($value) {
return $this->convert_dates? convert_date($value): $value;
}
}
$Model = new Posts;
$Model->convert_dates = true;
$post = $Model->first();
echo $post->created_at; // Isn't converted because $convert_dates is false
As you see in the codes above, it seems the model properties will be re-initial in mutators so the value of $convert_dates is always false.
Is there any other trick or solution to solve this problem?
This way you can set the constructor.
public function __construct($value = null, array $attributes = array())
{
$this->convert_dates = $value;
parent::__construct($attributes);
}
Now you can access this value in your mutator.
public function getCreatedAtAttribute($value)
{
return $this->convert_dates ? convert_date($value) : $value;
}
OR
Fill the protected fillable array like this:
class DataModel extends Eloquent
{
protected $fillable = array('convert_dates');
}
Then initialize the Model as:
$dataModel = new DataModel(array(
'convert_dates' => true
));

Resources