Laravel upgrade issue with whereHas - laravel

I recently changed versions of Laravel and I am now getting this error:
LogicException
Has method invalid on "belongsTo" relations.
Can anyone explain why I am now getting this error?
If I comment out the below three lines, no error.
Version: "laravel/framework": "4.1.7"
The piece of code in question is this:
$orderCount->whereHas('order', function($query) {
$query->whereRaw("status IN ('pending', 'prepaid')");
});
The entire controller logic here:
public function show($id) {
// the fields we want back
$fields = array('id', 'title', 'description', 'msrp', 'brand_id', 'category_id');
// how many products are in pending orders
$orders = 0;
// assume not admin must be display = 1
$display = 1;
// if logged in add more fields
if(Auth::check()) {
// add these fields to the query if dealer
array_push($fields, 'price_dealer', 'quantity');
// if admin add these fields
if (Session::get("admin")) {
$display = 0;
array_push($fields, 'cost', 'display', 'crate_quantity_threshold', 'price_crate');
}
}
$product = Product::with('images', 'brand', 'category', 'docs')
->select($fields)
->where('display', '>=', $display)
->find($id);
if(Auth::check()) {
// make orders obj
// we need to see how many orders
// there are pending for this product
$obj = new OrderItem;
$orderCount = $obj->newQuery();
$orderCount->where('product_id', '=', $id);
$orderCount->whereHas('order', function($query) {
$query->whereRaw("status IN ('pending', 'prepaid')");
});
$product->orders = $orderCount->sum('quantity') > 0 ? $orderCount->sum('quantity') : 0;
// dd(\DB::getQueryLog());
}
if ($product) {
return Response::json(array(
'product' => json_decode($product)
),
200
);
} else {
return Response::json(array(
'flash' => "Not found"
),
500
);
}
}
In Order model:
public function products()
{
return $this->belongsToMany('Product', 'order_items', 'order_id', 'product_id');
}

Short answer: Upgrade to 4.1.11+ due to:
4.1.7 - not implemented method
4.1.11 - method in place

Related

Laravel - How to update Input Array without deleting Sales Detail

In my Laravel-8 project, I have this controller for Input Field Array Update.
Controller:
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
SaleDetail::where('sale_id', $sale->id)->delete();
foreach ($data['invoiceItems'] as $item) {
$details = [
'sale_id' => $sale->id,
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$saleDetail = new SaleDetail($details );
$saleDetail->save();
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
In the form, the user can add more Sales Detail or remove.
Some of the SaleDetail fields are being used somewhere else.
Is there a way to update the input field array without deleting the SaleDetail as shown in what I did here:
SaleDetail::where('sale_id', $sale->id)->delete();
Thanks
I've tried to restructure your code so that's easier to edit. I've left some comments. I can really recommend refactoring.guru. There you will find many ways to improve your code so that it is more extensible, maintainable and testable. If you have any questions, please feel free to ask.
class Sale extends Model
{
// Use a relationship instead of building your own query
public function details() {
return $this->hasMany(SaleDetail::class);
}
}
class SaleDetail extends Model
{
// Use a computed property instead of manually calculating total price
// You can access it with $saleDetail->totalPrice
public function getTotalPriceAttribute() {
return $this->price * $this->quantity;
}
}
class UpdateSaleRequest extends Request
{
public function authorize() {
return true;
}
protected function prepareForValidation() {
$this->merge([
// Create a Carbon instance by string
'date' => Carbon::make($this->date)
]);
}
public function rules() {
// Your validation rules
// Please also validate your invoice items!
// See https://laravel.com/docs/8.x/validation#validating-arrays
}
}
// We let Laravel solve the sale by dependency injection
// You have to rename the variable name in ihr web.php
public function update(UpdateSaleRequest $request, Sale $sale)
{
// At this point, all inputs are validated!
// See https://laravel.com/docs/8.x/validation#creating-form-requests
$sale->update($request->validated());
// Please ensure, that all properties have the same name
// In your current implementation you have price = cost, be consistent!
foreach($request->input('invoiceItems') as $invoiceItem) {
// How we can consider that a detail is already created?
// I assume that each item_id will only occur once, otherwise you'll
// place the id of each detail in your update form (e.g. in a hidden input)
$candidate = $sale->details()
->where('item_id', $properties['item_id'])
->first();
if($candidate) {
$candidate->update($properties);
} else {
$sale->details()->create($properties);
}
}
// A JWT-Exception should not be necessary, since your authentication
// will be handled by a middleware.
return response()->json($sale);
}
I have not tested the code, few adjustments may be needed.
Laravel has a method called updateOrCreate as follow
/**
* Create or update a record matching the attributes, and fill it with values.
*
* #param array $attributes
* #param array $values
* #return \Illuminate\Database\Eloquent\Model|static
*/
public function updateOrCreate(array $attributes, array $values = [])
{
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
$instance->fill($values)->save();
});
}
That means you could do some thing like
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
foreach ($data['invoiceItems'] as $item) {
$details = [
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$sale->saleDetail()->updateOrCreate([
'sale_id' => $sale->id
], $details);
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
I would encourage you to refactor and clean up your code.You can also read more about it here https://laravel.com/docs/8.x/eloquent#upserts

Laravel / OctoberCMS frontend filter

I am using OctoberCMS and I have created a custom component. I am trying to create a frontend filter to filter Packages by the Tour they are assigned to.
This is what I have so far. The issue is that the code is looking for a tour field within the packages table rather than using the tour relationship. Does anyone have any ideas?
<?php namespace Jakefeeley\Sghsportingevents\Components;
use Cms\Classes\ComponentBase;
use JakeFeeley\SghSportingEvents\Models\Package;
use Illuminate\Support\Facades\Input;
class FilterPackages extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'Filter Packages',
'description' => 'Displays filters for packages'
];
}
public function onRun() {
$this->packages = $this->filterPackages();
}
protected function filterPackages() {
$tour = Input::get('tour');
$query = Package::all();
if($tour){
$query = Package::where('tour', '=', $tour)->get();
}
return $query;
}
public $packages;
}
I really appreciate any help you can provide.
Try to query the relationship when the filter input is provided.
This is one way to do it;
public $packages;
protected $tourCode;
public function init()
{
$this->tourCode = trim(post('tour', '')); // or input()
$this->packages = $this->loadPackages();
}
private function loadPackages()
{
$query = PackagesModel::query();
// Run your query only when the input 'tour' is present.
// This assumes the 'tours' db table has a column named 'code'
$query->when(!empty($this->tourCode), function ($q){
return $q->whereHas('tour', function ($qq) {
$qq->whereCode($this->tourCode);
});
});
return $query->get();
}
If you need to support pagination, sorting and any additional filters you can just add their properties like above. e.g;
protected $sortOrder;
public function defineProperties(): array
{
return [
'sortOrder' => [
'title' => 'Sort by',
'type' => 'dropdown',
'default' => 'id asc',
'options' => [...], // allowed sorting options
],
];
}
public function init()
{
$filters = (array) post();
$this->tourCode = isset($filters['tour']) ? trim($filters['tour']) : '';
$this->sortOrder = isset($filters['sortOrder']) ? $filters['sortOrder'] : $this->property('sortOrder');
$this->packages = $this->loadPackages();
}
If you have a more complex situation like ajax filter forms or dynamic partials then you can organize it in a way to load the records on demand vs on every request.e.g;
public function onRun()
{
$this->packages = $this->loadPackages();
}
public function onFilter()
{
if (request()->ajax()) {
try {
return [
"#target-container" => $this->renderPartial("#packages",
[
'packages' => $this->loadPackages()
]
),
];
} catch (Exception $ex) {
throw $ex;
}
}
return false;
}
// call component-name::onFilter from your partials..
You are looking for the whereHas method. You can find about here in the docs. I am not sure what your input is getting. This will also return a collection and not singular record. Use ->first() instead of ->get() if you are only expecting one result.
$package = Package::whereHas('tour', function ($query) {
$query->where('id', $tour);
})->get();

how to filter with two or more combinations in laravel

enter code hereMy question about the combination filters in laravel by using eloquent.
I am trying to filter with a combination of the following:
username
Category
Sub_category
started_at
created_at
status
I use where conditions but it not working as required.
public function filter(Request $request, User $user)
{
$user = $user->newQuery();
// Search for a user based on their name.
if ($request->has('username')) {
$user->where('name', $request->input('username'));
}
// Search for a user based on their Category.
if ($request->has('Category')) {
$user->where('Category', $request->input('Category'));
}
// Search for a user based on their Sub_category.
if ($request->has('Sub_category')) {
$user->where('Sub_category', $request->input('Sub_category'));
}
// Search for a user based on their started_at.
if ($request->has('started_at')) {
$user->where('started_at', $request->input('started_at'));
}
// Search for a user based on their status.
if ($request->has('status')) {
$user->where('status', $request->input('status'));
}
// Continue for all of the filters.
// Get the results and return them.
return $user->get();
}
You should save your where conditions to the $user variable.
$user = $user->where($dbField, $request->input($requestParam));
For improved readability, I'd suggest using a loop with all of your filtering cases.
public function filter(Request $request)
{
$users = User::query();
$filters = [
'username' => 'name',
'Category' => 'Category',
'Sub_category' => 'Sub_category',
'started_at' => 'started_at',
'status' => 'status'
];
foreach ($filters as $requestParam => $dbField){
if ($request->has($requestParam)) {
$users = $users->where($dbField, $request->input($requestParam));
}
}
return $users->get();
}
Bear in mind $request->has does not check whether the parameter value is empty, use $request->filled if you wish so.
This is My examle refer this
public function filter(Request $request)
{
$q = User::query();
$email = $request->input('email');
$username= $request->input('username');
$q->when($email,function ($query) use ($email){
$query->where('email',$email);
});
$q->when($username,function ($query) use ($username){
$query->where('username',$username);
});
$results = $q->get();
//code
}

Laravel ORM relationship returns error when value not present in DB

I have an issue with querying relationships.
I am querying relations between Projects, Companies and Products. However, whenever a Project ID is not present in the database an fatal exception is trown:
Call to a member function companies() on a non-object
public function index($i) {
return $this->returnTop($i, Array(
'projectid' => 5,
'products' => Array(1, 2, 3)
)
);
}
public function returnTop($count = 6, $args = Array()) {
$companies = Project::find($args['projectid'])->companies()->whereHas('products', function($q) use($args) {
$q->whereIn('products.id', $args['products']);
})->with('products')->limit($count)->get();
return Response::json($companies);
}
Now, I know that project id 5 is not present in the DB, and this is likely to be the cause of this error, but I want to return a message instead of the application throwing a fatal error....
Any ideas?
Just check if find() returns null. Something like this:
$project = Project::find($args['projectid']);
if(is_null($project)){
return Response::json(['message' => 'Project not found']);
}
$companies = $project->companies()->whereHas('products', function($q) use($args) {
$q->whereIn('products.id', $args['products']);
})->with('products')->limit($count)->get();
return Response::json($companies);
An alternative would be findOrFail which throws a ModelNotFoundException. You could handle the exception globally or catch it inside the controller:
try {
$companies = Project::findOrFail($args['projectid'])->companies()->whereHas('products', function($q) use($args) {
$q->whereIn('products.id', $args['products']);
})->with('products')->limit($count)->get();
return Response::json($companies);
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e){
return Response::json(['message' => 'Project not found']);
}
You first have to test whether the returned object is actually not null. Blindly assuming a database query succeeds is waiting for sh*t to hit the fan.
public function returnTop($count = 6, $args = Array()) {
$project = Project::find($args['projectid']);
if($project) {
$companies = $project->companies()->whereHas('products', function($q) use($args) {
$q->whereIn('products.id', $args['products']);
})->with('products')->limit($count)->get();
return Response::json($companies);
}
else {
return; // .. your error or whatever
}
}
Also the "call to a member function on a non-object" is quite specific, it tells you that a method (member function) could not be called due to the fact that you are trying to call it on a non-object.

Codeigniter - Trying to count articles view

Im working with codeigniter and have a controller and 3 model functions to read, insert and update a column of table.
i want to get count of view from db and update it per each view!
but after the update an extra row added to table! with zero value for content_id.
please check once!
this is my controller:
public function daily_report($id="")
{
$counter=$this->home_model->counter($id);
if($counter)
{
$view=$this->home_model->counter($id)->row()->view;
$views=$view+1;
$this->home_model->update_counter($views,$id);
}
else{
$views=1;
$this->home_model->set_counter($views,$id);
}
}
This is the model functions:
public function counter($id)
{
$code=$id;
$lang=$this->session->userdata('lang');
$data = $this->db
->select('view')
->from('tbl_views')
->where("content_id",$code)
->where("language",$lang)
->get();
if ($data->num_rows() > 0) {
return $data;
}else{
return false;
}
}
public function set_counter($views,$id)
{
$data = array(
'content_id' => $id ,
'view' => $views,
'language'=>$this->session->userdata('lang')
);
$this->db->insert('tbl_views', $data);
}
public function update_counter($views,$id)
{
$data = array(
'view' => $views,
);
$this->db->where('content_id', $id);
$this->db->update('tbl_views', $data);
}

Resources