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!
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).
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:
Now imagine some other part of your code does this:
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:
If you do unserialize the object now what should $allowed_actions be? Foo or Bar?
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.
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.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.
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.
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.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:
Serialization / Unserialization code:
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.