Yii2 ActiveQuery disambiguation - yii2

I have a policy table and a policy_chapter table. I want to modify my PolicyQuery so that it will only display policies from chapters that belong to that client. Here's my code inside PolicyQuery which extends ActiveQuery:
public function one($db = null)
{
if (Yii::$app->user->can('Admin')) return parent::one($db);
if (Yii::$app->user->can('PolicyUser')) {
$this->joinWith('chapter')->andWhere(['policy_chapter.client_id'=>Yii::$app->user->identity->client_id]);
return parent::one($db);
}
}
If the user has Admin access then he can look at any policy. If he has PolicyUser access, then it will make sure he can't get to a policy in another client's chapters. But I get this error:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in where clause is ambiguous The SQL being executed was: SELECT policy.* FROM policy LEFT JOIN policy_chapter ON policy.chapter_id = policy_chapter.id WHERE (id='280') AND (policy_chapter.client_id=1)
It fails because it is search on id and both the policy and the policy_chapter table have an id field. I cannot figure out how to disambiguate the id.

I solved this by changing the findModel method in the policy controller. I replaced the Policy::findOne($id) call with Policy::find()->where('policy.id = :id',[':id'=>$id])->one()
That disambiguated the policy id.

modify your query to this:
public function one($db = null)
{
if (Yii::$app->user->can('Admin')) return parent::one($db);
if (Yii::$app->user->can('PolicyUser')) {
$this->joinWith('chapter as cha')->alias('t')->andWhere(['policy_chapter.client_id'=>Yii::$app->user->identity->client_id]);
return parent::one($db);
}
}
then you can use cha.id reference to policy_chapter table and t.id reference to policy table.

Try with:
public function one($db = null)
{
if (Yii::$app->user->can('Admin')) return parent::one($db);
if (Yii::$app->user->can('PolicyUser')) {
$sql = $this->joinWith('chapter')->andWhere(['policy_chapter.client_id'=>Yii::$app->user->identity->client_id])->andWhere(['policy.id' => $this->id])->createCommand()->getRawSql();
$model = Policy::findBySql($sql)->one();
return $model;
}
}

Related

Can't delete a collection of Laravel eloquent

I am retrieving a collection from db and want to delete it. This is the code.
$signal_id = $request->del_signal_id;
$signal_details = VwDistressSignals::where('signal_id', $signal_id)->delete();
return "Success";
}
And this is the error.
message: "SQLSTATE[HY000]: General error: 1395 Can not delete from join view 'dvp.vw_distresssignals' (SQL: delete from `vw_distresssignals` where `signal_id` = 2)"
I have also tried giving all the column names. This is the model...
<?php
namespace App\Models;
use Reliese\Database\Eloquent\Model as Eloquent;
class VwDistressSignals extends Eloquent
{
protected $table = 'vw_distresssignals';
}
This error is happening because your relationship is not properly defined.
Delete with the loop
$signal_id = $request->del_signal_id;
$signal_details = VwDistressSignals::where('signal_id', $signal_id)->get();
foreach($signal_details as $detail)
{
$detail->delete();
}
return "Success";
Since you haven't shared your model, I assume it corresponds to a database view rather than a table.
If so try deleting from the base table(s) as join views are not deletable.
DELETE ... Join views are not allowed.
https://dev.mysql.com/doc/refman/5.7/en/view-updatability.html

How can I know if a table column is a foreign key in Laravel through Model?

So as the title says, how can I know if a field of a Model is a foreign key in Laravel ?
Supose I have a FK column called show_type_id and a model named Event and I want to know if there is a function that given the model class or model table and the named field returns true if it is or false if is not.
...
$model = Event:class; // or Event::getTable();
$isFK = isFK('show_type_id', $model);
...
Edit
Thanks to #piscator this is what is worked:
use Illuminate\Support\Facades\Schema;
function isFK(string $table, string $column): bool
{
$fkColumns = Schema::getConnection()
->getDoctrineSchemaManager()
->listTableForeignKeys($table);
$fkColumns = collect($fkColumns);
return $fkColumns->map->getColumns()->flatten()->search($column) !== FALSE;
}
Try this, assuming your table name is "events":
Schema::getConnection()
->getDoctrineSchemaManager()
->listTableForeignKeys('events')
This will return the Doctrine\DBAL\Schema\ForeignKeyConstraint object.
With this data you could write the isFK method like this:
use Illuminate\Support\Facades\Schema;
function isFK(string $table, string $column): bool
{
$fkColumns = Schema::getConnection()
->getDoctrineSchemaManager()
->listTableForeignKeys($table);
return collect($fkColumns)->map(function ($fkColumn) {
return $fkColumn->getColumns();
})->flatten()->contains($column);
}

Laravel Join Query returning empty column

I have a member table with some entries. Each member can create a user account in laravel's users table.
They each have a field called "person_id" and that's how the connection is made.
I have a search that returns a list with all of them. I have a checkbox "Search only registered" that means it returns only members that have users account, otherwise if the check doesn't check, return a mix with all of them.
The thing is, no matter if the checkbox is checked or not, the person_id must be pulled for each one.
if($reg == 'on') {
$Members = $Members->rightJoin('users', 'users.person_id', '=', 'members.person_id');
}
else {
$Members = $Members->leftJoin('users', 'users.person_id', '=', 'members.person_id');
}
I tried with leftJoin but person_id comes empty
at first look if you are using Eloquent i can tell you are missing the "->get();" at the end of each query.
Hope this helps.
Use relation in member model:
public function user()
{
return $this->belongsTo('App\User', 'person_id', 'person_id' );
}
public function getMemberWithUser()
{
return $this->select('*')->with('user')->get()->toArray();
}
and use (new Member)->getMemberWithUser(); in controller. That will return you member detail with user.
Neverming guys I found it out.
Most members don't have yet a user account, only 2. And the select wasn't specifying which table to take the person_id from. And with most members missing an user account, it was trying to get it from users.
I did this:
$Participants = Member::select(
'members.first_name',
'members.last_name',
'members.person_id',
'members.email'
);

Laravel 5.6 - eager load depending on value in parent table

Post.php
$fillable = ['id','flag'];
public function tags()
{
return $this->belongsToMany('App\Tags')->withPivot('pivot_flag');
}
public function flaggedTags()
{
return $this->tags()->where('pivot_flag', 1)->get();
}
Tag.php
$fillable = ['id']
public function posts()
{
return $this->belongsToMany('App\Post');
}
Pivot table post_tag columns: post_id,tag_id,pivot_flag
I need to fetch Post with Tags:
$post = Post::select('id','flag')->with('tags')->find($postId);
However, if the flag is set to 1 in Post then I only want tags which have pivot_flag value in pivot table post_tag set to 1
So in that case I'd have following:
$post = Post::select('id','flag')->with('flaggedTags')->find($postId);
How can I do this in single query? I could always do one query to check if the Post is flagged or not and then run the appropriate query for Tags, but that seems wasteful.
Eloquent or raw MySQL queries are welcome.
Thanks!
UPDATE - Alternative solution
Load all tags and then filter the result depending on Post's flag value:
if($post->flag)
{
$tags = $post->tags->filter(funtction($q) {
return $q->pivot->pivot_flag == 1;
});
}
else
{
$tags = $post->tags;
}
This might be faster depending on number of tags. Not really sure, need to test it though. Any feedback about this in comments is welcome.

Eloquent Specify Relation Columns of belongsToMany relationship

I have tried all kinds of methods of limiting the columns which are returned in my many-to-many relationship, and none seem to work.
Background - Not really necessary, but to give the big picture
Essentially, in my app, I want to build a list of contacts for the currently logged in user. Administrator and Billing users should be able to contact everybody including users of group Customer.
Customer should only be able to contact Administrator and Billing.
So my way to tackle this is firstly to determine the groups that the user is in.
$userGroups = Sentry::getUser()->getGroups()->lists('name', 'id');
Then iterate over the groups, to see if the user is in the group Administrator or Billing or Customer and build the contact groups for that user.
foreach($userGroups as $group)
{
if ($group === 'Administrator' || $group === 'Billing')
{
$contactGroups = \Group::with('users')->get(['id', 'name']);
}
else if ($group === 'Customer')
{
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with('users')
->get(['id', 'name']);
}
else
{
return Response::json('No Contacts found', 404);
}
}
The problem - It appears that I am unable to select specific columns to select on belongsToMany relations.
I have tried:
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with(['users', function($q){
$q->select('id', 'first_name', 'last_name');
}])
->get(['id', 'name']);
I have also tried limiting the select within the Group model
class Group extends Eloquent
{
protected $table = 'groups';
public function users()
{
return $this->belongsToMany('User', 'users_groups')
->select('id', 'first_name', 'last_name', 'email', 'telephone');
}
}
Either way, the query runs, but it returns the entire user object and completely ignores my selects.
As such, when I return a json response, everything that I do not want is included.
So what I have done as a temporary fix is iterate over each of the users in each of the groups, and unset all the attributes which I do not want.
foreach ($contactGroups as $group)
{
foreach($group->users as $user)
{
unset($user->persist_code);
unset($user->created_at);
unset($user->updated_at);
unset($user->deleted_at);
unset($user->last_login);
unset($user->permissions);
unset($user->activated_at);
unset($user->activated);
unset($user->reset_password_code);
unset($user->pivot);
}
}
return Response::json($contactGroups, 200);
This is really clunky, inefficient and seems like a waste of time. Is there a better way of achieving the above?
For some reason selecting specific columns with belongsToMany is not working.
But i have found an alternate solution.
There is a provision in laravel, Converting to Arrays or json that allows you to whitelist/blacklist specific columns when using toArray() or toJson.
To prevent specific fields from appearing in the relation :
class User extends Eloquent{
protected $hidden = array("persist_code","created_at","updated_at","deleted_at","last_login");
}
Instead if you wish to allow specific fields :
protected $visible = array("Visibile fields");
try this
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with(['users', function($q){
$q->get(['id', 'first_name', 'last_name']);
}])
->get(['id', 'name']);
or this
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with(['users:id,first_name,last_name'])
->get(['id', 'name']);