Laravel - can't find() attached object in many-to-many relationship - php

in my POST form users are able to add other users to a room.
I put a unique constraint on the link (no duplicate entry in the link between users and rooms).
However when I refresh my page (f5) after submitting the form, Laravel complains about duplicate entries, although I do check if the objects are attached before.
Here's the code:
$roomUsers = Room::find($request->room_id)->users();
if ($request->add != null) {
foreach ($request->add as $uId)
// if null, user hasnt been attach yet
if (!$roomUsers->find($uId)) {
Log::debug($roomUsers->find($uId) == null ? 'null' : 'not null');
// then we can attach him
$roomUsers->attach($uId);
}
}
The line !$roomUsers->find($uId) returns true yet the object has been attached in the previous iteration. How is that possible ? Thanks

The reason you're above code isn't working is because you're not creating a new instance of BelongsToMany for each check. This means that every time you call find you're not actually creating a new query you're just adding to the existing one e.g.
say you the ids to add are [1, 2, 3] by the last check your query would effectively be:
SELECT * FROM users WHERE id = 1 AND id = 2 AND id = 3
To keep with the above logic you could do:
$room = Room::find($request->room_id);
if ($request->add != null) {
foreach ($request->add as $uId)
// if null, user hasnt been attach yet
if (!$room->users()->find($uId)) {
// then we can attach him
$room->users()->attach($uId);
}
}
Or a much simpler way to go about this would be to syncWithoutDetaching.
Your code could then look something like:
$roomUsers = Room::find($request->room_id);
if ($request->has('add')) {
$roomUsers->users()->syncWithoutDetaching($request->add);
}
Hope this helps!

Related

How to set sequence data with existing random data?

I have a table entity named it as uniqueId where the entry generate randomly.Such as
$customer->uniqueId = $request->Input(['uniqueId']) ?: mt_rand(1000, 9999);
means if there is existing uniqueId it will store the existing one otherwise it will be set to the random number. Now instead of setting the random number i want to set it as sequentially . means from 1, 2, 3 like that.. as i can't delete the existing uniqueId which has already created how do I create new entry sequentially with the existing one?
If you simply set column to auto increment you will achieve this automatically you dont even need to call it.
In laravel you can achieve this in your migrations by
$table->increments('uniqueId');
OR
You can achieve this by
lets assume you have a customer Model
// find the last entry in you table
$oldCustomer = Customer::orderBy('uniqueId','DESC')->first();
$customer->uniqueId = ++($olderCustomer->uniqueId);
I hope this helps
**EDIT **
$customers = Customer::all();
$index = 1;
#foreach($customers as $customer)
{
$customer->uniqueID = $index++;
$customer->update();
}

Symfony: Delete all the records in a table who have the id in the string in table's column

[Please note that I do not have code for this problem, I need code, I have tried to explain it the best way, and if you can help, it will be great]
so here is the deal, I have a field in the table named "order" for every user. The main job of the user is to bring other users to the system and when they bring a new user their id is sticked to (concated with) the referring user's id and stored in his "order field"
for eg.
user 'a' has id 31. 'a' brings in 'b' whose is assigned 32, now b's therefore b has the value: '31-32' stored in his 'order' field. simililarly if b brings in 'c' whose id is 35, the order for c will be: '31-32-35' and it goes so on.
Now when I delete 'a' I want ALL the users who have his id in their order fields, that is if i decide to delete 'a' in this above field, all the users should be deleted from he system too!
I want to do this via symfony controller, and I think that can be done by findAll() function in symfony but I have no clue how to use it.
Please help, I am really stuck!
I did by using a parent field in each table for the user:
and here is the code if such thing arises for someone:
<?php
$repo = $em->getRepository('SystemBundle:Distributor');
$temp = $slug;
$pd = $repo->findAll();
for ($i = 1; $i <= count($pd); $i++) {
$u = $repo->findOneBy(['parent' => $temp]);
if ($u) {
$temp = $u->getId();
$em->remove($u);
$em->flush();
} else {
break;
}
}
$dis = $repo->findOneBy(["id" => $slug]);
$em->remove($dis);
$em->flush();
Using for loop it was possible to do this

Update certain fields based on condition in Doctrine

I have created a Symfony 2 Bundle that supports private messages between users. I gave them the ability to send messages from their inbox or sent folder to the trash one. Messages will be marked as trash through the isRTrash and isSTrash fields, marked by receiver and by sender, respectively. That is because, being the same message in my database, if I had one single field here, one user marking it as trash, would mark it for the other one, as well.
Now, I want to give them the possibility to delete them, too, from their trash folder. Messages won't be deleted, but marked similarly to trash ones, just that they are forever gone from standard user view. I'm having problems with marking them like this, because I have to mark both messages that are sent and received by the user.
I've made the following query in the entity's repository:
public function delete($user, $msg)
{
$qb = $this->createQueryBuilder('a')
->update('PrivateMessageBundle:Message', 'a')
->where('a IN(:msg)')
->andwhere('a.receiver = :user AND a.isRTrash IS NOT null AND a.isRDeleted = false')->set('a.isRDeleted', true)
->orWhere('a.sender = :user AND a.isSTrash IS NOT null AND a.isSDeleted = false')->set('a.isSDeleted', true)
->setParameters(
array('user' => $user, 'msg' => $msg)
);
echo '<pre>';
\Doctrine\Common\Util\Debug::dump($qb->getQuery()->getSQL()); exit;
echo '</pre>';
return $qb->getQuery();
}
And the output query is string(196) "UPDATE message SET isRDeleted = 1, isSDeleted = 1 WHERE (id IN (?) AND (receiver_id = ? AND isRTrash IS NOT NULL AND isRDeleted = 0)) OR (sender_id = ? AND isSTrash IS NOT NULL AND isSDeleted = 0)"
I give as input the curent logged in user and an array of message id's. Then, I check messages that are in trash, are not marked as deleted and have the curent user as receiver or sender and want to mark them as deleted.
The problem is that both conditions are met, and both SET are being called, marking a message's isRDeleted and isSDeleted to true, regardless.
I am very close, but don't know how to make it so that the fields are marked separately, only if their condition is met.
Meanwhile, I'm using a foreach loop, but I think it can be done faster with a query
$em = $this->getDoctrine()->getManager();
foreach ($msgs as $msgid) {
$msg = $messages->findOneBy(array('id' => $msgid));
if ($msg->getSender() == $this->getUser() && $msg->getIsSTrash() && $msg->getIsSDeleted() == false) {
$msg->setIsSDeleted(true);
$changedno++;
} else if ($msg->getReceiver() == $this->getUser() && $msg->getIsRTrash() && $msg->getIsRDeleted() == false) {
$msg->setIsRDeleted(true);
$changedno++;
}
$em->flush();
}
I think you need a CASE .. WHEN construction but Doctrine doesn't have that in DQL (See the Grammar). So you either must use a raw query, something along these lines (it's pseudo MySQL) :
UPDATE PrivateMessageBundle:Message a
SET a.isRDeleted = CASE
WHEN a.receiver = :user AND a.isRTrash IS NOT null THEN TRUE
ELSE a.isRDeleted = FALSE
END,
SET a.isSSDeleted = CASE
WHEN a.receiver = :user AND a.isRTrash IS NOT null THEN TRUE
ELSE a.isSDeleted = FALSE
END
... or use two standard queries, one for isRDeleted and one for isSDeleted, like the one you already did. To be honest I think that's a pretty simple solution in your case, and it looks more maintenance-friendly if you ever need to read your code again in six months.
NB : on a side note, the ->set() or ->when() functions in Doctrine (and all the others, in fact) do not follow a specific order; they are just adding properties to the Doctrine query object, and when you call getQuery(), a SQL query is made. That means that the following construction :
->when()->set()
->orwhen()->set()
is equivalent to :
->set()->set()
->when()->orWhen()
which is why your solution cannot work. There is no condition to be met before set() is called (if I'm not clear, tell me)

Laravel Eloquent: how to filter multiple and/or criteria single table

I am making a real estate related app and I've been having a hard time figuring out how to set up the query so that it would return "Only Apartments or Duplexes within selected areas" I'd like to user to be able to find multiple types of property in multiple selected quadrants of the city.
I have a database with a column "type" which is either "Apartment", "House", "Duplex", "Mobile"
In another column I have quadrant_main with values: "NW", "SW", "NE", "SE".
My code works when there is only 1 quadrant selected, but when I select multiple quadrants, I seem to get results which includes ALL the property types from the second or third or 4th quadrant, instead of only "Apartment" and "Duplex" or whatever types the user selects... Any help will be appreciated! thx in advance.
My controller function looks like this:
public function quadrants()
{
$input = \Request::all();
$currentPage = null;
$column = "price";
$order = "desc";
//
// Looks like the input is like 0 => { key: value } ...
// (an Array of key/value pairs)
$q = Listing::where('status','=','Active')->where(function($query) {
$input = \Request::all();
$currentPage = null;
$typeCount = 0;
$quadrantCount = 0;
foreach( $input as $index => $object ) {
$tempObj = json_decode($object);
$key = key((array)$tempObj);
$val = current((array)$tempObj);
if ( $key == "type" ) {
if ( $typeCount > 0 ) {
$query->orWhere('type', '=', $val );
}
else {
$query->where('type', '=', $val );
$typeCount++;
}
}
if ( $key == "quadrant_main" ) {
if ( $quadrantCount > 0 ) {
$query->orWhere('quadrant_main', '=', $val );
}
else {
$query->where('quadrant_main', '=', $val );
$quadrantCount++;
}
}
// else {
// $query->orWhere($key,$val);
// }
}
if( $currentPage ) {
//Force Current Page to Page of Val
Paginator::currentPageResolver(function() use ($currentPage) {
return $currentPage;
});
}
});
$listings = $q->paginate(10);
return $listings;
Looking at your question, its a bit confusing and not much is given to answer definitely. Probable causes of your troubles may be bad data in database, or maybe corrupted input by user.
Disclaimer: Please note that chances are my answer will not work for you at all.
In that case please provide more information and we will work things
out.
There is one thing that I think you have overlooked and thus you are getting awry results. First let me assume a few things.
I think a sample user input should look like this:
array(
0: '{type: Apartment}',
1: '{type: Duplex}',
2: '{quadrant_main: NW}',
3: '{quadrant_main: SW}',
)
What the user meant was give me any apartment or duplex which belongs in NW or SW region.
So after your loop is over, the final SQL statement should be something like this:
Oh and while we are at SQL topic, you can also log the actual
generated SQL query in laravel so you can actually see what was the
final SQL getting generated. If you can post it here, it would help a
lot. Look here.
select * from listings where status = 'Active' and (type = 'Apartment' or type = 'Duplex' and quadrant_main = 'NW' or quadrant_main = 'SW');
What this query will actually produce is this:
Select any listing which is active and:
1. Type is an apartment, or,
2. Type is a duplex, or,
3. Quadrant is SW, and,
4. Quadrant is NW
So assuming you have a database like this:
id|type|quadrant_main
=====================
1|Apartment|NW
2|Apartment|SW
3|Apartment|NE
4|Apartment|SE
5|Duplex|NW
6|Duplex|SW
7|Duplex|NE
8|Duplex|SE
9|House|NW
10|House|SW
11|House|NE
12|House|SE
You will only receive 1, and 5 in the result set. This result set is obviously wrong, plus it is depended on NW because that was the and condition.
The correct SQL query would be:
select * from listings where status = 'Active' and (type = 'Apartment' or type = 'Duplex') and (quadrant_main = 'NW' or quadrant_main = 'SW');
So structure your L5 app such that it produces this kind of SQL query. Instead of trying to cram everything in one loop, have two loops. One loop should only handle type and another loop should only handle quadrant_main. This way you will have the necessary and condition in the right places.
As a side note:
Never directly use user input. Always sanitize it first.
Its not a best practice to put all your logic in the controller. Use repository pattern. See here.
Multiple where clauses are generally applied via Criteria. Check that out in the above linked repository pattern.
You code logic is very complicated and utterly un-necessary. Instead of sending JSON objects, simply send the state of checkboxes. Don't try to generalize the function by going in loop. Instead handle all checkboxes one by one i.e. is "Apartments" selected, if yes, add that to your clause, if not, don't add.

Submitting a given number of times the same form: Why is this code not working?

So I am making a quiz in which the user gets confronted with a succession of questions which he answers through a form.
The series of problems each contain a given number of questions, and the questions get asked one after the other when the user validates.
I am therefore trying to re-render the view with the form for each problem until they're all done. This is my action:
public function actionAnswer($id_serie)
{
if ($id_serie != 0) //getting the serie's info
{
$serie = Serie::find()
->where(['id' => $id_serie])
->one();
$problems = (new \yii\db\Query()) //getting the problems in the serie
->select('*')
->from('problems')
->where(['id_serie' => $id_serie])
->all();
$prob_counter = $serie->nbr_of_problems; //counts the number of questions answered
$id_serie = 0;
}
$model = new Answer;
if ($model->load(Yii::$app->request->post()) && $model->validate())
{
$model->save(); // works just fine every time
if (--$prob_counter <= 0)
{
return $this->redirect('index.php?r=student/entry');
}
}
return $this->render('answer',
['model' => $model,
'problems' => $problems,
'serie' => $serie,
'prob_counter' => $prob_counter, //these last two are for debug
'id_serie' => $id_serie]);
}
When this action gets executed the first time, $id_serie is never null or =0. Hence I am using this to query the db only once and set a counter to the total number of problems in the serie. (id est the number of time the user has to submit the form)
If his answer is valid, I decrement my counter and if it falls under 0, there are no questions to answer anymore and the user gets redirected.
However, this counter never goes down to 0: it is set correctly, it is decremented only once, and then it never falls lower, no matter where I put the line. (inside or outside any loop)
On the other hand the data from the form is properly inserted in the db each time.
What am I getting wrong?
As per your code, $prob_counter just stores the number of problems for each series. You need to change this to show the number of unanswered problems for the series. How you implement this will depend on your models and database but it should be something like:
$problems = (new \yii\db\Query()) //getting the problems in the serie
->select('*')
->from('problems')
->where(['id_serie' => $id_serie])
->andWhere('not exists (select id from answer where problemid = problems.id')
->all();
Also you should probably look at working with relational data and avoid using Query() in the above section.

Categories