FuelPHP: Extending OrmAuth user model (need custom profile fields) - php

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
);

Related

TYPO3: Extend model and map to existing table

I need to create a custom FE user with some custom fields.
Also, it needs to be assignable through the frontend to different user groups.
You can find my first approach here. Didn't work out that well.
Second approach was to create another extension and follow the guide which is shown here.
First thing I did was to add \TYPO3\CMS\Extbase\Domain\Model\FrontendUser into the Extend existing model class-field for my CustomFEU-model.
Then I created another model which I named FEgroup and I mapped it to the table fe_groups. After that, I connected an n:m relation to the CustomFEU.
When I try to create a new CustomFEU with the new action, it returns a white empty page after submitting the form and no user is being added.
The only strange thing I found was that the /Classes/Domain/Repository/ folder is empty.
TYPO3 7.6.8
Although I didn't edit the files yet, here they are:
Model / Controller / Setup
Did anyone encounter similar problems?
First you need to create the repositories that handle the new user and usergroup models.
Second you try to save the user with $this->customFEURepository->add($newCustomFEU); and the variable customFEURepository does not exist. It would be the best to inject it, it has to be the repository that you should create first. You can inject it like that:
/**
* CustomFEUController
*/
class CustomFEUController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
/**
* #var \Vendor\Feregistration\Repository\CustomFEURepository
* #inject
*/
protected $customFEURepository;
// other code ...
}
Don't forget to clear the system cache after adding inject annotations, otherwise it wont work.
Last but not least i can't see the mapping to the database table for your model. You need to add it to your TypoScript (setup.txt)
config.tx_extbase.persistence.classes {
Vendor\Feregistration\Domain\Model\CustomFEU {
mapping {
recordType = 0
tableName = fe_users
}
}
Vendor\Feregistration\Domain\Model\FEGroups {
mapping {
recordType = 0
tableName = fe_groups
}
}
}

Testing existence of Eloquent relationship and creating if it doesn't exist

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
]);
});
}
}

PHP OOP - best way to implement 2 classes

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]

Best way to filter access to controller actions according to a specific client id

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.

Yii unit-tests fixtures not accessible in tests

config/test.php
'components'=>array(
'fixture'=>array(
'class'=>'system.test.CDbFxtureManager'
),
),
tests/unit/EntityTest.php (extends CDbTestCase)
public $fixtures = array('entities'=>'Entity'),
tests/fixtures/Entity.php
return array(
'entity1'=>array('slug'=>'slug1', 'title'=>'title1'),
'entity2'=>array('slug'=>'slug2', 'title'=>'title2'),
);
Now, in EntityTest class I try to get my entities
$entities = $this->entities;
$entity = $this->entities('entity1');
Output is 'Unknown property "entities" for class "EntityTest"'. Testing class is 'Entity', table name in database is 'tbl_entity', 'tablePrefix' option of 'CDbConnection' component is set to 'tbl_'
Let's go necroposting! :)
Your problem is the naming of the file in fixtures. You mention that it's a tests/fixtures/Entity.php, which should be after the name of your Model, class, but in fact, files in fixtures folder should be named after tableNames of your Models. I believe the real table name for your Entity model is entity (all in lower case), so just rename the file. I had the similar problem several times, and the solution is clearly stated in the documentation: "A fixture is represented as a PHP script whose name (without suffix) is the same as the table name (if schema name is needed, it should be prefixed to the table name)."
Also try this if you are using setUp() method. Just call parent::setUp(), when you are redefining it with your own method.
$entity = $this->entities['entity1']; // returns an array
$entity = $this->entities('entity1'); // returns an active record
Take a look on this solution Yii Fixtures Issue? I also came up with the same problem. I overlook the class to extend, make sure to extend the CDbTestCase.

Categories