How can I unscope a model on create in laravel - laravel

I have a Has and Belongs To Many relationship between leads and statuses. A Lead is a prospective customer. The statuses can be active, on hold, unsubscribed, etc. I have the following tables:
| Leads | Lead_Status | Statuses |
|---------|--------------|----------|
| - id | - lead_id | - id |
| - name | - status_id | - name |
We have multiple statuses for each lead because we need to keep track of the status history for analytics. This is all working great. It is common to apply a global scope as opposed to query scope to a model if the scope is used in most of the queries. My scope does something ugly to retrieve the current status of the lead and make it easily accessible with something like $lead->status that same way we get $lead->name. Here is the scoping process using newQuery to add query info to the initial query when getting the Lead:::
public function newQuery() {
return parent::newQuery()->select(DB::raw('leads.*, status.status_id AS status'))->from(DB::raw(' leads
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
SELECT lead_id, max(created_at) AS max_created_at
FROM lead_status GROUP BY lead_id
) AS ls2 INNER JOIN lead_status AS ls ON ls.lead_id = ls2.lead_id AND ls.created_at = ls2.max_created_at
) AS status ON leads.id = status.lead_id
'));
}
This works really well. It returns the lead with id, name and status.
Now for the problem:
When I try to create a new Lead I get an error:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near
'
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
' at line 2
(SQL:
insert into leads
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
SELECT lead_id, max(created_at) AS max_created_at
FROM lead_status GROUP BY lead_id
) AS ls2
INNER JOIN lead_status AS ls ON ls.lead_id = ls2.lead_id
AND ls.created_at = ls2.max_created_at
) AS status ON leads.id = status.lead_id
(`id`, `name`, `updated_at`, `created_at`) values (test5, Dustin Griffith, 2014-12-03 18:24:06, 2014-12-03 18:24:06)
)
It is obvious that the Lead is being scoped on Lead::create(). This is not what I want. I want it to scope only when we are selecting data from the leads table. So my main question is how do I tell this to only scope when it is a select statement?
I have also used the ScopeInterface method seen in the laravel docs under global scope which is more complex but favored in some situations. I can add that if we should be building off of that instead of the older newQuery method. I had the same results with that method.
I am not looking for a hacky solution. Any input will be greatly appreciated but I am searching for the correct way to handle these situations because I will be doing this time and time again in the future.

newQuery isn't the right method for this. What we want to use is Global Scope method mentioned in the question. This allows us to define the scoping used every time the query is called as well as how to remove the scoping when it isn't needed. Laravel is smart enough to remove the scoping when needed (create, update, delete). The Docs read:
If an Eloquent model uses a trait that has a method matching the bootNameOfTrait naming convention, that trait method will be called when the Eloquent model is booted, giving you an opportunity to register a global scope, or do anything else you want. A scope must implement ScopeInterface, which specifies two methods: apply and remove.
I skipped the trait part and placed the following in the model itself but for organization it would be wise to break this into its own Trait. I added the following to the model:
// Import the classes
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ScopeInterface;
// Add the global scope
public static function boot() {
parent::boot();
static::addGlobalScope(new StatusColumnScope);
}
// Create the scope
class StatusColumnScope implements ScopeInterface {
// Create our apply method that will be called for every query on this model
public function apply(Builder $builder) {
$builder->select(DB::raw('leads.*, status.status_id AS status'))->from(DB::raw(' leads
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
SELECT lead_id, max(created_at) AS max_created_at
FROM lead_status GROUP BY lead_id
) AS ls2 INNER JOIN lead_status AS ls ON ls.lead_id = ls2.lead_id AND ls.created_at = ls2.max_created_at
) AS status ON leads.id = status.lead_id
'));
}
// Create the remove method that is called every time we need to unscope
public function remove(Builder $builder) {
$query = $builder->getQuery();
$query->selectRaw('select *')->from('leads');
}
}
The remove method above is called when we need to un-scope. This was confusing to me at first because I was thinking it should just not scope if we don't need to scope. This isn't how Laravel works though. This only became appearant after really digging in. I will show you what I saw. Let's look under the hood of Laravel at a few methods in Eloquent/Builder.php:
create:
public static function create(array $attributes) {
$model = new static($attributes);
$model->save();
return $model;
}
This calls save:
public function save(array $options = array()) {
$query = $this->newQueryWithoutScopes();
.......
}
and that calls newQueryWithoutScope:
public function newQueryWithoutScopes() {
return $this->removeGlobalScopes($this->newQuery());
}
and of course removeGlobalScopes:
public function removeGlobalScopes($builder) {
foreach ($this->getGlobalScopes() as $scope) {
$scope->remove($builder, $this);
}
return $builder;
}
$scope->remove() is the remove method that we setup in our StatusColumnScope class. This is called to reverse the scoping but in our case I just overrode the query completely, setting it back to it's original query.

Related

How to make query in model with 2 parameters

I need make a query with 2 parameters in model on codeigniter 4.
Is this possible?
This is the model:
public function obtenerProyectoId($id_user,$id_project)
{
$proyectos = $this->db->query(" SELECT * FROM proyectos INNER JOIN empresa ON proyectos.id_usuario=$id_user and proyectos.id_proyecto=id_project");
return $proyectos->getResultArray();
}
in mysql this query is correct, but in CI4 it shows an error:
Too few arguments to function App\Models\Proyectos::obtenerProyecto(),
1 passed in
C:\xampp\htdocs\plataformaknowmad\app\Controllers\verProyectos.php on
line 25 and exactly 2 expected
Yes, it is possible to make a query with multiple params.
//if second param is optional
public function obtenerProyectoId($id_user,$id_project="")
In query $ is missing from id_project
$this->db->query("SELECT * FROM proyectos INNER JOIN empresa ON proyectos.id_usuario=$id_user and proyectos.id_proyecto=$id_project");
May be you did not pass second parameter($id_project) in controller:
verProyectos.php on line 25 and exactly 2 expected

How to convert query with many joins and inner select to Laravel ORM

Hi guys please help solve this problem.
Need to convert this query to Laravel ORM.
$user = Auth::user();
$datas = DB::select('select * from kvits k
left join (select o.kvit_id, sum(o.amount * o.price) as sum from operations o
group by o.kvit_id) s on k.id = s.kvit_id
left join users u on k.user_id = u.id
left join users h on k.hamkor_id = h.id
left join stores s2 on k.store_id = s2.id
where k.user_id = ' $user->id ');
Main problem is how to convert this query to Larave ORM.
Basically this part
left join (select o.kvit_id, sum(o.amount * o.price) as sum from operations o
group by o.kvit_id) s on k.id = s.kvit_id
Second problem is:
Why Laravel getting the last join id in the column without taking all the ids (e.g. k.id, ****, u.id, ***, s.id on this query). And does not display data from all other joined columns.
How to get them.
I want to get data all joined tables also, like k.id, k.date, ... u.id, u.name, .... s.id, s.name, s.notes . Like belowed table.
First, Why Laravel getting the last join id in the column without taking all the ids (e.g. k.id, ****, u.id, ***, s.id on this query)., Laravel uses PDO to fetch data from the database, and PDO fetches the data by the column name and not by the table_name.column_name index.
Basically what you have to do is create your Model (the model that will contain all this data together) like that (it is a model example):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class MyUserModel extends Model {
public function addresses() {
// query all addresses of this user and returns it
}
public function phone () {
// query User's phone from database and returns it
}
// ...
}
and then when you want to use some value, for example, you want to get the User's phone ID, you just use $user->phone->id and Laravel will run the phone() method from MyUserModel class. To learn more about go to Laravel Eloquent Relationships Queries Documentation
if you want to always load these data together whenever a query with this model is made, you can use the protected $with model variable to can fetch all data. see more on Laravel Documentation.
CAUTION: If you want to customize the method query from outside the method you must call the method as a Function, for example $user->phone()->where('active', 1), and not as a Dynamic Property like $user->phone->where('active', 1).

How to Join same table in laravel

I wan to wirte a join query to connect same table, and without ON, but when i write it in laravel without on it is showing error
$key = DB::table('api_keys as ak')
->join('api_keys as bk','')
->where('ak.api_key', $api_key)->where('ak.user_id',0)
->pluck('api_key');
want to build the below query,
SELECT * FROM `api_keys` as ak
JOIN `api_keys` as bk
WHERE ak.`api_key`=$akey
and ak.`user_id`=$auser
and bk.`user_id`=$bsuer
and bk.`api_key`=$bkey
You must provide an ON clause for the join. More about where ON clauses are required can be found in this answer.
You can view the generated query using toSql() on a QueryBuilder object:
echo $key = DB::table('api_keys as ak')
->join('api_keys as bk','')
->where('ak.api_key', $api_key)->where('ak.user_id',0)
->toSql();
Which in your case returns:
select * from `api_keys` as `ak` inner join `api_keys` as `bk`
on `` `` where `ak`.`api_key` = ? and `ak`.`user_id` = ?
In your case it isn't totally clear what you are trying to achieve, but you might consider joining on api_key or the primary key of the api_keys table, if that is different:
$key = DB::table('api_keys as ak')
->join('api_keys as bk','ak.api_key', '=', bk.api_key)
->where('ak.api_key', $api_key)->where('ak.user_id',0)
->pluck('api_key');
DB::table('registerusers as a')
->join('registerusers as b', 'a.id', 'b.refer_id')
->where('a.username', 'b.username')
->where('b.id', 'a.refer_id')
->value('b.id');
without using on clause in laravel query builder you can use following
$key = DB::table(DB::raw('api_keys as ak, api_keys as bk'))
->where('ak.api_key', '=', $api_key)
->where('ak.user_id','=',0)
->where('ak.PK','=','bk.PK')
->pluck('ak.api_key')
where PK references to your table's primary key.
result will in your case.
select * from api_keys as ak, api_keys as bk where ak.api_key= 'api_key_value' and ak.user_id = 0 and ak.PK = bk.PK
I solved this by creating my own class and starting out with a base query which I modify to apply the join (using Laravel's joinSub function) as follows:
public function __construct()
{
$this->query = DB::table('question_responses as BASE');
}
public function applyFilter($questionId, $questionValue) {
$filterTableStr = 'filter_table_'.$questionId;
$filterIdStr = 'filter_id_'.$questionId;
$filterQuery = DB::table('question_responses AS '.$filterTableStr)
->select('survey_response_id AS '.$filterIdStr)
->where($filterTableStr.'.question_short_name', $questionId)
->where($filterTableStr.'.value', $questionValue);
$resultTableStr = 'result_table_'.$questionId;
$this->query = $this->query
->joinSub($filterQuery, $resultTableStr, function($join) use ($resultTableStr, $filterIdStr) {
$join->on('BASE.survey_response_id', '=', $resultTableStr.'.'.$filterIdStr);
});
}
After applying my required filters I can just call $this->query->get() as normal to obtain the result.
The important part was to make sure that each resulting table and join fields has unique names.
With this method I can apply unlimited filters to my base query.

Symfony2 subquery within Doctrine entity manager

I need to perform this query:
SELECT * FROM (SELECT * FROM product WHERE car = 'large' ORDER BY onSale DESC) AS product_ordered GROUP BY type
In Symfony2 using the entity manager.
My basic query builder would be :
$query = $em->getRepository('AutomotiveBundle:Car')
->createQueryBuilder('p')
->where('pr.car = ?1')
->andWhere('pr.status = 1')
->orderBy('pr.onSale', 'DESC')
->setParameter(1, $product->getName())
->groupBy('p.type')
->getQuery();
But I cannot work out how to add in a subquery to this.
Ive tried making a separate query and joining it like:
->andWhere($query->expr()->in('pr.car = ?1',$query2->getQuery()));
But I get:
Call to undefined method Doctrine\ORM\Query::expr()
One trick is to build two queries and then use getDQL() to feed the first query into the second query.
For example, this query returns a distinct list of game ids:
$qbGameId = $em->createQueryBuilder();
$qbGameId->addSelect('distinct gameGameId.id');
$qbGameId->from('ZaysoCoreBundle:Event','gameGameId');
$qbGameId->leftJoin('gameGameId.teams','gameTeamGameId');
if ($date1) $qbGameId->andWhere($qbGameId->expr()->gte('gameGameId.date',$date1));
if ($date2) $qbGameId->andWhere($qbGameId->expr()->lte('gameGameId.date',$date2));
Now use the dql to get additional information about the games themselves:
$qbGames = $em->createQueryBuilder();
$qbGames->addSelect('game');
$qbGames->addSelect('gameTeam');
$qbGames->addSelect('team');
$qbGames->addSelect('field');
$qbGames->addSelect('gamePerson');
$qbGames->addSelect('person');
$qbGames->from('ZaysoCoreBundle:Event','game');
$qbGames->leftJoin('game.teams', 'gameTeam');
$qbGames->leftJoin('game.persons', 'gamePerson');
$qbGames->leftJoin('game.field', 'field');
$qbGames->leftJoin('gameTeam.team', 'team');
$qbGames->leftJoin('gamePerson.person', 'person');
// Here is where we feed in the dql
$qbGames->andWhere($qbGames->expr()->in('game.id',$qbGameId->getDQL()));
Kind of a long example but i didn't want to edit out stuff and maybe break it.
You can use DBAL for performing any sql query.
$conn = $this->get('database_connection');//create a connection with your DB
$sql="SELECT * FROM (SELECT * FROM product WHERE car =? ORDER BY onSale DESC) AS product_ordered GROUP BY type"; //Your sql Query
$stmt = $conn->prepare($sql); // Prepare your sql
$stmt->bindValue(1, 'large'); // bind your values ,if you have to bind another value, you need to write $stmt->bindValue(2, 'anothervalue'); but your order is important so on..
$stmt->execute(); //execute your sql
$result=$stmt->fetchAll(); // fetch your result
happy coding

doctrine 2 - query builder conditional queries... If statements?

My query is doctirne 2. i have a status field in users, private or
public. i want to be able to run this query and display all comments
where status= public and private only if userid = current logged in
user id(which i know, $loggerUserVarID)
$q = $this->em->createQueryBuilder()
->select('c')
->from('\Entities\Comments', 'c')
->leftJoin('c.users', 'u')
->where('status = public') ??? display all public comments but private if it belpongs to the logged in user.?
->setParameter(1, $loggerUserVarID)
->getQuery();
at the moment, i am using an if statement after i get thee results, is there a way to do an if statement inside this query?
So, you want to return Comments "If status is 'public' or the ownerId is $loggedUserVarID", right?
Note that if $loggedUserVarID matches the owner, you don't really care about status.
Check out the querybuilder and dql docs. They explain pretty clearly how to put together complex where conditions.
What you probably want is something like:
$q = $this->em->createQueryBuilder()
->select('c')
->from('\Entities\Comments', 'c')
->join('c.users', 'u')
->where(
$qb->expr()->orX(
$qb->expr()->eq('status','public'),
$qb->expr()->eq('u.id',$loggedInUser)
)
)
->setParameter(1, $loggerUserVarID)
->getQuery();

Resources