I have a simple DB with user data table, a controller data table and user_controller table that have the controllerID and the userId.
In my PHP OOP (first time) implementation i have a class User and a class Controller.
The functionality for the user is simple - he can login and logout and see a table of all his controllers.
I have an admin user who can see all users and their controllers, he can add/delete/edit controller to a user and also can add\delete\edit users.
The Login functionality is implemented (DB class, user class and helpers class)
My problem is how and where should i build the functionality to add a controller to a user.
Controller has an id (unique), a name(not unique) and password.
User private fields are:
private $_db,
$_data, //all of this user data
$_sessionName,
$_cookieName,
$_isLoggedIn;
and controller class:
private $_db,
$_data; //all of this controller data
I tried to implement function Create in controller:
public function create($fields = array()){
if(!$this->_db->insert('controllers',$fields)){
throw new Exception("Problem creating new controller");}}
and function createController in the user:
public function addController($pass,$controller_name,$lat='',$lon=''){
if ($pass && $controller_name){
$controller = new Controller();
$salt = Hash::salt(32);
try{
$controller->create(array(
'pass' => Hash::make($pass,$salt),
'salt' => $salt,
'controller_name' => $controller_name,
'lat' => $lat,
'lon' => $lon
));
//if success then we update user-controller table
$controller_id = $controller->find($controller_name)->data()->id;
$userID = $this->data()->ID;
$fields = array(
'UserID' => $userID,
'ControllerID' => $controller_id
);
if(!$this->_db->insert('user_controllers',$fields)){
throw new Exception("Problem creating controller for user");
}
}
catch(Exception $e){
throw new Exception('There was a problem creating new controller');
}
} else {
throw new Exception('No name or password had been given');
}
Generally it is working. But there have been couple problems:
The id of the controller is created automatically by the DB so i dont know it when i create it. I cant later find the controller by name because i can have a lot of controllers with this name
I am not sure this is the proper way to handle the relationship between user and controller
i have trouble of implementing "show me all your controllers" for the user.
Can you please tell me what is the correct way to implement this relationship and how to make the createController and ShowControllers of the user?
Your structure seems to be correct. You need a users table, a controllers table and a user_controller table as you mentioned which maps the relationships between the two (user has many controllers).
Solutions for your problems:
When you insert data, you need to retrieve the inserted ID of the new record to use in your mapping relationship. I'm not sure in your example if you're written this MVC structure yourself or otherwise; it looks a little like CodeIgniter. If it is a CI application, here's an example of how to get the last insert ID. If it's a bespoke application, you can use $mysqli->insert_id (mysqli) or $pdo->lastInsertId(); for PDO.
What you're doing is correct.
In a "show me all your controllers" for the user scenario, you will query the user_controller table to get a list of all the controller relationships, and you'll use a join (inner might suit, for a mandatory successful join). You specify that you want to return controller.* in your select fields, and you'll end up with a record for each controller and ignore the user controller fields (if you don't need them). An example SQL query might look like this:
SELECT controller.* FROM user_controller UC
INNER JOIN controller ON controller.id = user_controller.controller_id
WHERE user_controller.user_id = [YOUR_USER_ID_HERE]
Related
What would be the best way to create a relationship if it doesn’t exist already, within Eloquent, or at least a central location.
This is my dilemma. A User must have a Customer model relationship. If for whatever reason that customer record doesn’t exist (some bug that stopped it from being created) - I don’t want it to throw errors when I try to retrieve it, but I also request the customer object in multiple locations so I don’t want to test for existence in all those places.
I thought of trying the following in the User model:
public function getCustomerAttribute($value) {
// check $value and create if null
}
But that doesn’t work on relationships, $value is null.
EDIT
I already create a customer upon user creation, but I have come across a situation where it wasn't created and caused exceptions in many places, so I want to fallback.
User::created(function($user) {
$customer = Customer::create([
'user_id' => $user->id
]);
});
Is it possible for you to assume when a user is created that a customer needs to be created as well? If the rest of your system depends on this assumption I would make a model event.
use App\{User, Customer}; // assuming php7.0
UserServiceProvider extends ServiceProvider
{
/**
* Boot
*/
public function boot()
{
// on a side note, we're using "created" not "creating" because the $user->id needs to exist in order to save the relationship.
User::created(function($user) {
$customer = Customer::create([
'user_id' => $user->id
]);
});
}
}
I'm working in a Webapp and I have a problems to work with a intermediate table, these are my tables in mysql:
User:
Integer:id
String:name
String:email
String:phone
Exercise:
Integer:id
String:name
String:description
User_Exercise:
Integer:id
Integer:id_user
Integer:id_exercise
Integer:record
So, what I want to do is that when I create an exercise, it be created one row for each user with the exercise-id that I have created it before. Later the user could change his record in this exercise.
I have thought to create a model to handle the user_exercise's table but I don't know if there is some way to do this better or not.
So, There are some way to do this without create a new model?
PD: Sorry for my terrible english
You don't need a seperate model for User_Exercise
You can use $this->belongsToMany from base Model i.e., User
Note :
For insert process you can get the parent id by
$insertUser = User::create($userData);
then
$insertUser->id for taking the last insert id
And then to retrieve with respect to User_Exercise you shall use $this->belongsToMany from your User Model
Example
Have this in your User Model
public function getUser() {
return $this->belongsToMany('App\User', 'excercise_name', 'user_id', 'excercise_id')->select(array('exercise.id', 'excercise.name'));
}
And Get the data you need from any Controller like this
$userData = User::find($userId)->getUser;
I'm learning FuelPHP and try to use OrmAuth to handle the authentication and authorisation processes. I was able to generate all the "standard" OrmAuth tables (user, users_permissions, users_metadata and so on). However, I don't understand what is the right way to add custom fields to the user object (e.g., telefon_number). Can someone help me out here and give some examples?
Thank you.
First, what you want is easily achievable with the EAV container already configured in your user model (from the OrmAuth package). You just have to set any metadata on the model, and save it, like that:
$user = Auth_User::find(1);
$user->telefon_number = '+36 30 taratatta-taratatta';
$user->save();
That'll put the telefon_number in your users_metadata table, but when you query a user, it'll be automatically available on that model instance.
However, sometimes that's just not sufficient, for example when you want to build advanced queries using these properties. Than you might want to have the field to be present in the users table.
What I did to achieve this is documented under the extending the core part of the documentation. Basically I extended the \Auth\Model\Auth_User class from the OrmAuth package, like this:
namespace Model;
class Auth_User extends \Auth\Model\Auth_User
{
/**
* #var array model properties
*/
protected static $_properties = [
// your properties go here
];
}
Now, in your app's bootstrap.php, you have to tell the autoloader to use this class instead of the one in the OrmAuth package:
\Autoloader::add_classes(array(
// Add classes you want to override here
// Example: 'View' => APPPATH.'classes/view.php',
'Auth_User' => APPPATH.'classes/model/auth/user.php',
'Model\\Auth_User' => APPPATH.'classes/model/auth/user.php',
));
Note: This example sets both Auth_User and Model\Auth_User, but one might just be enough, depending on your needs.
To extend user profile fields in fuelphp go to.
PKGPATH//auth/classes/auth/login/ormauth.php # line 225
change the line
public function create_user($username, $password, $email, $group = 1, Array $profile_fields = array())
to something like
public function create_user($firstname, lastname, $username, $password, $email, $group = 1, Array $profile_fields = array())
go on to your code and add
$user = Auth::create_user(
$firstname,
$lastname,
Input::post('username'),
Input::post('password'),
Input::post('email'),
1,//group id
);
I have two models 'a' and 'b' and a->hasMany('b') and b->belongsTo('a')
So when I create one 'b' this should belongs to exactly one 'a'.
The problem is with the usual Route::resource('b', 'bController') I can just create this 'b' and don't know to which 'a' this belongs.
I tried editing the create() method in bController to create($id) but
Redirect::action('bController#create', $a->id)
Still redirects to /b/create?2 an gives an error
Missing argument 1 for bController::create()
Maybe a bit easier to unserstand when I use phoneand user.
Every phone belongs to one user.
How can I create a phone? How do I give the create() the parameter of the user and still use the Route::resource?
I think you are on a wrong direction because User and Phone are two models and hence a User has many phones so here Phone model is a child model (related) of User class and a Phone can't exist without a User so you only need to create a User through the controller and Phone will be created when the User gets created, for example:
Route::resource('users', 'UserController');
Now assume that you have tow models as User and Phone and the User model has phones method which builds the relationship (phones = User->hasMany('Phone')) and the Phone model has a user method which builds the relationship (user = Phone->belomgsTo('User')).
// User model
class User extends Eloquent {
//...
public function phones()
{
return $this->hasMany('Phone');
}
}
// Phone model
class Phone extends Eloquent {
//...
public function user()
{
return $this->belongsTo('user');
}
}
Now to create a User the store method will be used like this:
// UserController.php
class UserController extends BaseController {
// Other methods
// Creates a User
// URI: /users Method: POST
public function store()
{
// Create a User using User model
$user = User::create(...);
if($user) {
// Initialize/Create a Phone
$phone = new Phone(array(...));
if($phone) {
// Save the Phone and relate with User
$user->phones()->save($phone);
}
}
}
}
This is the basic idea and the final thing is that, a Phone doesn't require a Controller to be created, because it's the part of a User so when you create a new User then create a (or more) Phone from the UserController after you create a User or update a Phone when you update a User.
So if you want to load a User with it's related Phone models then you may do it like this:
$user = User::with('phones')->find(1);
So, when you load a user, you can load all the phones related with that user so during editing of an user; you only need to load a User with related Phone models and pass that model to the view.
To add a new Phone to an existing User you need to edit the User model so you can load a User model with phones and pass that User model to the view for editing. When new Phone being added to the user, you only need to attach that Phone with existing User, that's it. Hope it makes sense.
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.