Когда пользователь регистрируется на моем сайте, я создаю экземпляр класса User
, извлекаю некоторые данные, связанные с пользователем, и сохраняю объект в SESSION
.
Некоторые из данных, которые я извлекаю из базы данных, должны быть постоянными на протяжении всего сеанса. И я хочу, чтобы данные были доступны из других объектов. Я предпочитаю использовать User::$static_value_in_class
для $_SESSION['static_value_in_session']
при использовании значения из другого объекта, но я открыт для убеждения.
Проблема в том, что значения не запоминаются, когда я сериализую свой экземпляр User
в SESSION
, а затем загружаю другую страницу.
Определения классов:
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
Должен ли я реализовать Serializable
и написать свою собственную версию serialize()
и unserialize()
для включения статических данных?
Должен ли я кусать губы и получать доступ к переменной $_SESSION
из класса Blog
?
Должен ли я потребовать действительный экземпляр User
отправленный методу write()
в Blog
?
Или, может быть, у интернов есть лучшая идея …
EDIT: Написание моего реального прецедента (не полный код, но достаточно, чтобы получить суть).
Мой сайт обрабатывает группы пользователей с общими бюджетами. Пользователи могут тратить групповые деньги на определенные вещи, согласованные группой, и они сообщают о транзакциях, создавая экземпляры класса Transaction
и отправляя их в класс Bank
для хранения базы данных.
Класс Bank
:
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 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 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 } } }
Фактический код (login.php или что-то еще):
$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!
Как я уже упоминал в комментарии, это скорее вопрос дизайна программного обеспечения, чем вопрос о том, как достичь этого с помощью PHP.
Статическое свойство не является частью состояния объекта и поэтому не сериализуется с ним.
Я приведу вам небольшой пример того, как я буду решать связанную проблему. Представьте, что у вас есть следующий класс сообщений, который имеет статическое свойство $ 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:
$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);
Чтобы убедиться, что идентификатор уникален для нескольких сценариев, дальнейшая работа необходима. Вы должны убедиться, что Message::$id
инициализирован до создания любого объекта, используя значение из последнего запуска сценария. Это будет дополнительно подключено, когда речь заходит о параллельном запросе PHP на веб-сервере.
Это просто пример с самым простым статическим свойством, которым я знаком: счетчик экземпляров. В этом случае я бы сделал это. Но я надеюсь, что вы видите, что требуется дополнительная работа для сериализации / несериализации static
свойств без побочных эффектов. И это зависит от ваших потребностей приложения.
На этот вопрос нельзя ответить вообще. Я склонен говорить, что в любом случае не имеет смысла сериализовать статические элементы. Но я был бы признателен за комментарии по этому поводу.
Некоторые из данных, которые я извлекаю из базы данных, должны быть постоянными на протяжении всего сеанса. И я хочу, чтобы данные были доступны из других объектов.
Если данные действительно постоянны, сделайте их постоянными.
Если данные не являются постоянными, рассмотрите, принадлежат ли они отдельным пользователям (экземплярам объекта) или Пользователю в качестве общей концепции (что является классом).
Должен ли я реализовать Serializable и написать свою собственную версию serialize () и unserialize () для включения статических данных?
Не имеет смысла хранить статические члены в строке сериализованного объекта, потому что они независимы друг от друга. Хранение их было бы моментальным снимком состояния класса во время сериализации объекта.
Рассмотрим следующий фрагмент кода:
$user = new User; $user::$allowed_actions = 'foo'; $string = serialize($user); unset($user);
Теперь представьте себе, что какая-то другая часть вашего кода делает это:
echo User::$allowed_actions;
Он по-прежнему дает «foo», несмотря на то, что на данный момент в памяти нет объекта. Это потому, что это статический член. Это состояние класса.
Теперь представьте, что вы это делаете:
User::$allowed_actions = 'bar';
Если вы теперь несериализуете объект, что должно быть $ allowed_actions? Foo или Bar?
$user = unserialize($string); echo $user::$allowed_actions;
Выход должен и должен быть «баром», потому что статические члены относятся к классу. Тот факт, что мы создали, уничтожили и вернули объект из него, не имеет значения. Это все состояние класса, которое мы изменили здесь.
Кроме того, учтите, что статика – это смерть для проверки, и вы хотите избежать их, когда это возможно. В конце концов, это называется OOP, а не Class-Oriented-Progamming.
Должен ли я кусать губы и получать доступ к переменной $ _SESSION из класса Blog?
Нет, вы не должны обращаться ни к одной из суперглобаллов нигде, кроме как писать абстракции для каждого из них или, скорее, для данных внутри них. Это всего лишь исходные источники. В случае $_SESSION
то, что вы хотите сделать, это получить все данные, необходимые для этого конкретного запроса, прямо в вашей начальной загрузке, а затем передать данные вместо этого, например, воссоздать пользователя и передать его.
Должен ли я потребовать действительный экземпляр пользователя, отправленный методу write () в Blog?
В общем, методы должны быть на объектах с наибольшей информацией для выполнения действия. Является ли это применимым к вашему блогу :: write, я не знаю. Если allowed_actions являются частью экземпляра User, то, вероятно, да, скорее всего, вам понадобится действительный экземпляр пользователя.
Или, может быть, у интернов есть лучшая идея …
Другой вариант – разместить разрешения в выделенный объект Permissions, удерживая роль пользователя и его разрешение. Затем вы можете найти разрешение из этого списка, передав объект User. Найдите списки контроля доступа (ACL) для получения дополнительной информации о возможных реализациях.
EDIT: Написание моего реального прецедента (не полный код, но достаточно, чтобы получить суть).
Если ваша проблема просто в том, что Bank::$valid_transaction_reasons
может быть пустым, то не храните банк в сеансе вообще, а загружайте его только у пользователя при выполнении транзакции, например, создайте экземпляр банка в submit_transaction.php (создайте это когда вам это нужно). Таким образом, вы никогда не столкнетесь с ошибкой.