PHP – serialice una clase con propiedades estáticas

Cuando un usuario inicia sesión en mi sitio, creo una instancia de mi clase de User , obtengo algunos datos relacionados con el usuario y almaceno el objeto en la SESSION .

Algunos de los datos que obtengo de la base de datos deben ser constantes durante toda la sesión Y quiero que los datos sean accesibles desde otros objetos. Prefiero usar User::$static_value_in_class a $_SESSION['static_value_in_session'] cuando uso el valor desde dentro de otro objeto, pero estoy abierto a la persuasión.

El problema es que los valores no se recuerdan cuando serializo mi instancia de User en la SESSION , luego cargo una página diferente.

Definiciones de clase:

 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 

¿Debo implementar Serializable y escribir mi propia versión de serialize() y unserialize() para incluir los datos estáticos?

¿Debo $_SESSION el labio y acceder a la variable $_SESSION dentro de la clase Blog ?

¿Debo solicitar una instancia de User válida enviada al método Blog write() ?

O tal vez los internets tienen una mejor idea …

EDITAR: Escribo mi caso de uso real (no el código completo, pero es suficiente para obtener la esencia).

Mi sitio maneja grupos de usuarios con cuentas de presupuesto compartido. Los usuarios pueden gastar dinero grupal en ciertas cosas que el grupo acordó e informan las transacciones creando instancias de la clase Transaction y enviándolas a la clase Bank para el almacenamiento de la base de datos.

Clase de 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); } } 

Clase de 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']; } } 

Clase de 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 } } } 

Código real (login.php, o algo así):

 $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! 

Como mencioné en el comentario, esta es más una pregunta de diseño de software que una pregunta sobre cómo lograr esto con PHP.

Una propiedad estática no es parte del estado de un objeto y, por lo tanto, no se serializará con él.

Te daré un pequeño ejemplo de cómo resolvería un problema relacionado. Imagine que tiene la siguiente clase de mensaje, que tiene una propiedad $ id estática para asegurarse de que todas las instancias tengan una ID única:

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

Código de serialización / deserialización:

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

Para asegurarse de que el ID sea único en varias secuencias de comandos, es necesario trabajar más. Deberá asegurarse de que Message::$id se inicialice antes de la creación de cualquier objeto, utilizando el valor de la última ejecución del script. Esto se cableará adicionalmente cuando se trate de solicitudes PHP paralelas en un servidor web.


Es solo un ejemplo con la propiedad estática más simple que conozco: un contador de instancias. En este caso, lo haría. Pero espero que vea que se requiere más trabajo para serializar / deserializar propiedades static sin tener efectos secundarios. Y esto depende de las necesidades de tu aplicación.

Esta pregunta no puede responderse en general . Tiendo a decir que, en cualquier caso, no tiene sentido serializar miembros estáticos. Pero agradecería los comentarios sobre esto.

Algunos de los datos que obtengo de la base de datos deben ser constantes durante toda la sesión Y quiero que los datos sean accesibles desde otros objetos.

Si los datos son realmente constantes, conviértelos en constantes.

Si los datos no son constantes, considere si pertenecen a los usuarios individuales (las instancias del objeto) o al Usuario como el concepto general (que es lo que es una clase).

¿Debo implementar Serializable y escribir mi propia versión de serialize () y unserialize () para incluir los datos estáticos?

No tiene sentido almacenar miembros estáticos en la cadena del objeto serializado porque son independientes el uno del otro. Almacenarlos sería una instantánea del estado de la clase en el momento en que el objeto fue serializado.

Considere el siguiente fragmento de código:

 $user = new User; $user::$allowed_actions = 'foo'; $string = serialize($user); unset($user); 

Ahora imagina alguna otra parte de tu código que haga esto:

 echo User::$allowed_actions; 

Todavía da “foo” a pesar de que ningún objeto está en la memoria en este momento. Eso es porque es un miembro estático. Es estado de clase.

Ahora imagina que haces esto:

 User::$allowed_actions = 'bar'; 

Si realiza la deserialización del objeto ahora, ¿qué debería ser $ allowed_actions? Foo o Bar?

 $user = unserialize($string); echo $user::$allowed_actions; 

La salida debería y sería “bar”, porque los miembros estáticos son sobre la clase. El hecho de que hayamos creado, destruido y recuperado un objeto de él es irrelevante. Es todo el estado de la clase que cambiamos aquí.

Además, tenga en cuenta que la estática es mortal para la capacidad de prueba y desea evitarlas cuando sea posible. Después de todo, se llama OOP no Progtwigdo Orientado a la Clase.

¿Debo morderme el labio y acceder a la variable $ _SESSION dentro de la clase Blog?

No, no debes acceder a ninguno de los superglobales en ninguna parte, sino escribir abstracciones para cada uno de ellos o más bien para los datos que contienen. Son meramente fonts de entrada. En el caso de $_SESSION lo que desea hacer es obtener todos los datos que necesita para esa solicitud particular directamente en su arranque y luego pasar los datos en su lugar, por ejemplo, recrear al usuario y pasarlo.

¿Debo solicitar una instancia de Usuario válida enviada al método Blog write ()?

En general, los métodos deben estar en los objetos con más información para cumplir una acción. Si eso se aplica a tu Blog :: escribir, no lo sé. Si las allowed_actions son parte de la instancia de User, entonces probablemente sí, probablemente requiera una instancia de usuario válida.

O tal vez los internets tienen una mejor idea …

Otra opción sería colocar los permisos en un objeto de Permisos dedicado, manteniendo el rol de usuario y su permiso. Luego puede buscar el permiso de esa lista pasando un objeto Usuario. Busque listas de control de acceso (ACL) para obtener más información sobre posibles implementaciones.

EDITAR: Escribo mi caso de uso real (no el código completo, pero es suficiente para obtener la esencia).

Si su preocupación es simplemente que Bank::$valid_transaction_reasons podría estar vacío, entonces no almacene el banco en la sesión en absoluto, sino que solo cárguelo del usuario cuando ejecute la transacción, por ejemplo, cree la instancia bancaria en submit_transaction.php (crear cuando lo necesitas). De esa forma, nunca se encontrará con un error.