PHP – сериализация класса со статическими свойствами

Когда пользователь регистрируется на моем сайте, я создаю экземпляр класса 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 (создайте это когда вам это нужно). Таким образом, вы никогда не столкнетесь с ошибкой.