Laravel 5.2 Insert from select using dynamic content - php

As a novice to laravel I am hoping I can get to the bottom of this rather irritating issue!
I have a small app that consists of 2 user types, Buyers and Providers, The usertype->buyers can request a free estimation for a service they require and the provider can create this manually or have an estimation sent automatically based on what they set as there default value for that specific price range so for example if a user(buyer) requests an estimation for a service that is £200 the provider may have set a default estimation of £180 - £220 for that price range and this is what will be shown to the user.
The problem I have is to make it fair we use the random function within laravel to randomly select 5 providers from the providers_table along with their default estimation and show this to the user from orderBy in descending order and looping through the results in the view.
$projectbids = ProjectBids::where('provider_id', '=', $proid)
->where('category_id', '=', $catid)
->orderBy('bid_price', 'desc')
->get()->random(5);
foreach($projectbids as $bid)
{{ $bid->bid_price }}
endforeach
So I'm trying to find a way to insert the 5 displayed estimations into a saved_estimates table so if the user refreshes the page or comes back to the page the model and view will not reorder the providers results based from the random(5) function that gets called.
I'm aware of this question: Create a Insert... Select statement in Laravel but wasn't to sure if there could be a better or easier way of doing this as my problem lays where I'm looping through the 5 results provided to the view from the table.
In a nutshell: I want to be able to store the 5 dynamic results straight into a saved_estimations table with several columns such as project_id, provider_id, user_id, estimated_price so the user can come back to their saved estimations in the future without them changing.
Very grateful for any input and advice, it will be very helpful to see what the more experienced users would suggest.
Thanks
-- Updated personal approach to see if there is room for improvement and to make it more laravel friendly.
$results = $bidders;
foreach($results as $row){
$project = $projectid;
$category = $row->category_id;
$provider = $row->service_providers_id;
$bidprice = $row->bid_price;
$propvalue = $row->property_value_id;
DB::table('static_bids')->insert(array(
'project_id' => $project,
'category_id' => $category,
'service_providers_id' => $provider,
'bid_price' => $bidprice,
'property_value_id' => $propvalue
));
}

All you need is to use simple insertions into the table, something like this:
foreach ($projectbids as $bid) {
$result = SavedEstimation::create(array(
'project_id' => $bid->project_id,
'provider_id'=> $bid->provider_id,
'user_id' => $bid->user_id,
'estimated_price' => $bid->estimated_price
));
}
There is no way to insert this by using just one query anyway. You could simply store output HTML if the only reason you want to do this is to show same HTML to the user later, but this is bad practice.

Related

How to simplify this Laravel PHP code to one Eloquent query?

I assume that this should all be in one query in order to prevent duplicate data in the database. Is this correct?
How do I simplify this code into one Eloquent query?
$user = User::where( 'id', '=', $otherID )->first();
if( $user != null )
{
if( $user->requestReceived() )
accept_friend( $otherID );
else if( !$user->requestSent() )
{
$friend = new Friend;
$friend->user_1= $myID;
$friend->user_2 = $otherID;
$friend->accepted = 0;
$friend->save();
}
}
I assume that this should all be in one query in order to prevent
duplicate data in the database. Is this correct?
It's not correct. You prevent duplication by placing unique constraints on database level.
There's literally nothing you can do in php or any other language for that matter, that will prevent duplicates, if you don't have unique keys on your table(s). That's a simple fact, and if anyone tells you anything different - that person is blatantly wrong. I can explain why, but the explanation would be a lengthy one so I'll skip it.
Your code should be quite simple - just insert the data. Since it's not exactly clear how uniqueness is handled (it appears to be user_2, accepted, but there's an edge case), without a bit more data form you - it's not possible to suggest a complete solution.
You can always disregard what I wrote and try to go with suggested solutions, but they will fail miserably and you'll end up with duplicates.
I would say if there is a relationship between User and Friend you can simply employ Laravel's model relationship, such as:
$status = User::find($id)->friends()->updateOrCreate(['user_id' => $id], $attributes_to_update));
Thats what I would do to ensure that the new data is updated or a new one is created.
PS: I have used updateOrCreate() on Laravel 5.2.* only. And also it would be nice to actually do some check on user existence before updating else some errors might be thrown for null.
UPDATE
I'm not sure what to do. Could you explain a bit more what I should do? What about $attributes_to_update ?
Okay. Depending on what fields in the friends table marks the two friends, now using your example user_1 and user_2. By the example I gave, the $attributes_to_update would be (assuming otherID is the new friend's id):
$attributes_to_update = ['user_2' => otherID, 'accepted' => 0 ];
If your relationship between User and Friend is set properly, then the user_1 would already included in the insertion.
Furthermore,on this updateOrCreate function:
updateOrCreate($attributes_to_check, $attributes_to_update);
$attributes_to_check would mean those fields you want to check if they already exists before you create/update new one so if I want to ensure, the check is made when accepted is 0 then I can pass both say `['user_1' => 1, 'accepted' => 0]
Hope this is clearer now.
I'm assuming "friends" here represents a many-to-many relation between users. Apparently friend requests from one user (myID) to another (otherId).
You can represent that with Eloquent as:
class User extends Model
{
//...
public function friends()
{
return $this->belongsToMany(User::class, 'friends', 'myId', 'otherId')->withPivot('accepted');
}
}
That is, no need for Friend model.
Then, I think this is equivalent to what you want to accomplish (if not, please update with clarification):
$me = User::find($myId);
$me->friends()->syncWithoutDetaching([$otherId => ['accepted' => 0]]);
(accepted 0 or 1, according to your business logic).
This sync method prevents duplicate inserts, and updates or creates any row for the given pair of "myId - otherId". You can set any number of additional fields in the pivot table with this method.
However, I agree with #Mjh about setting unique constraints at database level as well.
For this kind of issue, First of all, you have to enjoy the code and database if you are working in laravel. For this first you create realtionship between both table friend and user in database as well as in Models . Also you have to use unique in database .
$data= array('accepted' => 0);
User::find($otherID)->friends()->updateOrCreate(['user_id', $otherID], $data));
This is query you can work with this . Also you can pass multiple condition here. Thanks
You can use firstOrCreate/ firstOrNew methods (https://laravel.com/docs/5.3/eloquent)
Example (from docs) :
// Retrieve the flight by the attributes, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// Retrieve the flight by the attributes, or instantiate a new instance...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
use `firstOrCreate' it will do same as you did manually.
Definition of FirstOrCreate copied from the Laravel Manual.
The firstOrCreate method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the given attributes.
So according to that you should try :
$user = User::where( 'id', '=', $otherID )->first();
$friend=Friend::firstOrCreate(['user_id' => $myId], ['user_2' => $otherId]);
It will check with both IDs if not exists then create record in friends table.

Laravel (OOP), model specfic array of values (for example titles of users)

I have model User and inside there is a field named title.
Now I want to define all possible titles. For example an array of
['0' => 'Mr.', '1' => 'Ms.']
What is the correct way to define these.
My idea is to create the public function titles() { return ['0' => 'Mr.', '1' => 'Ms.']; inside the User.php model and call for the array as $user->titles() whenever I need it. However this makes me twitch a bit because I'm calling the function on the model instance.
I really don't think creating a relation here is needed as there aren't more than ten possible titles.
Is there a better/right way to do these kind of things. I'm not that new to Laravel but I'm self thought so I'm trying to check my ways of doing stuff here.
I need to get into these oop basics a bit so any pointers here are helpfull.
Thanks in advance.
One way to do this is to keep titles in config file, like:
'titles' => [
1 => 'Mr.',
2 => 'Mrs.'
....
],
Benefits are:
you can edit this info at any time (keeping data hardcoded into model class is a bad practice)
you can use this list as is for select list building
you can keep data as TINYINT by keeping title IDs, sometimes it's a benefit
To build select list, do something like this:
{!! Form::select('titles', config('custom_config.titles'), 1) !!}
If you want dynamic select box write this code
$titles = User::lists('title', 'id'); // controller
{!! Form::select('title', $titles, null, ['class' => 'form-control']) !!} // view
You can use config files as #Alexey Mezenin suggested, but in this cases I prefer to use the database to store that kind of options (I mean in a different table, not in same users table), so I can add or delete options without touching the code and use the same form template/partial across different pages or projects if needed.
If you just need to retrieve the array it would be as easy as make a query in your controller and return it, but if you need it for form options I suggest you to use something like a form builder helper in which you get the form options from database and build your form using laravelcollective package, then you can pass the helper object to your view to show your fields and show whatever info you need.

How do I add a count of related records in CakePhp index

I am working on an application to store records for a youth group.
This youth group has a system of badges for members to achieve and I have the following tables;
members
badges
member_badges
The first two tables are self explanatory, members are the youth group members and the badges is the name of a badge, eg. FieldCraft, Life Saving, First Aid and so on.
The member_badges records the achievement of a member and will have the member_id, badge_id, date of attempt and status (Pass or Fail). We record failed attempts to as it will help us to gauge the effectiveness of a course - what's the pass rate and so on.
I want to create a table with the following data;
Badge | Number of Attempts | Pass | Fail
I've created a badgeController function as follow;
public function badgereportsummary() {
$paginate = array(
'contain' => array('MemberBadge'));
$this->Paginator->settings = $paginate;
$this->set('badges', $this->paginate());
}
Now this works in a fashion - it returns an array of Badges, each of which contains an array of the Member Badges and I've got a rough and ready page work with the following in the View
echo h(count($badge['MemberBadge']));
However - this won't allow me to sort on this field, and also in future I want to add the ability to limit on a date range e.g. "Show me the count of First Aid badges attempted from 1/1/15 through 30/3/15". I've seen some suggestions online about using counterCache (I've used that in other places on the app) but this won't allow me to work out count for date range etc.
What I think I want to to is add the count of the Member Badges on a particular page as a virtual field in the controller. I've been searching the docs and Google, and beating my head on the keyboard all day and can't work it out. Any suggestions on how to accomplishment this are greatly appreciated.
Your summary report (Badge | Number of Attempts | Pass | Fail) looks like it needs a SQL statement such as:
SELECT badge.description AS Badge
, COUNT(*) AS NumberOfAttempts
, SUM(IF(member_badges.status = 'Fail',0,1)) AS Fails
, SUM(IF(member_badges.status = 'Pass',0,1)) AS Passes
FROM member_badges
JOIN badges ON badges.id = member_badges.badge_id
WHERE member_badges.date_of_attempt BETWEEN '20140101' AND '20140131'
GROUP BY badge.description
ORDER BY NumberOfAttempts DESC;
I would then change your badgereportsummary method to generate this SQL, which is relatively straightforward.
Move the function from the Badge controller model to the MemberBadge model
Create virtual fields for the new column names within the method ->
$this->MemberBadge->virtualFields['NumberOfAttempts'] = 0;
$this->MemberBadge->virtualFields['Fails'] = 0;
$this->MemberBadge->virtualFields['Passes'] = 0;
Write the find:
Code Example
$fields = array('Badge.description'
,'COUNT(*) AS MemberBadge__NumberOfAttempts'
,'SUM(IF(MemberBadge.status = 'Fail',0,1)) AS MemberBadge__Fails'
,'SUM(IF(MemberBadge.status = 'Pass',0,1)) AS MemberBadge__Passes');
return $this->find('all'
,'conditions' => array('MemberBadge.date_of_attempt BETWEEN ? AND ?' => array($start_date,$end_date)
,'fields' => $fields
,'order' => array('NumberOfAttempts DESC')
,'group' => array('Badge.description'));
Then in any Badge controller method call $this->Badge->MemberBadge-> badgereportsummary($start_date, $end_date)
I would not use a permanent virtual field (i.e., defining the virtual field in the model) since this is an aggregate function. Also, I would not go with counterCache for your exact reason - needing to filter the results based on another field.

Setting user_data in session giving different results

Ok, so this one perplexes me.
When the user logs in I set the userdata in the session like so:
$data = array();
$data['email'] = $this->input->post('email');
// get the user's info where it matches their email in the db.
$user = $this->User_model->get_user($data);
$this->session->set_userdata('user_session', $user);
Now this works great. It creates user_session in the session data as an object. Which I'm ok with.
Then when the user updates their information, I want to reset the data in the user_session to match their new data. I do this like so:
$data = array(
'first_name' => $this->input->post('first_name'),
'last_name' => $this->input->post('last_name'),
'email' => $this->input->post('email'),
'phone' => $this->input->post('phone'),
'street1' => $this->input->post('street1'),
'street2' => $this->input->post('street2'),
'city' => $this->input->post('city'),
'state' => $this->input->post('state'),
'zip' => $this->input->post('zip'),
'password' => $encrypted_password,
'organizations_id' => $this->input->post('org_select')
);
$user = $this->User_model->get_user($data);
$this->session->set_userdata('user_session', $data);
Now the $data here is being used to update their information in the DB and then I reuse it to pull their data out via indexing at their email.
Lastly, here is the model method I use for both of them:
public function get_user($data)
{
return $this->db
->where('email', $data['email'])
->select('first_name, last_name, email, password, id, organizations_id')
->get('users')
->row();
}
This works but differently. Instead of giving me an object, it just gives me an array. Which causes a whole hell of a lot of problems elsewhere in my code.
How do I control wether or not it gives me an object or an array?
EDIT:
I realized an idiot move. I was sending $data instead of $user to my model when the user changed their information.
So I fixed the problem and now I get an object both times. But I still want to know how the two methods gave different results. That way I can control it in the future.
So with that being said, here is my OFFICIAL QUESTION:
How do I control the input of session data so that it is either an array or an object?
well the best option - make a field called uniqueid in your users table. when you create the user - generate a random string - that is your unique id - include it in the insert data.
create a new session - store only the unique id .
then when you update the Users table - the unique id in the session stays the same. you return the updated result to your controller and you don't have to fuss with the session.
a typical process after unique id is set:
get the unique id from session
search users table with unique id
users table returns an object (or array) containing the fields you need
use that user object in your app (not the session)
so we are only using the session cookie to store the unique id. and now we have options - if the user is submitting a form to update their profile then you can pass the unique id in a hidden form field. you get the unique id and you dont have to deal with cookies. again that is only for appropriate for scrambled unique id strings (not sequential ids).
edit: if you use a sequential id then suddenly you have opened up a security hole. it might not matter for this particular app, but i am recommending this approach because its so versatile.
another way to do this - especially if you have more complicated app where users are doing things and drawing from different tables. say if you need to accumulate or track what a user does through the session - you can have a table that oversees the session - and you use the unique id. then store the 'user' id - the sequential one - in the session table. you can then add more ids for other tables, date time, status, errors, totals, whatever -- for that session.
so then with the unique id - you get back an object say called $editsession with easy to get at values like $editsession->userid $editsession->storyid $editsession->status
// example get your user
if( ! $user = $this->user->_get($editsession->userid) ; )
{ $this->_error() ; }
else { $this->_showEditor($user) ;}
and this is a sample random string generator
$uniqueid = random_string( 'alnum', 10 );
finally note that i call the model just 'user' . even a month ago i would have added '_model' or '_m' to the model file name. but that is wrong - it just adds noise - so just get out of the habit now and your code will be much cleaner. all the files are clearly in a folder called models :-)

Data Filtering in Cakephp

Please forgive me, I am still fairly new to cakephp and in the process of reviving my redundant PhP skills.
I have searched all over the "interweb" for the answer, sadly to no avail.
Here is the scenario:
I have 2 tables - 1 being Users the other Groups ( for permissions).
Users table - pretty standard plus some additional profile info.
Groups table - holds Administrators, Super Users, Auth Users and Unauth Users.
The Users table is being used for authentication and profiles (other users can browse user profiles). Now with this in mind, I cannot work out how to filter out which users get rendered into the index view based on which group the users (not the the currently logged user) belong.
For example, The admin and Super user accounts profiles are being rendered and belongs to the "Administrators" and "Super users" groups respectively along with all the other Users. However all I want end users (Auth Users) to be able to see are other user profiles which are part of the Auth Users group.
So any and all help would be appreciated for both saving the remainder of my hair and finding a resolve to this problem I have.
Ok so here is what I did which is working like a charm. Configured the paginate variable to look like this:
var $paginate = array(
'fields' => array(
'User.img_file', 'User.given_name', 'User.family_name', 'Group.name', 'User.username', 'User.created'),
'limit' => 10,
'order' => array(
'User.given_name' => 'asc'
)
);
Then for the in the index function (which I wanted) I added the following:
function index() {
$this->User->recursive = 0;
$this->paginate = array(
'conditions' => array(
'group_id' => 3
));
$users = $this->paginate('User');
$this->set(compact('users'));
}
It's working like I want it to, but if anything does look malformed, or this can be extended, please do post comments.
First off, take a look at this for information on using the built in authentication features of cakephp.
" I cannot work out how to filter out which users get rendered into the index view based on which group the users (not the the currently logged user) belong."
not 100% on what you mean by this, but if you mean you only want to see other users that are admins (and assuming you know that the admin_group_id == 1) then, what you kind of want is the following.
$administrator_group_id = 1;
$conditions = array(
'group_id' => "{$administrator_group_id}"
);
$this->data = $this->User->findAll($conditions);
// note. automatically makes the result data available in the VIEW layer
pr($this->data); // VARDUMP utility
(ps. check this out for how to paginate this result data)
"...all I want end users (Auth Users) to be able to see are other user profiles which are part of the Auth Users group"
(assuming the auth stuff didn't help you.)
(if there are only 3 types of group types, and I can control them completely, then I would consider not using a group table, and using an enumeration. here are notes on how to do this in MySQL
http://dev.mysql.com/doc/refman/5.0/en/enum.html
or, you can just sort of hack it in PHP.
eg.
define('USER', 1);
define('S_USER', 10);
define('ADMIN', 100);
$user = $this->User->findById($my_user_id); // or get from auth
$conditions = array(
"group_id <= " => "{$user['User']['group_id']}"
);
$this->data = $this->User->findAll($conditions);
pr($this->data); // VARDUMP
( what I did was that you get the logged in user, and I made sure that the ADMIN had the highest level ID. SuperUsers had the 2nd, and Users the lowest. This query will return all users that are on the same level, or lower than their own level. group_id <= User.group_id)
I hope I haven't confused you too much. Just keep on experimenting with cake. It's worth it!

Categories