Run an extra where clause in the model - laravel

Is it possible to specify a where clause for the model to run with each select?
Example:
I have a query $var = TestModal::where('date', '2021-06-08')->get(); and I want my TestModal to run ->where('type', 'r') for every query so I don't have to write it multiple times

You could use an Anonymous Global Scope to define the query for each Query running on the Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class TestModal extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::addGlobalScope('type', function (Builder $builder) {
$builder->where('type', 'r');
});
}
}

yes you can use multiple where like this
var = TestModal->where(['date' => '2021-06-08', 'type' => '' ])->get();
or
var = TestModal->where('date' , '2021-06-08' )->where('type' , '')->get();
or can your addGlobalScope
protected static function booted()
{
static::addGlobalScope('table_name', function (Builder $builder) {
$builder->where('type', ');
});
}

Related

Laravel Mystery - Two Similar Item Types Producing 2 Different Query Strings in Same Use Case

Ok, this is weird... You ready?
I have an item type on my site, lets call it SomeItem
It can have tags associated with it via a one-to-many relationship.
The sorts of queries that Laravel builds when dealing with tags for SomeItem are like this, for instance in response to route api/someitem/10:
select `tags`.*, `someitem_tag`.`someitem_id` as `pivot_someitem_id`, `someitem_tag`.`tag_id` as `pivot_tag_id` from `tags` inner join `someitem_tag` on `tags`.`id` = `someitem_tag`.`tag_id` where `someitem_tag`.`someitem_id` in (10)
When I create a second Item with identical settings - let's call it AnotherItems - it treats the database query for extracting tags in a different manner, using a different syntax in the queries. Extremely weird.
(and yes, I have an s at the end of the model name...)
For instance, this route api/anotheritems/1
produces this error:
Base table or view not found: 1146 Table 'mysite.tag_anotheritems' doesn't exist (SQL: select `tags`.*, `tag_anotheritems`.`anotheritems_id` as `pivot_anotheritems_id`, `tag_anotheritems`.`tag_id` as `pivot_tag_id` from `tags` inner join `tag_anotheritems` on `tags`.`id` = `tag_anotheritems`.`tag_id` where `tag_anotheritems`.`anotheritems_id` in (1))
See what is happening? Of course I am getting this error - in the database this tag table for AnotherItems is created as anotheritems_tag. That is analogous to SomeItem.
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item??? WTF?
First let me show you how SomeItem is set up.
Here is the database structure related to Tags:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSomeItemTagTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('someitem_tag', function (Blueprint $table) {
$table->integer('tag_id')->unsigned();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->integer('someitem_id')->unsigned();
$table->foreign('someitem_id')->references('id')->on('someitems')->onDelete('cascade');
$table->primary(array('tag_id', 'someitem_id'));
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('someitem_tag');
}
}
There is a Tags model/class that has this:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
protected $fillable = ['name'];
protected $hidden = [];
public $timestamps = false;
public function someitems()
{
return $this->belongsToMany(SomeItem::class);
}
}
And here is some relevant lines for SomeItem model/class:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use App\Presenters\Presentable;
use Illuminate\Notifications\Notifiable;
use Auth;
class Exercise extends Model
implements Presentable
{
use Traits\SerializesUniversalDate;
use Traits\Presents;
use Notifiable;
protected $presenter = 'App\Presenters\SomeItemPresenter';
protected $fillable = ['title', etc];
protected $hidden = [];
public function parentitem()
{
return $this->belongsTo(ParentItem::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
/**
* Update lesson tag array.
*
* #param array \App\Tag $tags
* #return void
*/
public function updateTags($tagsArray)
{
foreach ($tagsArray as &$value)
{
$tag = Tag::where('name', $value['name'])->first();
if (is_null($tag))
{
$tag = new Tag([
'name' => $value['name']
]);
$tag->save();
}
if (!$this->tags->contains($tag->id))
{
$this->tags()->attach($tag->id);
}
}
foreach($this->tags as &$existingTag)
{
if (!self::arrayContains($tagsArray, 'name', $existingTag->name))
{
$this->tags()->detach($existingTag->id);
}
}
$this->load('tags');
}
private static function arrayContains($array, $key, $value)
{
foreach ($array as $item)
{
if($item[$key] == $value) return true;
}
return false;
}
}
And here is some relevant code for SomeItem API controller:
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Input;
class SomeItemController extends Controller
{
public function index(Request $request)
{
$query = \App\SomeItem::query();
return $query->get()->load('parentitem')->load('tags');
}
//show item for editing
public function show($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->load('parentitem')->load('tags');
$someitem->attachKindToFiles();
return $someitem;
}
//store new entry to db
public function store()
{
$someitem = \App\SomeItem::create(Input::all());
isset(Input::all()['tags']) ? $someitem->updateTags(Input::all()['tags']) : '';
return $someitem;
}
//update/save
public function update($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->update(Input::all());
$someitem->updateTags(Input::all()['tags']);
$someitem->load('tags');
return $someitem;
}
There is also a SomeItem presenter and composer but they don't do anything with tags.
With AnotherItems, I literally I duplicated everything from SomeItem and just changed names as needed.
So in the Tag model there is
public function anotheritems()
{
return $this->belongsToMany(AnotherItems::class);
}
In AnotherItems model there is this, for instance
public function tags()
{
return $this->belongsToMany(Tag::class);
}
In the AnotherItems API controller there is this, for instance (which is for route api/anotheritems/1):
public function index(Request $request)
{
$query = \App\AnotherItems::query();
if ($request->has('id')) {
$query->where('id', $request['id']);
}
return $query->get()->load('parentitem')->load('tags');
}
So, this is a total mystery. I have been trying to figure this out for 2 days now. And I continue asking myself
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item???
I upgraded from laravel 5.2 to 5.3 and it is after the upgrade that I added this AnotherItems. But I can't figure out how that could possibly alter things in terms of these database queries.
I have tried a ton of artisan commands for clearing everything imaginable, but somewhere in the framework it wants to handle SomeItem and AnotherItems differently when building these join queries to extract/save tags.
Thoughts?
thanks,
Brian
Decided to step through code in debugger. Seems things are breaking down in Str.php in various snake related function, and I also noticed a snakeCache call, whatever the heck that is. Not sure why such a strange methodology to determine table names... Also in these functions there is some pluralizing related checks, so maybe this is related to me using an s at the end of my item name. Pretty messed up stuff if an s at the end of a model name can cause two different logic branches...

hasMany relationships return Undefined property

I like to list all MovimentoProdutoUnidade that movimento_id = 3 using the hasMany function.
My Model Movimento:
use Illuminate\Database\Eloquent\Model;
use App\Unidade;
class Movimento extends Model
{
protected $fillable = [
"movimento", "descricao", "requisitante", "despachante", "data", "unidade_ori_id", "unidade_des_id"
];
protected $table = "movimentos";
public function movimentoProdutoUnidade(){
return $this->hasMany('App\MovimentoProdutoUnidade', 'movimento_id');
}
}
My Model MovimentoProdutoUnidade
use Illuminate\Database\Eloquent\Model;
use App\Movimento;
class MovimentoProdutoUnidade extends Model
{
protected $fillable = [
"movimento_id", "unidadeProduto_id"
];
protected $table = "movimento_produtounidades";
public function movimento(){
return $this->belongsTo('App\Movimento', 'movimento_id');
}
}
My Controller:
public function licitacao(Request $request){
$movimentos = Movimento::where('unidade_ori_id', 3)->movimentoProdutoUnidade;
dd($movimentos);
//return view('relatorios.licitacao', compact('movimentos'));
}
The dd fuction return
Undefined property: Illuminate\Database\Eloquent\Builder::$movimentoProdutoUnidade
Your error is because you're not calling first() on the query builder object, so you have an instance of Builder (which does not have a $movimentoProdutoUnidade property) instead of a Movimento model:
$movimento = Movimento::where('unidade_ori_id', 3)->first();
$movimento_produto_unidade = $movimento->movimentoProdutoUnidade;
However, if you want all MovimentoProdutoUnidade, try thinking "backwards":
$movimento_produto_unidade = MovimentoProdutoUnidade::whereHas('movimento', function ($query) {
return $query->where('unidade_ori_id', 3);
})
->get();
As stated in the comment i made, try using first function like this:
Movimento::where('unidade_ori_id', 3)->first()->movimentoProdutoUnidade;
Remember always after the condition use get(), first() or find() functions to pull the data from the database.
Take a look to this link

Get onlyTrashed() does not exist in query builder

I am trying to get trashed rows from table messages:
public function trash() {
return $this->onlyTrashed()
->where('user_id', '=', $this->_u)
->orWhere('receiver', '=', $this->_u)
->orderBy('deleted_at', 'desc')->get();
}
I get this error:
Method Illuminate\Database\Query\Builder::onlyTrashed does not exist.
I checked up Builder and SoftDeletes files for onlyTrashed method and it does not exist, how can I look up to trashed messages from message table?
The only way I think about is to create method that doesn't return messages where delete_at is not null and for trashed to return only those where it is not null. But I am still wondering why this doesn't work since it is in documentation at this url:
https://laravel.com/docs/5.6/eloquent#soft-deleting
MORE INFO
Yes it is inside model and yes I added use SoftDeletes:
use Illuminate\Database\Eloquent\SoftDeletes; - on top
use SoftDeletes; after opening the class
Let me paste entire model here:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
class Messages extends Model
{
use SoftDeletes;
protected $fillable = [
'user_id', 'subject', 'text', 'receiver'
];
public $_u;
protected $dates = ['deleted_at'];
public function __construct() {
$this->_u = auth()->user()->user_id; //or some id as string
}
public function trash() {
return $this->onlyTrashed()
->where('user_id', '=', $this->_u)
->orWhere('receiver', '=', $this->_u)
->orderBy('deleted_at', 'desc')->get();
}
public static function trashed() {
return self::onlyTrashed();
}
}
And controller has:
public function __construct() {
$this->middleware('auth');
}
public function index($field = 'trash') {
if ($field !== "new") {
$messages = (new Msg)->$field();
$user = auth()->user();
return view('pages.messages', compact('messages', 'user'));
}
return view('pages.messages.new', compact('messages', 'user'));
}
I tried calling static as well and I tried doing it from tinker and still keep getting:
onlyTrashed() doesn not exist
You have to call the parent constructor:
public function __construct() {
parent::__construct();
$this->_u = auth()->user()->user_id;
}
I think what you want is to define the trash method static:
public static function trash() {
return self::onlyTrashed()
->where('user_id', '=', $this->_u)
->orWhere('receiver', '=', $this->_u)
->orderBy('deleted_at', 'desc')->get();
}
Then call this function by:
$messages = Messages::trash();
This should work
# YourModelController.php
/**
* Show only trashed
*
* #return \Illuminate\Http\Response
*/
public function trashed()
{
...
$trashed = YourModel::onlyTrashed()->get();
...
}
I have researched a little further and I got this:
From https://laravel.com/api/5.6/Illuminate/Database/Eloquent.html
I should have
SoftDeletesTrait
but I have
SoftDeletes
. In softdeletestrait we have onlyTrashed method but in SoftDeletes we do not.
So I copied that method from this page:
https://github.com/laravel/framework/blob/7d9e7068c49f945385673014d4cba4de28accd5e/src/Illuminate/Database/Eloquent/SoftDeletingTrait.php#L119
And added it to SoftDeletes class, now it works like it should. I haven't find why it doesn't exist inside SoftDeletes class so if anyone find out let us know!

Laravel trait function not found

I have look all over stackoverflow and google and I cannot seem to solve my trait function not found. I have tried composer dump-autoload, my composer.json have the app directory connected and even checked my namespace and trait names. Here is my user controller.
<?php
namespace App\Http\Controllers;
use App\User;
use App\Traits\ControllerTrait;
use App\Http\Requests\UpdateUser;
use Illuminate\Http\Request;
use App\Http\Requests\IndexUser;
class UserController extends Controller
{
use ControllerTrait;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show multiple users.
*
* #return \Illuminate\Http\Response
*/
public function index(IndexUser $request)
{
$per_page = 50;
$order_by = 'id';
$sort_by = 'ASC';
if($request->has('per_page')) {
$per_page = $request->input('per_page');
}
if($request->has('order_by')) {
$order_by = $request->input('order_by');
}
if($request->has('sort_by')) {
$sort_by = $request->input('sort_by');
}
$users = User::when($request->has('select'), function ($query) use ($request) {
selectPrepare($query, $request->input('select'));
})->when($request->has('include'), function ($query) use ($request) {
if(!empty($request->input('include'))) {
$includedTables = explode(',', $request->input('include'));
$tables = array_map('trim', $includedTables);
return $query->with($tables);
}
return $query;
})->orderBy("{$order_by}", "{$sort_by}")
->paginate($per_page);
return response()->json($users);
}
}
}
Here is my Trait
<?php
namespace App\Traits;
trait ControllerTrait
{
/**
* Function: scopeSelectPrepare
public function selectPrepare($query, $select) {
if(!empty($select)) {
$selectedColumns = explode(',', $select);
$columns = array_map('trim', $selectedColumns);
return $query->select($columns);
}
return $query;
}
}
As you can see my name space for the Trait is App\Traits and call the use App\Traits\ControllerTrait in my controller then can the use ControllerTrait to get the functions from the trait. When I try to get the function to use in my query it says: Call to undefined function App\Http\Controllers\selectPrepare()
Is there something I am missing? I am new to the traits functionality of laravel but I thought I was following all of the examples and naming conventions. Can anyone see what I am doing wrong.
You need to use $this when accessing trait methods, just like you would for any other method:
$users = User::when($request->has('select'), function ($query) use ($request) {
$this->selectPrepare($query, $request->input('select'));

Laravel: override eloquent function for get results?

I can override function before save :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
But which method I can use for get result ? and where is documentation for this. I am looking for something like :
public function get()
{
parent::get();
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
}
}
With that I can convert 10 date rows without need of 20 mutators..
It seems that Attribute casting fits your needs or use Date mutators
You may customize which fields are automatically mutated, and even completely disable this mutation, by overriding the $dates property of your model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
// more dates
];
}
EDIT
Another way, you can override getAttribute method in Model
<?php
namespace App;
use Carbon\Carbon;
trait DateFormatting
{
protected function dateFields()
{
return [];
}
public function getAttribute($key)
{
if ( array_key_exists( $key, $this->dateFields() ) ) {
return Carbon::createFromFormat('d/m/Y', $this->attributes[$key])->format('Y-m-d');
}
return parent::getAttribute($key);
}
}
then you can use this trait in any your model, just don't forget override dateFields in it
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\DateFormatting;
class User extends Model
{
use DateFormatting;
protected function dateFields()
{
return [
'finished_at',
// other field names that you need to format
];
}
after all you can access to this fields as usual(using magic __get())
$model->finished_at;
I find a solution, My solution is :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = \Carbon\Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
public function getAttribute($key)
{
$value = parent::getAttribute($key);
if(isset($this->attributes[$key])){
if(isset($this->datesConvert) && in_array($key, $this->datesConvert)){
$value = \Carbon\Carbon::createFromFormat('Y-m-d', $value)->format('d/m/Y');
}
}
return $value;
}
Laravel comes with something very useful for this problem. I'm not sure what it's called, but you can modify attributes or even add new attributes like this:
class YourModel extends Model
{
...
public function getDateAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
...
}
You can retrieve this attribute like:
$yourModel->date;
Edit after comment #fico7489
You can't ignore the fact you always have to modify things. However there are still some solutions to make it easier.
For example you can change your date column to a string and just store your date with the desired date format.
Other solution should be modifying the date through sql. FORMAT(Now(),'YYYY-MM-DD').
Example in laravel would look like (not tested):
YourModel::select([
'*',
DB::raw('
FORMAT(yourDateColumn,'YYYY-MM-DD')
')
])->get();

Resources