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 :-)
Related
I have been looking for a solution that would replicate MySQL's INSERT ... ON DUPLICATE KEY UPDATE in my Codeigniter model, which led me to the save() function.
I get the logic of it, and my script works fine... but for UPDATING only.
What I've realised now is that my data array is formed from info pulled from an external datafeed, and includes both new AND existing products. There will ALWAYS be a sku (primary key) included in for every product, therefore the save function will ALWAYS look to update the corresponding row in my database - but never insert it - hence, no new objects are ever added to my database - only updated.
// Defined as a model property
$primaryKey = 'sku';
// Does an insert()
$data = [
'brand' => 'Heinz',
'name' => 'Baked Beans'
];
$userModel->save($data);
// Performs an update, since the primary key, 'sku', is found.
$data = [
'sku' => xrt567ycr,
'brand' => 'Heinz',
'title' => 'Baked Beans'
];
$userModel->save($data);
What is the easiest way to get around this and have the script either insert or update based on whether the sku is actually present in the database already or not? All help greatly appreciated!
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.
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.
Using CakePHP 2.2, I am building an application in which each client has it's own "realm" of data and none of the other data is visible to them. For example, a client has his set of users, courses, contractors and jobs. Groups are shared among clients, but they cannot perform actions on groups. All clients can do with groups is assign them to users. So, an administrator (using ACL) can only manage data from the same client id.
All my objects (except groups, of course) have the client_id key.
Now, I know one way to get this done and actually having it working well, but it seems a bit dirty and I'm wondering if there is a better way. Being early in the project and new to CakePHP, I'm eager to get it right.
This is how I'm doing it now :
1- A user logs in. His client_id is written to session according to the data from the user's table.
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
2- In AppController, I have a protected function that compares that session id to a given parameter.
protected function clientCheck($client_id) {
if ($this->Session->read('User.client_id') == $client_id) {
return true;
} else {
$this->Session->setFlash(__('Invalid object or view.'));
$this->redirect(array('controller' => 'user', 'action' => 'home'));
}
}
3- Im my different index actions (each index, each relevant controller), I check the client_id using a paginate condition.
public function index() {
$this->User->recursive = 0;
$this->paginate = array(
'conditions' => array('User.client_id' => $this->Session->read('User.client_id'))
);
$this->set('users', $this->paginate());
}
4- In other actions, I check the client_id before checking the HTTP request type this way.
$user = $this->User->read(null, $id);
$this->clientCheck($user['User']['client_id']);
$this->set('user', $user);
The concept is good - it's not 'dirty', and it's pretty much exactly the same as how I've handled situations like that.
You've just got a couple of lines of redundant code. First:
$this->Auth->user('id')
That method can actually get any field for the logged in user, so you can do:
$this->Auth->user('client_id')
So your two lines:
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
Aren't needed. You don't need to re-read the User, or write anything to the session - just grab the client_id directly from Auth any time you need it.
In fact, if you read http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#accessing-the-logged-in-user it even says you can get it from outside the context of a controller, using the static method like:
AuthComponent::user('client_id')
Though it doesn't seem you'll be needing that.
You could also apply the client_id condition to all finds for a Model by placing something in the beforeFind function in the Model.
For example, in your User model, you could do something like this:
function beforeFind( $queryData ) {
// Automatically filter all finds by client_id of logged in user
$queryData['conditions'][$this->alias . '.client_id'] = AuthComponent::user('client_id');
return $queryData;
}
Not sure if AuthComponent::user('client_id') works in the Model, but you get the idea. This will automatically apply this condition to every find in the model.
You could also use the beforeSave in the model to automatically set that client_id for you in new records.
My answer may be database engine specific as I use PostgreSQL. In my project I used different schema for every client in mysql terms that would be separate database for every client.
In public schema (common database) I store all data that needs to be shared between all clients (objects that do not have client_id in your case), for example, variable constants, profile settings and so on.
In company specific models I define
public $useDbConfig = 'company_data';
In Controller/AppController.php beforeFilter() method I have this code to set schema according to the logged in user.
if ($this->Session->check('User.Company.id')) {
App::uses('ConnectionManager', 'Model');
$dataSource = ConnectionManager::getDataSource('company_data');
$dataSource->config['schema'] =
'company_'.$this->Session->read('User.Company.id');
}
As you see I update dataSource on the fly according to used company. This does exclude any involvement of company_id in any query as only company relevant data is stored in that schema (database). Also this adds ability to scale the project.
Downside of this approach is that it creates pain in the ass to synchronize all database structures on structure change, but it can be done using exporting data, dropping all databases, recreating them with new layout and importing data back again. Just need to be sure to export data with full inserts including column names.
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!