【问题标题】:PHP - serialize a class with static propertiesPHP - 用静态属性序列化一个类
【发布时间】:2013-01-29 07:55:39
【问题描述】:

当用户登录我的站点时,我创建了一个 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() 版本来包含静态数据吗?

我应该咬紧牙关从Blog 类中访问$_SESSION 变量吗?

我是否应该要求将有效的User 实例发送到Blog write() 方法?

或者也许互联网有更好的主意......



编辑:编写我的真实用例(不是完整的代码,但足以了解要点)。

我的网站处理具有共享预算帐户的用户组。 用户可以将团体的钱花在团体同意的某些事情上,他们通过创建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!

【问题讨论】:

  • 你确定$allowedActions 应该是static 吗?它是用户状态的一部分,因此它不应该是静态的。
  • 谢谢!你能举一个__sleep()__wakeup 的例子,它们模仿了默认的serialize()unserialized(),所以我知道要改变什么吗?
  • @hek2mgl, $allowed_actions 就是一个例子。我贴的代码没什么用,只是说明问题,不是实际用例。
  • @Dori 我目前正准备写一个回答,说明您的软件中的设计错误。虽然你可以这样做 __sleep, __wakeup hack 它会导致不必要的副作用。那是因为静态变量的性质。静态属性不是对象状态的一部分,因此不会被序列化。

标签: php class serialization static


【解决方案1】:

正如我在评论中提到的,这更像是一个软件设计问题,而不是如何使用 PHP 实现这一点的问题。

静态属性不是对象状态的一部分,因此不会被序列化。

我会给你一个简短的例子,我将如何解决一个相关的问题。假设你有以下消息类,它有一个静态的 $id 属性来确保所有实例都有一个唯一的 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;
    }
}

序列化/反序列化代码:

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

为了确保 id 在多个脚本运行中是唯一的,进一步的工作是必要的。您必须确保 Message::$id 在创建任何对象之前使用上次脚本运行的值进行初始化。当涉及到网络服务器上的并行 PHP 请求时,这将得到额外的连接。


它只是一个我知道的最简单的静态属性的例子:一个实例计数器。在这种情况下,我会这样做。但我希望您看到序列化/反序列化 static 属性还需要进一步的工作,而不会产生副作用。这取决于您的应用程序需求。

这个问题不能笼统地回答 我倾向于说在任何情况下序列化静态成员都是没有意义的。但我会很感激 cmets 在这方面。

【讨论】:

  • 所以基本上,将静态值的值复制到实际的类属性中,以便它们被序列化?
  • 这只是一个我知道的最简单的静态属性的例子:一个实例计数器。在这种情况下,我会这样做。但请阅读最后一段。我希望您看到在没有副作用的情况下序列化/反序列化 static 属性需要进一步的工作
  • @Dori 将它们转换为实际对象属性的问题在于,当其他一些代码更改类状态时,它们不会得到更新。
  • @Gordon 类状态在SESSION 被销毁之前不会改变,然后我无论如何都需要重新序列化一个新实例
  • @Dori 所以允许的操作是每个用户的。很明显,它们应该是 User 对象中的对象成员。
【解决方案2】:

我从数据库中获取的一些数据在整个会话期间应该保持不变,并且我希望可以从其他对象访问这些数据。

如果数据确实是常数,则将它们设为常数。

如果数据不是常数,考虑它们是属于单个用户(对象实例)还是属于一般概念的用户(即类是什么)。

我应该实现 Serializable 并编写自己的 serialize() 和 unserialize() 版本来包含静态数据吗?

将静态成员存储在序列化对象的字符串中是没有意义的,因为它们彼此独立。存储它们将是对象被序列化时类状态的快照。

考虑以下代码sn-p:

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

输出应该是“bar”,因为静态成员是关于类的。我们创建、销毁并从中恢复对象的事实是无关紧要的。这是我们在这里更改的类的所有状态。

另外,尽可能考虑statics are death to testabilityyou want to avoid them。毕竟,这叫 OOP 而不是 Class-Oriented-Progamming。

我应该咬紧牙关从 Blog 类中访问 $_SESSION 变量吗?

不,您不应该在任何地方访问任何超全局变量,而应为它们中的每一个或更确切地说为其中的数据编写抽象。它们只是输入源。在$_SESSION 的情况下,您想要做的是在您的引导程序中获取该特定请求所需的所有数据,然后将数据传递给周围,例如重新创建用户并传递它。

我是否应该要求将有效的用户实例发送到博客的 write() 方法?

一般来说,方法应该在具有最多信息的对象上来完成一个动作。这是否适用于您的 Blog::write 我不知道。如果 allowed_actions 是 User 实例的一部分,那么可能是的,您可能需要一个有效的 User 实例。

或者也许互联网有更好的主意......

另一种选择是将权限放入专用的 Permissions 对象中,持有用户角色及其权限。然后,您可以通过传入 User 对象从该列表中查找权限。搜索访问控制列表 (ACL) 以获取有关可能实现的更多信息。

编辑:编写我的真实用例(不是完整的代码,但足以了解要点)。

如果您只是担心Bank::$valid_transaction_reasons 可能为空,则根本不要将 Bank 存储在 Session 中,而仅在您运行交易时从用户那里加载它,例如在 submit_transaction.php 中创建 Bank 实例(在需要时创建)。这样你就永远不会遇到错误。

【讨论】:

    猜你喜欢
    • 2012-01-03
    • 1970-01-01
    • 2011-03-10
    • 2016-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    相关资源
    最近更新 更多