Laravel Model, Correct neat way of doing this - php

I have this table called blocked users, It is laid out like so.
id, user_id, blocked_user_id, reason, created_at, updated_at
Now I have a message controller which 2 people can converse. I wish to check if the user is blocked before sending a message which I have done like so.
$blkchk = $thread->participants()->withTrashed()->get();
foreach ($blkchk as $usr) {
$usrs[] = $usr->user_id;
}
$block = BlockedUsers::where('user_id',$usrs[0] )->where('blocked_user_id', $usrs[1])->first();
$block2 = BlockedUsers::where('user_id',$usrs[1])->where('blocked_user_id', $usrs[0])->first();
if (!empty($block) || !empty($block2)) {
return response()->view('errors.403', ['error' => 'One of the users in this conversation is blocked.'], 404);
}
This is pretty messy but blkchk is getting both user ids in the conversation. setting them as an array which i then target each one individually, It works perfect, However I believe this to be verry messy.
Just wondering how the correct and neatest way would be about doing this.

Given you want to check whether specific entries exist in the blocked_users table, you can use whereIn() method likewise:
$blockedUsers = BlockedUsers::whereRaw(
'user_id = ? AND blocked_user_id = ?', [$usrs[0], $usrs[1]]
)
->orWhereRaw(
'user_id = ? AND blocked_user_id = ?', [$usrs[1], $usrs[0]]
);
if ($blockedUsers->count()) {
return 'Some of the users are blocked';
}
Personally, I don't think your database design is the best way to go about it, but this will work for what you need.

Related

Database data field check before Data insertion

I have a data coming from the HTML Page. And i want to check whether the date and the place values already exists. If they exists, it should throw an error saying Data is already present, if those date and place data is not there it should allow the user to save it.
Here is the code which i have written to save it,
public function StoreSampling(Request $request)
{
$date = Carbon::createFromFormat('d-m-Y', $request->input('date'))->format('Y-m-d');
$doctorname = Input::get('doctorselected');
$product = Input::get('product');
$product= implode(',', $product);
$quantity = Input::get('qty');
$quantity =implode(',',$quantity);
$representativeid = Input::get('representativeid');
//Store all the parameters.
$samplingOrder = new SamplingOrder();
$samplingOrder->date = $date;
$samplingOrder->doctorselected = $doctorname;
$samplingOrder->products = $product;
$samplingOrder->quantity = $quantity;
$samplingOrder->representativeid = $representativeid;
$samplingOrder->save();
return redirect()->back()->with('success',true);
}
I searched some of the Stack over flow pages. And came across finding the existence through the ID And here is the sample,
$count = DB::table('teammembersall')
->where('TeamId', $teamNameSelectBoxInTeamMembers)
->where('UserId', $userNameSelectBoxInTeamMembers)
->count();
if ($count > 0){
// This user already in a team
//send error message
} else {
DB::table('teammembersall')->insert($data);
}
But i want to compare the date and the place. And if they are not present, i want to let the user to save it. Basically trying to stop the duplicate entries.
Please help me with this.
There are very good helper functions for this called firstOrNew and firstOrCreate, the latter will directly create it, while the first one you will need to explicitly call save. So I would go with the following:
$order = SamplingOrder::firstOrNew([
'date' => $date,
'place' => $place
], [
'doctorname' => Input::get('doctorselected'),
'product' => implode(',', Input::get('product')),
'quantity' => implode(',',Input::get('qty')),
'representativeid' => Input::get('representativeid')
]);
if($order->exists()) {
// throw error
return;
}
$order->save();
// success
You need to modify your query to something like this:
$userAlreadyInTeam = SamplingOrder::where('date', $date)
->where('place', $place) // I'm not sure what the attribute name is for this as not mentioned in question
// any other conditions
->exists();
if (userAlreadyInTeam) {
// Handle error
} else {
// Create
}
You do not need to use count() as your only trying to determine existence.
Also consider adding a multi column unique attribute to your database, to guarantee that you don't have a member with the same data and place.
The best way is to use the laravel unique validation on multiple columns. Take a look at this.
I'm presuming that id is your primary key and in the sampling_orders table. The validation rule looks like this:
'date' => ['unique:sampling_orders,date,'.$date.',NULL,id,place,'.$place]
p.s: I do not see any place input in your StoreSampling()

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

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!

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.

Complex Zend Query from same user table

I have a rather unique set of conditions and orders in which I need to retrieve data from a "sellers" table for an application I'm building in Zend framework.
The client is basically requesting an application where the directory page lists sellers in a very particular order, which is:
Sellers who have been approved in the last 7 days (then order by #4 below)
Then, selllers who have paid for upgraded features on the site, and are more the 7 days old (then order by #4 below)
Then, Sellers who are more than 7 days old and are more than 7 days old (then order by #4 below)
For all of the above, secondary order by would be their launch date, then alpha by business name
I'm trying to figure out the most effective way to write an action helper that will return the data in the correct sequence above, knowing that some of my views only need 1,2 (and 4), whereas other views within the application will need all 4.
Right now, I've been writing two or three separate queries, and passing them to 2 or 3 partialloop's inside the view, but I strive for properly written code, and would like to either combine my 3 queries into one object I can pass to one partial loop, or.... write one query. How can this be done?
Here's my helper at the moment:
class Plugin_Controller_Action_Helper_ListSellers extends Zend_Controller_Action_Helper_Abstract
{
//put your code here
public function direct($regulars = false, $filter = false)
{
$dateMod = $this->dateMod = new DateTime();
$dateMod->modify('-7 days');
$formattedDate = $dateMod->format('Y-m-d H:i:s');
// get sellers initialized in last 7 days
$sellerTable = new Application_Model_DbTable_Seller();
// get sellers initialized in last 7 days
$select = $sellerTable->select()->setIntegrityCheck(false);
$select->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$select->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$select->order('s.launchDate DESC','s.businessName ASC');
$select->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1');
$select->where('s.launchDate > ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$newSellers = $sellerTable->fetchAll($select);
$query = $sellerTable->select()->setIntegrityCheck(false);
$query->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$query->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$query->order('s.launchDate DESC','s.businessName ASC');
$query->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured = 1');
$query->where('s.launchDate < ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$featuredSellers = $sellerTable->fetchAll($query);
if($regulars){
$where = $sellerTable->select()->setIntegrityCheck(false);
$where->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$where->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$where->order('s.launchDate DESC','s.businessName ASC');
$where->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured IS NULL');
$where->where('s.launchDate < ?', $formattedDate);
$regularSellers = $sellerTable->fetchAll($where);
}
}
}
I don't see any limits being applied to your queries. So does that mean you really want to select all matching records? For scalability reasons I'd guess that the answer should be no, there will be limits applied. In this case, you may just have to do 3 different queries.
But if there are no limits to be applied, then you could do a single simple query that selects all sellers, unfiltered and unsorted, and do your sorting and filtering in view helpers or just in your views.
Regardless, I recommend not putting database queries inside your controller layer, assuming you want to use the Model-View-Controller pattern which Zend is built for. Controllers should be thin. Your models should handle all database queries and just spit out the results into your controllers. I use the Data Mapper pattern extensively. Something like:
$mapper = new Application_Model_SellerMapper();
$newSellers = $mapper->fetchNewSellers();
$featuredSellers = $mapper->fetchFeaturedSellers();
$regularSellers = $mapper->fetchRegularSellers();
Each of your fetchX() methods would return an array of Application_Model_Seller instances, rather than Zend_Db_Table_Row instances.
This way you maintain Separation of Concerns and Single Responsibility Principle better, for more maintainable code. Even if you're the only developer on the project over the long-term, 6 months from now you won't remember what you wrote and why. And if someone else comes on the project, clarity becomes really important.

Categories