I'm writing my first real MVC-application with PHP. I'm not using a framework because my application is so small that I figured it would be faster to write everything from scratch.
In my app, users can register, login and then write/edit/delete content. Each piece of content references its owner by a userid-column in the database.
I am now about to implement user access restrictions (in the sense that users can only view/edit/delete their OWN content/items/models). I'm wondering where the check for "valid access" should happen and where user-objects are instantiated.
I mean, I definitely need information about the current user in controllers, models and views. So I'm thinking if it's viable to have a global user object (defined in index.php) that stores all the user information so I could access it comfortably from each part of my application.
At the moment, this snippet grants my controllers access to user information which I then also store in the data-array that is passed to the view:
class Controller {
protected $data, $id, $user;
public function __construct($action = null, $data = null) {
if (User::isLoggedIn()) {
$this->user = new User($_SESSION['user']);
$this->data['user'] = $this->user;
}
}
}
Following this pattern, I'd have to pass on user information to each model I create or alternatively have them instantiate their own user-object.
What is the way to go here? Global user object, instantiation in each model or passing the user-object as a parameter to models and views?
Thank you for your help!
There are several things to note here. Firstly: "I then also store in the data-array that is passed to the view:"
In MVC, the view has direct access to the model, see my answer here How is MVC supposed to work in CodeIgniter for an overview of that.
Secondly, your question is really about Dependency Management. Globals (and by extension statics) are problematic ( Make all variables global, PHP , Are global variables bad? , Static methods or not? ). The preferred method is passing the fully-constructed $user object into class which requires it.
class Controller {
protected $data, $id, $user;
public function __construct(User $user) {
if ($user->isLoggedIn()) {
//...
}
}
}
However, in this case you can't have a fully-constructed user object because you need to know whether the user is logged in prior to constructing the object so pass a mapper, factory or DAO object into the constructor and create the user as needed.
class Controller {
protected $data, $id, $user;
public function __construct(DataMapper $userMapper) {
if (isset($_SESSION['user'])) {
$user = $userMapper->find($_SESSION['user']);
}
}
}
Finally, however, if you take my first point, that Views can access their model directly and don't get passed data by their controller, then your controller may not even need to know whether the user is logged in. This is, after all, domain logic so belongs in the model.
Related
I realize this topic has been asked and addressed repeatedly, and although I've read through countless similar questions and read through countless articles, I'm still failing to grasp a few key concerns... I'm attempting to build my own MVC framework for learning purposes and to better familiarize myself with OOP. This is for personal private use, not to imply that as an excuse for being lazy, but rather I'm not so concerned with having all the bells and whistles of more robust frameworks.
My directory structure is as follows:
public
- index.php
private
- framework
- controllers
- models
- views
- FrontController.php
- ModelFactory.php
- Router.php
- View.php
- bootstrap.php
I have an .htaccess file with directs all requests to index.php, this file contains basic configuration settings such as timezone and global constants, and it then loads the bootstrap.php file. The bootstrap contains my autoloader for classes, starts the session, defines global functions to use throughout my project, and then calls the router. The router picks apart the request from the URL, validates it using a ReflectionClass, and executes the request in the form of example.com/controller/method/params.
All of my controllers extend the FrontController.php:
<?php
namespace framework;
class FrontController
{
public $model;
public $view;
public $data = [];
function __construct()
{
$this->model = new ModelFactory();
$this->view = new View();
}
// validate user input
public function validate() {}
// determines whether or not a form is being submitted
public function formSubmit() {}
// check $_SESSION for preserved input errors
public function formError() {}
}
This front controller loads the ModelFactory:
<?php
namespace framework;
class ModelFactory
{
private $db = null;
private $host = 'localhost';
private $username = 'dev';
private $password = '********';
private $database = 'test';
// connect to database
public function connect() {}
// instantiate a model with an optional database connection
public function build($model, $database = false) {}
}
And base View:
<?php
namespace framework;
class View
{
public function load($view, array $data = [])
{
// calls sanitize method for output
// loads header, view, and footer
}
// sanitize output
public function sanitize($output) {}
// outputs a success message or list of errors
// returns an array of failed input fields
public function formStatus() {}
}
Finally, here is an example controller to demonstrate how a request is currently processed:
<?php
namespace framework\controllers;
use framework\FrontController,
framework\Router;
class IndexController extends FrontController implements InterfaceController
{
public function contact()
{
// process form if submitted
if ($this->formSubmit()) {
// validate input
$name = isset($_POST['name']) && $this->validate($_POST['name'], 'raw') ? $_POST['name'] : null;
$email = isset($_POST['email']) && $this->validate($_POST['email'], 'email') ? $_POST['email'] : null;
$comments = isset($_POST['comments']) && $this->validate($_POST['comments'], 'raw') ? $_POST['comments'] : null;
// proceed if required fields were validated
if (isset($name, $email, $comments)) {
// send message
$mail = $this->model->build('mail');
$to = WEBMASTER;
$from = $email;
$subject = $_SERVER['SERVER_NAME'] . ' - Contact Form';
$body = $comments . '<br /><br />' . "\r\n\r\n";
$body .= '-' . $name;
if ($mail->send($to, $from, $subject, $body)) {
// status update
$_SESSION['success'] = 'Your message was sent successfully.';
}
} else {
// preserve input
$_SESSION['preserve'] = $_POST;
// highlight errors
if (!isset($name)) {
$_SESSION['failed']['name'] = 'Please enter your name.';
}
if (!isset($email)) {
$_SESSION['failed']['email'] = 'Please enter a valid e-mail address.';
}
if (!isset($comments)) {
$_SESSION['failed']['comments'] = 'Please enter your comments.';
}
}
Router::redirect('contact');
}
// check for preserved input
$this->data = $this->formError();
$this->view->load('contact', $this->data);
}
}
From what I'm able to understand, my logic is off for the following reasons:
Validation should be done in the Model, not the Controller. However, a Model should not have access to $_POST variables, so I am not entirely sure whether or not I'm doing this part correctly? I feel like this is what they call a "fat controller" which is bad, but I'm not sure what needs to change...
The controller should not send data to the View; instead, the View should have access to the Model to request its own data. So would moving the $data property out of the FrontController and into the ModelFactory, and then calling the View from the Controller without passing along data resolve this issue? Technically it would then adhere to the MVC flowchart, but the proposed solution seems like such an insignificant or even trivial detail, assuming it's that simple, which it probably isn't..
The part that has me questioning my whole implementation is that I have a User object that is instantiated with a users corresponding roles and permissions, and I've been trying to figure out how or more specifically where to create an isAllowed() method that can be called from both the Controller and View. Would it make sense then to put this method in the Model, since both the Controller and View should have access to the Model?
Overall, am I on the right track here or what glaring problems do I need to address in order to get on the right track? I'm really hoping for a personal response specific to my examples rather than a "go read this".. I appreciate any honest feedback and help.
The $_POST superglobals should be abstracted by a request instance, as explained in this post.
Input validation is not the responsibility of the controllers. Instead it should be handles by domain objects within model layer.
Model factory is no a model.
Defining class parameter visibility as public breaks the object's encapsulation.
HTTP location header (redirect) is a form of response. Thus, it should be handled by view instance.
In its current form, your controllers are directly manipulating superglobals. This causes tight coupling to the globals state.
Authorization checks should be performed outside controller. Not inside of it.
Your "model factory" should instead be a service factory, that is injected in both controller and view. It would ensure that each service is instantiated only once and thus let your controllers work with same model layer's state.
First, I think it's great you are trying to create you own framework. Many say that everyone should do this, if only for learning purposes.
Second, I would suggest you read this Wikipedia article on frameworks. Many people don't realize there are different patterns for routing (url dispatch, traversal), and views (push, pull).
Personally, I don't see a need to abstract out the super globals since they are already abstractions (by php) from the raw input (php://input) and can be modified. Just my opinion.
You are right that validation should be done by the Model. You don't validate forms, you validate data. As for the View accessing the data, that depends on the pattern you choose. You can push the data to the View, or the View can pull the data.
If you are curious, my attempt at a MVC basic framework is on github. Its 4 files, less 2K line of code (DB layer is 1K lines). It implements traversal (component) routing and pulls data, there were already plenty of frameworks that implement the alternate patterns.
Validation should be done in the Model, not the Controller. However, a
Model should not have access to $_POST variables, so I am not entirely
sure whether or not I'm doing this part correctly? I feel like this is
what they call a "fat controller" which is bad, but I'm not sure what
needs to change...
That is right, Model should know nothing about the request, so, you need to pass $_POST to the models, but it will not know that are request parameters.
One thing: validation that has nothing to do with business logic should stay in controller. Let's say, you create a CSRF token for your forms for security reasons, this valitadion should be inside the controller, because it handles requests.
The controller should not send data to the View; instead, the View should have access to the Model to request its own data. So would moving the $data property out of the FrontController and into the ModelFactory, and then calling the View from the Controller without passing along data resolve this issue? Technically it would then adhere to the MVC flowchart, but the proposed solution seems like such an insignificant or even trivial detail, assuming it's that simple, which it probably isn't..
That's not necessarily true. This approach is called Active Model, and normally you use the Observer pattern, with models being observed by the views. If some model changes, it notifies the view that will update itself. This approach is more suitable for desktop applications, not web-based ones. In web applications, the most common is to have controllers as intermediaries between model and view (Passive Model). There's no right approach, you should choose the one you like the most.
The part that has me questioning my whole implementation is that I have a User object that is instantiated with a users corresponding roles and permissions, and I've been trying to figure out how or more specifically where to create an isAllowed() method that can be called from both the Controller and View. Would it make sense then to put this method in the Model, since both the Controller and View should have access to the Model?
Well, this one there's no way, I'll have to tell you to read about ACL.
When a user logs into my site, I create an instance of my User class, fetch some user-related data and store the object in the SESSION.
Some of the data I fetch from the database should be constant throughout the session AND I want the data to be accessible from other objects. I prefer using User::$static_value_in_class to $_SESSION['static_value_in_session'] when using the value from within another object, but I'm open to persuasion.
The problem is, the values aren't remembered when I serialize my User instance into the SESSION, then load a different page.
Class definitions:
class User {
public $name;
public static $allowed_actions;
public function __construct($username, $password) {
// Validate credentials, etc.
self::$allowed_actions = get_allowed_actions_for_this_user($this);
}
}
class Blog {
public static function write($text) {
if (in_array(USER_MAY_WRITE_BLOG, User::$allowed_actions)) {
// Write blog entry
}
}
}
login.php:
$user = new User($_POST['username'], $_POST['password']);
if (successful_login($user)) {
$_SESSION['user'] = $user;
header('Location: index.php');
}
index.php:
if (!isset($_SESSION['user'])) {
header('Location: login.php');
}
Blog::write("I'm in index.php! Hooray!")
// Won't work, because Blog requires User::$allowed_actions
Should I implement Serializable and write my own version of serialize() and unserialize() to include the static data?
Should I bite my lip and access the $_SESSION variable from within the Blog class?
Should I require a valid User instance sent to the Blog write() method?
Or maybe the internets has a better idea...
EDIT: Writing my real use case (not full code, but enough to get the gist).
My site handles groups of users with shared budget accounts.
Users may spend group money on certain things the group agreed upon, and they report transactions by creating instances of the Transaction class and sending it to the Bank class for database storage.
Bank class:
class Bank {
// Group-agreed reasons to spend money
public static $valid_transaction_reasons;
public function __construct(User $user) {
Bank::$valid_transaction_reasons = load_reasons_for_this_group($user->bank_id);
}
}
User class:
class User {
public $bank_id;
public function __construct($username, $password) {
$query = "SELECT bank_id FROM users WHERE username=$username AND password=$password";
$result = mysql_fetch_array(mysql_query($query));
$this->bank_id = $result['bank_id'];
}
}
Transaction class:
class Transaction {
public function __construct($reason, $amount) {
if (!in_array($reason, Bank::$valid_transaction_reasons)) {
// Error! Users can't spend money on this, the group doesn't cover it
}
else {
// Build a Transaction object
}
}
}
Actual code (login.php, or something):
$user = new User($_GET['uname'], $_GET['pword']);
$_SESSION['bank'] = new Bank($user);
// Some shit happens, user navigates to submit_transaction.php
$trans = new Transaction(REASON_BEER, 5.65);
// Error! Bank::$valid_transaction_reasons is empty!
As I mentioned in the comment, this is more a software design question than a question how to achieve this with PHP.
A static property is not part of the state of an object and will therefore not being serialized with it.
I'll give you a short example how I would solve a related problem. Imagine you have the following message class, that has a static $id property to make sure all instances have a unique id:
class Message {
public static $id;
public $instanceId;
public $text;
/**
*
*/
public function __construct($text) {
// the id will incremented in a static var
if(!self::$id) {
self::$id = 1;
} else {
self::$id++;
}
// make a copy at current state
$this->instanceId = self::$id;
$this->text = $text;
}
}
Serialization / Unserialization code:
$m1 = new Message('foo');
printf('created message id: %s text: %s%s',
$m1->instanceId, $m1->text, PHP_EOL);
$m2 = new Message('bar');
printf('created message id: %s text: %s%s',
$m2->instanceId, $m2->text, PHP_EOL);
$messages = array($m1, $m2);
$ser1 = serialize($m1);
$ser2 = serialize($m2);
$m1 = unserialize($ser1);
printf('unserialized message id: %s text: %s%s',
$m1->instanceId, $m1->text, PHP_EOL);
$m2 = unserialize($ser2);
printf('unserialized message id: %s text: %s%s',
$m2->instanceId, $m2->text, PHP_EOL);
To make sure that the id is unique across multiple script runs further work is nessary. You'll have to make sure that Message::$id is initialized before any object creation, using the value from last script run. This will get additionally wired when it comes to parallel PHP request on a webserver.
Its just an example with the simplest static property I know: an instance counter. In this case I would do so. But I hope you see that there is further work required to serialize / unserialize static properties without have side effects. And this depends on your application needs.
This question cannot be answered general I tend to say it makes no sense in any case to serialize static members. But I would appreciate comments on this.
Some of the data I fetch from the database should be constant throughout the session AND I want the data to be accessible from other objects.
If the data is really constant, then make them a constant.
If the data is not constant, consider whether they belong to the individual users (the object instances) or the User as the general concept (which is what a class is).
Should I implement Serializable and write my own version of serialize() and unserialize() to include the static data?
It does not make sense to store static members in the serialized object's string because they are independent from each other. Storing them would be a snapshot of the class state at the time the object was serialized.
Consider the following code snippet:
$user = new User;
$user::$allowed_actions = 'foo';
$string = serialize($user);
unset($user);
Now imagine some other part of your code does this:
echo User::$allowed_actions;
It still gives "foo" despite no object being in memory at the moment. That is because it's a static member. It's class state.
Now imagine you do this:
User::$allowed_actions = 'bar';
If you do unserialize the object now what should $allowed_actions be? Foo or Bar?
$user = unserialize($string);
echo $user::$allowed_actions;
The output should and would be "bar", because static members are about the class. The fact that we created, destroyed and brought back an object from it is irrelevant. It's all state of the class we changed here.
Also, take into account that statics are death to testability and you want to avoid them when possible. After all, it's called OOP not Class-Oriented-Progamming.
Should I bite my lip and access the $_SESSION variable from within the Blog class?
No, you should not access any of the superglobals anywhere but write abstractions for each of them or rather for the data inside them. They are merely input sources. In case of $_SESSION what you want to do is get all the data you need for that particular request right in your bootstrap and then pass the data around instead, e.g. recreate the user and pass that around.
Should I require a valid User instance sent to the Blog write() method?
In general, methods should be on the objects with the most information to fulfill an action. Whether that applies to your Blog::write I do not know. If the allowed_actions are part of the User instance, then probably yes, you should likely require a valid User instance.
Or maybe the internets has a better idea...
Another option would be to put the permissions into a dedicated Permissions object, holding the user role and it's permission. You could then lookup the permission from that list by passing in a User object. Search for Access Control Lists (ACL) for more info on possible implementations.
EDIT: Writing my real use case (not full code, but enough to get the gist).
If your concern is simply that Bank::$valid_transaction_reasons could be empty, then don't store Bank in the Session at all but only load it from the user when you run the transaction, e.g. create the Bank instance in submit_transaction.php (create it when you need it). That way you will never run into an error.
I have this User class
class User{
private $logged = false;
private $id;
public function User() {
//> Check if the user is logged in with a cookie-database and set $logged=true;
}
public function isLogged() {}
public function editPerms() {}
//> other methods
}
Well now considering I can't have more than 1 user logged (of course because we are talking for a single http request) in Where should i store the ref of my istance?
This is the case where singleton would be useful but these days everyone say singleton is evil (like static methods).
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
I could do a $GLOBALS['currentUser'] = new User(); and having it accesible everywhere but I think this is worse than a singleton.
So what Can I do?
Please note I don't need to save this instance between requests. I just need a way to access this instance in my framework within the same request.
If you want to know what i do now for all of my Helper Objects is a Service Container (that's considered as well bad):
function app($class) { //> Sample
static $refs = array();
if (!isset($refs[$class]))
$refs[$class] = new $class();
return $refs[$class];
}
//> usage app('User')->methods();
(IE what symfony does)
Patterns are supposed to be a helpful guide, like a library of previously successful software abstractions. Too often these days people view patterns as being some kind of religion where things are either "right" or "wrong" regardless of the context of the program.
Think about what you want to achieve and map in out in a way that makes sense to you. Fuggering about with minute distinctions between this pattern and that pattern misses the point, and it won't get your program written. Learn by doing!
HTH.
Singletons are not evil. Bad usages of singletons are evil. The reason people have come to dislike this pattern so much (even going to the extent of calling it an anti-pattern, whatever that is), is due to improper use:
Too many inexperienced people make a class a singleton when they find they don't need more than one instance of a class. But the question isn't if you need only a single instance of the class, but whether more than one instance would break your code. So ask yourself this question: would your code break if there were more User instances? If not, then maybe you shouldn't bother. :)
There are legitimate uses of singletons. There are those people who fear this pattern like the plague and consider it always to be bad, without realizing that sometimes it can be very helpful. In the words of a much more experinced programmer than me, "singletons are like morphine: they can give you a real boost, but use them the wrong way and they an become a problem themselves". If you want me to go into some details as to when singletons could be a good choice, leave a comment to this answer. :)
It is always hard to answer architectural questions without the context. In this case it is pretty important how the User objects are persisted (where do they come from?) and how is the client code organized. I will assume a MVC architecture because it's trendy this days. Also I suppose your user objects will have more responsibility as only authentication (you mention some permission control here, but it's still not clear enough).
I would push the authentication responsibility to a service and just pass it around as needed. Here is some sample code.
class AuthenticationService {
/**
* #var User
*/
private $currentUser;
public function __construct(Request $request) {
// check if the request has an user identity
// create a user object or do nothing otherwise
}
public function getCurrentUser() {
return $this->currentUser;
}
}
class User {
public function editPerms(){}
}
// the client code
class Controller {
private $auth;
public function __construct(AuthenticationService $auth) {
$this->auth = $auth;
}
public function handleRequest() {
$currentUser = $this->auth->getCurrentUser();
if ($currentUser === null) { // of course you could use Null Object Pattern
// no user is logged in
}
// do something with the user object
}
}
So the answer to your question is: you need proper dependency injection through out your whole application. The only object you get from the server is a request. The dependency injection container injects it into the AuthenticationService and the latter gets injected into your controller. No singletons, no static methods, no global variables. The dependencies are tracked in the DI container and are injected as needed. Also the DI container makes sure your service is instantiated only once.
The article "Container-Managed Application Design, Prelude: Where does the Container Belong?" may clarify some DI concepts.
Not sure why all the arguing up top. Seems like a perfectly reasonable question to me.
The key here is to use static members of the User class. Static methods are your friends, regardless of what some may say:
class User
{
private $logged = false;
private $id;
private static $_currentUser;
public static function currentUser()
{
if (empty(self::$_currentUser))
{
#session_start();
if (array_key_exists('current_user', $_SESSION))
{
self::$_currentUser = $_SESSION['current_user'];
}
else
{
// force login in or whatever else.
// if you log in, make sure to call User::_setCurrentUser();
return null; //or some special 'empty' user.
}
}
return self::$_currentUser;
}
// you may consider making this public, but it is private because it is a bit
// more secure that way.
private static function _setCurrentUser(User $user)
{
self::$_currentUser = $user;
$_SESSION['current_user'] = $user;
}
public function User() {
//> Check if the user is logged in with a cookie-database and set $logged=true;
}
public function isLogged() {}
public function editPerms() {}
//> other methods
}
// Usage
$pUser = User::currentUser();
The influence of Misko Hevery is pretty strong on me. So is his newable - injectable distinction. A user is not an injectable but a newable. What are the responsibilities of a user: should he be able to tell of himself whether he is logged in or not? There's a post of him where he talks about a similar problem: a credit card and charging it(self?). It happens to be a post about singletons, what you would like to make it:
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
That would leave it to an service to check whether the user is logged in or not, what rights he has on the site.
It also means your architecture would change, your problem will become different (passing around the user?, where is it needed?, how will you have access to the 'checking user is logged in' service, ...).
since everyone else is weighing in on this, singletons are not evil.
i even read the "liars" article, and he's using a contrived example of non-modular design and poor dependency inheritance.
i think you should consider a singleton factory pattern, where a singleton factory (Auth) provides a login() method which returns a User class, as well as methods for saving state between HTTP requests on that User.
This will have the benefits of separating the security and session functionality from the User functionality. Additionally using the factory, you can have multiple types of users without the rest of the system needing to understand which object to request before the db is examined
class auth {
private static $auth = null;
private $user = null;
// must use getAuth();
private __construct(){};
public getAuth() {
if (is_null($this->auth) {
$this->auth = new auth();
}
return $this->auth;
}
public function login($user,$pass) {
... // check db for user,
if ($dbrow->user_type == 'admin') {
$this->user = new admin_user($dbrow);
} else {
$this->user = new normal_user($dbrow);
}
$this->user->setSession($db->getsession());
}
public function getUser() {
return $this->user;
}
public function saveSession() {
// store $this->user session in db
}
public function saveUser() {
// store $this->user changes in db
}
...
}
the user class itself become a data structure, simply enforcing security and business rules, and maybe formatting some data for output purposes.
class normal_user extends user {
... getters and setters
public function getName() {}
public function setEmail() {}
public function setprofile() {}
}
all db, state and security concerns are centralized in the auth.
the only way to create a user object (legally) is to run auth->login().
you are still allowed to do
$me = new normal_user();
$me->setName();
echo $me->getName();
but there is no way for a new coder to save this in the db since it's not referenced in $auth->user;
you can then create a function in auth to consume user objects to create new users (on signup)
...
public function create(user $user) {
// validate $user
$this->user = $user;
$this->saveUser();
}
...
you just need to make sure you run the save functions at the end of execution...
possibly in a destructor()
simple
Our development team is debating a general best practice:
Is it better to access a session variable directly from a function in a model class or pass the session variable from the controller as an argument to a function in the model class. Look at the two examples below:
Accessing session variable directly from the model class to use in a query:
class MyModel {
public function getUserPrefs($userID) {
$this->query("SELECT * FROM my_table WHERE id=$_SESSION['userID']");
}
}
Or pass the session variable from the controller to a function in the model class as a function argument:
class MyController {
public function displayUsers() {
$this->model->getUserPrefs($_SESSION['userID']);
}
}
class MyModel {
public function getUserPrefs($userID) {
$this->query("SELECT * FROM my_table WHERE id=$userID");
}
}
The reasoning for passing it to the model from the controller is so all data that is referenced is coming from one point of entry, that being the controller.
What is recognized as a better practice?
The second version (passing $_SESSION['userId'] as an argument to the method) results in a more decoupled class, and therefore more flexible. Go with it.
You NEVER want to have session variables in your model. You should always pass these variables as parameters to the function in the model. This also makes your code more expandable and flexible. Consider a model that gets a user by their id. You may write a function like:
function find_by_id() {
// SELECT * FROM Users WHERE user_id = $_SESSION['user_id'];
}
However, what if you now what to build an admin functionality with a user lookup feature? Your model is hardcoded to use the session's user_id, but you want to be able to pass your own id. You would be better off:
function find_by_id($id) {
// SELECT * FROM Users WHERE user_id = $id
}
and in your controller
$user = Model::find_by_id(1);
//or
$user = Model::find_by_id($_SESSION['user_id']);
//etc
In this case, however, I would really consider making your code even MORE flexible:
function find($ids) {
// this is pseudo code, but you get the idea
if(is_array($ids))
$ids = implode(',', $ids); // if an array of ids was passed, implode them with commas
SELECT * FROM Users WHERE user_id IN ($ids);
}
This allows you to get multiple users in ONE query! Which is way more efficient. Then, in your view:
foreach($users as $user){
// iterate over each user and do stuff
}
You should also consider using a singelton class for a User to limit database load. Create a non-changing instance class called CurrentUser (for example) like:
class CurrentUser {
private static $user;
// we never instantiate it -its singleton
private function __construct() {}
public function user() {
return self::$user;
}
}
This is a really basic example of a singleton class and is missing a lot of stuff. If you want to know more about singleton classes, post another question.
Keep in mind that "session" is just yet another model. However first approach is unacceptable - what if you would like to fetch another users preferences just to compare them with something? Use the second approach.
I agree with Seth re. "You should also consider using a singelton class for a User to limit database load. Create a non-changing instance class called CurrentUser".
In my pseudo-MVC app I have class User (meaning current user) with methods for session, getting user info, roles etc., and class Member (meaning any given user) with methods for registering new users, getting/updating their properties etc but nothing to do with sessions, for example. Also, it is a singleton scenario, so current user is static and does not require much interaction with the DB.
So in my case, my controller and views make calls to User methods, e.g.
User::getId() or User::getGroups().
I have a session class that basicly just sets and retrieves session variables,
the reason I made it was so I could easily change it to use sessions or something
like memcache to set the items and have them accessible on multiple pages without hitting the database
I then have this user class which uses the session object to get session variables in it.
I am wanting to add to this user class though, to make it more encapsulated I would like to be able to set the variables that I am retrieving in this class
so right now I can display the userid with $user->userid; I would like to first have a method or something that sets its value from the session object I guess
Does this sound lke a good idea or possibly a lot of overhead?
And if what I am trying to do is a good idea maybe you could suggest/show example of how I should do it? I am thinking that if I add that method in that possibly I should move the code in the __construct method into it's own method
Basicly, I have the variables listed in the top part of the class that are used in the construct method, if I have multiple methods in the class though would I need to set them all at the top like that?
<?PHP
//user.class.php file
class User
{
public $userid;
public $name;
public $pic_url;
public $gender;
public $user_role;
public $location_lat;
public $location_long;
public $newuser;
function __construct()
{
global $session;
if($session->get('auto_id') != ''){
//set user vars on every page load
$this->userid = $session->get('auto_id'); //user id number
$this->name = $session->get('disp_name');
$this->pic_url = $session->get('pic_url');
$this->gender = $session->get('gender');
$this->user_role = $session->get('user_role');
$this->location_lat = $session->get('lat');
$this->location_long = $session->get('long');
$this->newuser = $session->get('newregister');
}else{
return false;
}
}
}
//with the class above I can easily show some user variables I have saved into a session like this below
$user = new user();
$user->userid;
?>
In general your idea is a good one
3 things I would do differently:
1) In your implementation doesn't seem to consider having several users. ie Several instances of the same class.
2) I would use factories instead of using IF in the constructor.
So for a user you have saved in the session you would call:
$savedUser = User::fromSession($userId);
for a new user
$user = new User()
3) Use the serialize and unserialze functions to save that data to the session
Then your class could could be implemented as
public static function fromSession($userId) {
return unserialize($session->get('users_'.$userId));
}
public function save() {
return $session->set('users_'.$this->id , serialize($this));
}
I guess this is vaguely an answer to the "is this a good idea" question. In my understanding, locating variables in the session versus refreshing them from the database is a question of the trade off between complex queries and deserializing data. The session data isn't a free magic cache that escapes database calls, it is just a convenient wrapper around a database call that you don't have to deal with. Any variable that you place in the session must be serializable. The whole collection of serialized data is then managed; the server fetches the data using the session key, deserializes it all, and hands it to the php script. Then when it closes the session for that request-response cycle it serializes it all and puts it back in the db.
So the mess in dealing with all that can, in some cases, be worse than the mess of just opening a connection and asking the db for the same stuff (or a subset of stuff) directly.
I would say that putting one or two key values in the session is a good stopping place, and relying on it too heavily for statefulness is a less-optimal plan.
I would set a new session with a name like "ValuesInSession" to true or false depending on whether or not you have session values for the fields in your user class. Then, in the sessions\users class you can check whether this session is true or false and set your values accordingly (IE from the existing sessions or to empty strings\0)
EDIT: You could, alternatively to putting that code in the user or sessions class, write a new class which could work with your users class to set the values properly (perhaps it could extend the sessions class?)
I'm not sure I understand the question, however, if you are using php 5, you can use the __set magic method to help with this.
Modifying your current class:
class User
{
private $id;
private $data = array();
public function __construct()
{
global $session;
$this->id = $session->get('auto_id');
$this->data = array(
'disp_name'=>$session->get('disp_name'),
'pic_url'=>$session->get('pic_url'),
'gender'=>$session->get('gender'),
'user_role'=>$session->get('user_role'),
'lat'=>$session->get('lat'),
'long'=>$session->get('long'),
'newregister'=>$session->get('newregister')
);
}
// return the user id
public function id()
{
return $this->id;
}
// the __get magic method is called when trying to retrieve a value of a
// property that has not been defined.
public function __get($name)
{
if(array_key_exists($name, $this->data))
{
return $this->data[$name];
}
return null;
}
// the __set magic method is called when trying to store a value in a property
// that has not been defined.
public function __set($name, $value)
{
global $session;
// check if the key exists in the 'data' array.
// if so, set the value in the array as well as the session
if(array_key_exists($name, $this->data))
{
$this->data[$name] = $value;
$session->set($name, $value);
}
}
}
This way you can still get and set values the same as you were, but will also store the set the value in your session class.
To test this:
$user = new User;
if($user->id())
{
echo $user->disp_name;
$user->disp_name = 'new name';
echo $session->get('disp_name');
}
I would not suggest you that because:
It is not a good practice to select an architecture "in case of future need" ('the reason I made it was so I could easily change'). Check http://www.startuplessonslearned.com (Eric Ries) or http://highscalability.com articles
Your code is hard/impossible to test (See Misko Hevery's blog (A google evangelist) http://misko.hevery.com for further information).
You are using "global" (never a good idea if you want to keep track of the dependencies).
It is better to seperate "the business logic" (a User class) and the wiring/building (a factory class for example). (See http://en.wikipedia.org/wiki/Single_responsibility_principle and "separation of concerns")
For really good code examples (and to understand which OO laws should not be broken), I can advice you Misko's blog (Also do not miss his technical talks at google that you can find on youtube). I am sure you will love them.
Hope this helps.