(SOLVED) How to update parent model in laravel? - php

[SOLVED]
/***
The parent-children relationship works fine. But there was a code i wrote several weeks ago that prevent me to update the progress value.
***/
I have a project that has similar functionality to trello (kanban)
There are 3 models which are projects, TaskList (columns), Tasks (cards).
a project has many columns
a column has many tasks
a task has many
subtasks (the subtask refer to the model itself)
each model has a column named progress. This column must be updated when the children are updated. The progress is calculated by averaging the progress of all cards.
Models :
Project.php
class Project extends Model
{
use HasFactory;
protected $table = 'projects';
public $timestamps = true;
protected $fillable = [
'id','title', 'description', 'actual_start', 'actual_end',
'start', 'end','progress'
];
public function updateProgress(){
$sum=0;
if(count($this->columns)){
for ($i = 0; $i < count($this->columns); $i++) {
$list = $this->columns[$i];
if($list->progress==null){
$list->progress=0;
}
$sum+=$list->progress;
}
$progress=$sum/count($this->columns);
$this->progress=round($progress);
$this->save();
}
return $this->progress;
}
public function scopeExclude($query, $value = [])
{
return $query->select(array_diff($this->fillable, (array) $value));
}
public function lists(){
return $this->hasMany(TaskList::class,'projects_id');
}
TaskList.php
class TaskList extends Model
{
protected $table = 'lists';
protected $fillable = [ 'title', 'position', 'projects_id', 'start', 'end', 'actual_start', 'actual_end','progress' ];
public static function boot() {
parent::boot();
static::saved(function($list){
$list->project->updateProgress();
});
}
public function updateProgress(){
$tes=[];
if(count($this->cards)){
$sum=0;
for ($i = 0; $i < count($this->cards); $i++) {
$task = $this->cards[$i];
if($task->progress==null){
$task->progress=0;
}
$sum+=$task->progress;
$tes[]=['id'=>$task->id,'progress'=>$task->progress,$task->cards];
}
$progress=$sum/count($this->cards);
$this->progress=round($progress);
$this->save();
}
return ['id'=>$this->id,'progress'=>$this->progress,'cards'=>$this->cards];
}
public function cards(){
return $this->hasMany(Task::class,'lists_id');
}
}
Task.php
class Task extends Model
{
use HasFactory;
protected $table = 'tasks';
public $timestamps = true;
protected $fillable = [
'id','users_id', 'title', 'description', 'complete', 'end', 'start',
'actual_start', 'actual_end', 'start_label', 'end_label',
'progress', 'is_subtask', 'lists_id','parent_task_id'
];
protected $casts = [
'progress' => 'double',
];
public static function boot() {
parent::boot();
static::saving(function($task){
if(!empty($task->start) && !empty($task->end)){
$start = Carbon::parse($task->start);
$end = Carbon::parse($task->end);
$days= $start->diffInDays($end);
$task->days=$days+1;
}
if(!empty($task->actual_start) && !empty($task->actual_end)){
$actual_start = Carbon::parse($task->actual_start);
$actual_end = Carbon::parse($task->actual_end);
$work_days= $actual_start->diffInDays($actual_end);
$task->work_days=$work_days+1;
}
// This part was the problem
if($task->parentTask){
if($task->actual_end){
$task->progress=100;
}else{
$task->progress=0;
}
}else{
if($task->actual_end){
$task->progress=100;
}else{
$task->progress=0;
}
}
if($task->parentTask){
if($task->actual_end){
$task->progress=100;
}else{
$task->progress=0;
}
}else{
if($task->actual_end){
$task->progress=100;
}else{
$task->progress=0;
}
}
//------------------------
if($task->progress==100){
$task->complete=true;
}
});
static::saved(function($task){
//is a subtask
if($task->parentTask){
// dd('subtask',$task->id,$task->complete,$task->progress,$task->parentTask,$task->list);
$task->parentTask->updateProgress();
}else{
// dd('parent task',$task->id,$task->complete,$task->progress,$task->parentTask,$task->list);
$task->list->updateProgress();
// dd('list ',$task->list->updateProgress());
}
});
static::deleting(function($task) {
foreach ($task->members as $i => $member) {
$member->delete();
}
foreach ($task->cards as $k => $subtask) {
$subtask->delete();
}
$task->tags()->detach();
$task->task_members()->detach();
});
}
public function updateProgress(){
if(count($this->cards)){
$valuePerSubtask=100/count($this->cards);
$completeSubtaskCounter=0;
for ($i = 0; $i < count($this->cards); $i++) {
$subtask = $this->cards[$i];
if($subtask->complete){
$completeSubtaskCounter++;
}
}
$progress=$completeSubtaskCounter*$valuePerSubtask;
$this->progress=$progress;
$this->save();
return [$this->progress,$progress];
}
}
public function scopeExclude($query, $value = [])
{
return $query->select(array_diff($this->fillable, (array) $value));
}
public function parentTask(){
return $this->belongsTo(Task::class,'parent_task_id');
}
public function list(){
return $this->belongsTo(TaskList::class,'lists_id');
}
}
I can't update the parent task from the children. As you can see in Task.php model, I have a updateProgress() that return [$this->progress, $progress]; after i call $this->save(). I already have $this->progress=$progress before i save it. But $this->progress still show the same result.
[![the first item is $this->progress, the second one is $progress][1]][1]
[1]: https://i.stack.imgur.com/RS2mq.png
i tried override touch() and place updateProgress() inside it. But still doesn't show the expected result.
Any advice for a better approach?
Am I missing something?

A solution also could be:
Create an observer on task.
php artisan make:observer TaskObserver
and then in the observer.
class UserObserver
{
//SOME METHODS
public function updated(Task $task)
{
/* Important, this will be done with all tasks on update in the tree
like Task->task->task etc... unless you put some extra logic. But
this is the general idea
*/
#search if parent
if ($task->parent_task_id !== null) {
//run progress
$task->parentTask->updateProgress()
}
}
}

Related

Laravel Eloquent map in query

I have a problem with mapping objects in a Laravel eloquent query.
How to map relations in a query builder.
How to connect two separate collections.
For example. Having models:
class CartProduct extends Model
{
protected $fillable = [
'quantity',
'cart_id',
'product_id',
'unit_price',
'product_code'
];
function product(){
return $this->belongsTo(Product::class);
}
function cart(){
return $this->belongsTo(Cart::class);
}
}
class Cart extends Model
{
function productsInCart()
{
return $this->hasMany(CartProduct::class);
}
public function products()
{
return $this->belongsToMany(
Product::class,
'cart_products',
'cart_id',
"product_id");
}
}
class Product extends Model
{
protected $fillable = [
'name',
'code',
'description',
'price',
];
}
The tasks are:
Get a set of products that are in the same cart (doesn't matter which one) as $product_id (excluding $product_id)
Get a set of products that were in any cart together with $product_id (excluding $product_id, without duplications)
I would solve it like this:
1.
public function task_one($product_id)
{
return $products = CartProduct::where('product_id', $product_id)->first()->cart->products
->filter(function (Product $p) use ($product_id) {
return $p->id !== $product_id;
});
}
public function task_two($product_id)
{
$cartProducts = CartProduct::where('product_id', $product_id)->get();
$products = collect(new Product);
foreach ($cartProducts as $cartProduct) {
$productsInCart = $cartProduct->cart->products
->filter(function (Product $p) use ($product_id) {
return $p->id !== $product_id;
});
$products = $products->merge($productsInCart);
}
return $products->unique();
}
However, the 2nd function seems to be awful. How can I do this properly, to achieve fast execution and a good style of code?
Is there any method to "map" the whole collection to related model objects? For example by
$carts = CartProduct::getByProductId($product_id)->"mapByRelationship('cart)";
//The result should be a collection od Carts
$products = CartProduct::getByProductId($product_id)->"mapByRelationship('cart)"->"mapByRelationship('products')"->unique();
//The result should be the same as task_two($product_id);
Thank you in advance
I think I have done this Controller-Model Relationship.
Controller:
class MakeAWishController extends Controller
{
public function getMakeAWishes(Request $request)
{
$limit = (int) ($request->limit ?? 1);
$offset = (int) ($limit * (($request->page ?? 1) - 1));
$wishes = MakeAWish::with('product')
->offset($offset)->limit($limit)->where('product_quantity', '>' , '0')->get()
->map(function ($wish) {
$wish->children_image = asset(Storage::url($wish->children_image));
if(!empty($variant = $wish->product->variant())) {
$wish->product->variant_id = $variant->variant_id;
$wish->product->variant_price = $variant->variant_price ?? "0.00";
$wish->product->variant_compare_at_price = $variant->variant_compare_at_price ?? "0.00";
}
return $wish;
});
$response = [
'status' => 200,
'data' => $wishes
];
return response()->json($response);
}
}
Model:
class MakeAWish extends Model
{
protected $collection = 'make_a_wishes';
protected $fillable = [
'children_name',
'children_name_for_isis',
'age',
'country',
'children_image',
'product_id',
'quantity'
];
protected $casts = [
'product_id' => 'string'
];
public function product()
{
return $this->hasOne(Product::class, 'product_id', 'product_id');
}
public function orders()
{
return $this->hasMany(OrderHistory::class, 'type_id', '_id');
}
public function orderCount()
{
return $this->orders()->where('type', 'M')->count();
}
}

Laravel Model is freezing the server. Timeout error

I'm having a really strange issue here. I have a user model (detailed below).
It all works fine until I added the getReportsSharedAttribute function. When this is added, the server freezes and I get:
PHP Fatal error: Maximum execution time of 60 seconds exceeded in C:\Users\User\PhpstormProjects\laravel-vue\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasRelationships.php on line 637
more:
exception: "Symfony\\Component\\ErrorHandler\\Error\\FatalError"
file: "C:\\Users\\User\\PhpstormProjects\\laravel-vue\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Concerns\\HasAttributes.php"
I thought there was something up with the code, so I ran it manually in a controller and dumped it, it worked fine.
So I tried it as a relation instead of an attribute. Same error.
So then I thought, is it just specific to the ReportingSetAssigned model, so I did another query on another collection, and another, and I still get the timeout error.
I tried another Model, it worked fine, for no apparent reason. Even though there were a lot more records inside. It doesn't seem to be dependant on how many columns are involved in the return. None of my tables have more than 50 records inside, even in the relations.
What's going on here? Is there some limit somewhere?
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Query\Builder;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\DB;
use Laravel\Sanctum\HasApiTokens;
use stdClass;
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
public $appends = [
'full_name',
'profile_photo_thumb',
'permissions_alt',
'line_managed_only_id',
'line_managers_only_id',
'permissions_meetings_only_id',
'reports_shared',
];
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
protected $dates = ['deleted_at'];
public function permissions(){
return $this->belongsToMany(Permission::class);
}
public function timelineitems(){
return $this->hasMany(TimelineItem::class);
}
public function line_managers(){
return $this->belongsToMany(User::class,'permissions_lm','user_id','lm_id');
}
public function line_managed(){
return $this->belongsToMany(User::class,'permissions_lm','lm_id','user_id');
}
public function permissions_meetings(){
return $this->belongsToMany(Area::class,'permissions_meetings','user_id','area_id')->withPivot('level');
}
public function getPermissionsMeetingsOnlyIdAttribute(){
return $this->permissions_meetings()->pluck('permissions_meetings.area_id');
}
public function permissions_qed(){
return $this->belongsToMany(Area::class,'permissions_qed','user_id','area_id')->withPivot('level');
}
public function permissions_reporting(){
return $this->belongsToMany(Area::class,'permissions_reporting','user_id','area_id')->withPivot('level');
}
public function permissions_reporting_sets(){
return $this->belongsToMany(ReportingSet::class,'permissions_reporting_sets','user_id','set_id')->withPivot('level');
}
public function improvement_category_objective_action_milestones(){
return $this->belongsToMany(ImprovementSetCategoryObjectiveActionMilestone::class);
}
public function planning_review_forms(){
return $this->hasMany(PerformanceManagementSetAssigned::class)->whereHas('set', function($q) {
$q->where('appraisal', 0);
});
}
public function appraisal_forms(){
return $this->hasMany(PerformanceManagementSetAssigned::class)->whereHas('set', function($q) {
$q->where('appraisal', 1);
});
}
public function performance_manager_set_as_lm(){
return $this->belongsTo(PerformanceManagementSetAssigned::class, 'lm_id');
}
public function getPermissionsAttribute(){
return $this->permissions()->get();
}
public function getLineManagersOnlyIdAttribute(){
return $this->line_managers()->pluck('permissions_lm.lm_id');
}
public function getLineManagedOnlyIdAttribute(){
return $this->line_managed()->pluck('permissions_lm.user_id');
}
public function hasPermissionTo($permission){
if(auth()->user()->super_admin){
return true;
}
if($permission==='super_admin'&&auth()->user()->super_admin){
return true;
}
if(!is_array($permission)){
$access = $this->permissions()->where('permission', $permission)->exists();
if($access){
return true;
}
return false;
}else{
foreach($permission as $p){
$access = $this->permissions()->where('permission', $permission)->exists();
if($access){
return true;
}
}
}
}
public function checkPermissionReportingSet($permission){
$access = $this->permissions_reporting_sets()->where('set_id', $permission)->first();
if($access){
if($access->pivot->level=='read'){
return 'read';
}
if($access->pivot->level=='write'){
return 'write';
}
}
}
public function checkPermissionReportingArea($permission){
$access = $this->permissions_reporting()->where('area_id', $permission)->first();
if($access){
if($access->pivot->level=='true'){
return true;
}
}
}
public function truePermission($permission){
$access = $this->permissions()->where('permission', $permission)->exists();
if($access){
return true;
}
}
public function updateTimeline($type_main,$type_sub,$title,$content,$icon,$color,$link,$relevant_id = null,$user_id = null){
if(!$user_id){
$user_id = $this->id;
}
$t = new TimelineItem();
$t->type_main = $type_main;
$t->type_sub = $type_sub;
$t->title = $title;
$t->content = $content;
$t->icon = $icon;
$t->color = $color;
$t->link = $link;
$t->user_id = $user_id;
$t->relevant_id = $relevant_id;
$t->save();
}
public function getPermissionsForVueAttribute(){
$permissions = $this->permissions;
$new = [];
foreach($permissions as $p){
$new[$p->permission] = true;
}
$new['meeting_areas'] = [];
$permissions = $this->permissions_meetings;
foreach($permissions as $p){
$new['meeting_areas'][$p->id] = $p->pivot->level;
}
$new['qed_areas'] = [];
$permissions = $this->permissions_qed;
foreach($permissions as $p){
$new['qed_areas'][$p->id] = $p->pivot->level;
}
$new['reporting_areas'] = [];
$permissions = $this->permissions_reporting;
foreach($permissions as $p){
$new['reporting_areas'][$p->id] = $p->pivot->level;
}
$new['reporting_sets'] = [];
$permissions = $this->permissions_reporting_sets;
foreach($permissions as $p){
$new['reporting_sets'][$p->id] = $p->pivot->level;
}
return json_encode($new);
}
public function getPermissionsAltAttribute(){
//General permissions
$permissions = Permission::get();
$newP = [];
foreach($permissions as $p){
$newP[$p->permission] = false;
}
$permissions = $this->permissions;
foreach($permissions as $p){
$newP[$p->permission] = true;
}
$newP['meeting_areas'] = [];
$newP['qed_areas'] = [];
$newP['reporting_areas'] = [];
$newP['reporting_sets'] = [];
foreach(Area::orderBy('name', 'ASC')->get() as $p){
$newP['meeting_areas'][$p->id] = "false";
$newP['qed_areas'][$p->id] = "false";
$newP['reporting_areas'][$p->id] = "false";
}
$meetings = DB::table('permissions_meetings')->where('user_id', '=', $this->id)->get();
foreach($meetings as $p){
$newP['meeting_areas'][$p->area_id] = $p->level;
}
$qed = DB::table('permissions_qed')->where('user_id', '=', $this->id)->get();
foreach($qed as $p){
$newP['qed_areas'][$p->area_id] = $p->level;
}
$reporting = DB::table('permissions_reporting')->where('user_id', '=', $this->id)->get();
foreach($reporting as $p){
$newP['reporting_areas'][$p->area_id] = $p->level;
}
foreach(ReportingSet::orderBy('name', 'ASC')->get() as $p){
$newP['reporting_sets'][$p->id] = "false";
}
$reporting = DB::table('permissions_reporting_sets')->where('user_id', '=', $this->id)->get();
foreach($reporting as $p){
$newP['reporting_sets'][$p->set_id] = $p->level;
}
return $newP;
}
public function getCyclesAttribute(){
return Cycle::orderBy('id')->get();
}
public function getFullNameAttribute(){
return $this->first_name . " " . $this->last_name;
}
public function getProfilePhotoThumbAttribute(){
if($this->profile_photo){ return "THUMB-" . $this->profile_photo; }else{ return "no-avatar.png"; }
}
public function getReportsSharedAttribute(){
return ReportingSet::where('observee_id', $this->id)->where('observee_share', 1)->where('published', 1)->without('set.modules')->get()->toArray();
}
public function canLineManage($id){
if($this->super_admin==1) return true;
foreach($this->line_managed as $lm){
if($lm->id==$id){
return true;
}
}
}
}
EDIT: If I run this code in a controller, it does't hang at all. It loads up the data in less than a second
EDIT: Restarted computer, still happening
This looks a lot like an infinitive call-loop, please refer to this on github issue
Allowed Memory size exhaused when accessing undefined index in toArray
You are just running out of memory when calling parent::toArray(). You
either need to reduce the amount of items in your collection or
increase your allowed memory allocation.

Laravel 8 Array to string conversion

I am facing a Laravel 8 array to string conversion. I am trying to get data from the units table, but I am getting the following error.
Array to string conversion
Unit Model
class Unit extends Model
{
use HasFactory;
use Uuid;
protected $keyType = 'string';
public $incrementing = false;
protected $guarded = [];
public function getKeyType()
{
return 'string';
}
public function __construct($table_name)
{
$this->setTable($table_name.'_units');
}
public function building()
{
return $this->belongsTo(Building::class);
}
}
Controller table_name_prefix is unix time 1623748840
public function show($id)
{
$building = Building::find($id);
if ($building == NULL) {
return response()->json(['message' => 'Building not exists'], 400);
}
$table_name_prefix = $building->stage->complex->table_name_prefix;
if (Schema::hasTable($table_name_prefix.'_units')) {
$units_table = new Unit($table_name_prefix);
$units_table->where('status', 'Liber')->get();
return view('buildings.show')->with('building', $building)->with('units', $units);
} else {
return view('buildings.show')->with('building', $building);
}
}
Please help me to find problem. Next code is working:
$unit = new Unit($table_name_prefix);
...
$unit->save();
The next part of the Model has the error: Array to string conversion.
$this->setTable($table_name.'_units');
Screenshot of Model error
Screenshot of Controller error

Save data into two tables at same time in laravel

I am trying to save datas into two different tables depends on selection(x-editable). Here is my code and Help me by pointing where i am doing mistake.
Result i am looking for: I change payment status pending to paid in TBL:Product, paid values also change 0 to 1 in TBL:Product_payment
TBL:Product
- product_id
- client_id
...
- status {paid/pending}
TBL:Product_payment
- product_id
- payment_id
....
- paid {1/0}
Controller:
public function update()
{
$inputs = Input::all();
if ($row = Product::with('payments')->find($inputs['pk']))
{
$row->$inputs['name'] = $inputs['value'];
if($row['status'] == 'paid') {
$row['paid'] = 1;
}
$row->save();
return $row;
}
Product.php(model)
class Product extends Eloquent
{
protected $primaryKey = 'product_id';
protected $table = 'products';
protected $fillable = array('client_id', 'date', 'amount', 'status', 'notes');
public function payments()
{
return $this->hasMany('ProductPayment');
}
}
ProductPayment.php(model)
class ProductPayment extends Eloquent
{
public $table = 'product_payments';
protected $primaryKey = 'product_payment_id';
protected $fillable = array('product_id', 'client_id', 'payment_method', 'amount_paid', 'paid');
public function product()
{
return $this->belongsTo('Products');
}
public function clients()
{
return $this->belongsTo('Clients');
}
}
Add a model event listener to boot in your AppServiceProvider for whenever an instance of Product is saved.
Product::saved(function ($product) {
$paymentStatus = [
'pending' => 0,
'paid' => 1,
];
if(array_key_exists($product->status, $paymentStatus))
{
ProductPayment::where('product_id', $product->id)
->update(['paid' => $paymentStatus[$product->status]]);
}
});

Get distinct attribute from database in Laravel

I have two tables, one called "products" and another "product_brands".
A product has one brand, and a brand can belong to many products.
I have:
class Product extends Eloquent {
protected $table = 'products';
public function type() {
return $this->hasOne('ProductTypes');
}
public function brand()
{
return $this->hasOne('ProductBrands', 'id', 'brand_id');
}
public function image() {
return $this->hasMany('ProductImages');
}
public function toArray() {
$ar = $this->attributes;
$ar['type'] = $this->type;
$ar['brand'] = $this->brand;
return $ar;
}
public function getBrandAttribute() {
$brand = $this->brand()->first();
return (isset($brand->brand) ? $brand->brand : '');
}
}
And my controller:
class ProductsController extends BaseController {
public function index($type_id) {
$Product = new Product;
$products = $Product->where('type_id', $type_id)->get();
return View::make('products.products', array('products' => $products));
}
}
Ideally I would like the column from "product_brands" to be in the same array as the columns from "products", hence why I am trying that stuff with toArray() and getBrandAttribute() but it isn't working.
How can I do this?
I'm sure the getBrandAttribute accessor collides with the brand relationship. Try this instead:
class Product extends Eloquent {
protected $table = 'products';
public function type() {
return $this->hasOne('ProductTypes');
}
public function productBrand() {
return $this->hasOne('ProductBrands', 'id', 'brand_id');
}
public function image() {
return $this->hasMany('ProductImages');
}
public function getBrandAttribute() {
$brand = $this->productBrand()->first();
return (isset($brand->brand) ? $brand->brand : '');
}
protected $appends = array('brand'); // this makes Laravel include the property in toArray
}
You should change your accessor to other name:
public function getSpecBrandAttribute() {
$brand = $this->brand()->first();
return (isset($brand->brand) ? $brand->brand : '');
}
and in toArray you should then use:
public function toArray() {
$ar = $this->attributes;
$ar['type'] = $this->type;
$ar['brand'] = $this->spec_brand;
return $ar;
}
That's because you shouldn't create fields with the same name as relationship name.
In addition as it's one to many relationship, probably for brand() you should use belongsTo and not hasOne

Categories