PHP使用数组作为名称设置魔术方法

Tid*_*ddo 3 php setter

我正在创建一个类,我将用它来存储和加载一些设置.在课堂内,所有设置都存储在一个数组中.设置可以嵌套,因此设置数组是一个多维数组.我想使用魔术方法__get和__set存储和加载设置,因此设置可以充当类成员.但是,由于我使用的是嵌套方法,因此在尝试访问嵌套设置时无法使__set方法起作用.

这个班是这样的:

class settings
{
    private $_settings = array();

    //some functions to fill the array

    public function __set($name, $value)
    {
        echo 'inside the __set method';
        //do some stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

以及使用此类的代码:

$foo = new settings();
//do some stuff with the class, so the internal settings array is as followed:
//array(
//    somename => somevalue
//    bar => array (
//               baz = someothervalue
//               qux = 42
//                 )
//     )
$foo->somename = something; //this works, __set method is called correctly
$foo->bar['baz'] = somethingelse; //Doesn't work, __set method isn't called at all
Run Code Online (Sandbox Code Playgroud)

我怎样才能让最后一行工作?

Zim*_*zat 6

使用此方法访问数组时,它实际上会通过__get.为了在返回的数组上设置参数,需要将其作为引用返回:&__get($name)

除非,你的意思是你希望每个作为数组返回的项目与父对象的行为方式相同,在这种情况下,你应该看看Zend Framework的Zend_Config对象源,以获得一个好方法.(它以子数组作为参数返回自身的新实例).


Gor*_*don 5

这会起作用:

\n\n
$settings = new Settings();\n$settings->foo = \'foo\';\n$settings->bar = array(\'bar\');\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,使用魔法方法或内部数组根本没有意义。无论如何,当您允许获取和设置随机成员时,您也可以将它们全部公开。

\n\n

评论后编辑(不回答上面的问题)

\n\n

就像我在评论中已经说过的那样,我认为你的设计有缺陷。让我们一步步解决这个问题,看看是否可以改进。以下是您所说的有关设置类要求的内容:

\n\n
    \n
  • 设置可以保存到文件或数据库中
  • \n
  • 设置可能需要更新应用程序的其他部分
  • \n
  • 更改设置之前需要验证它们
  • \n
  • 应该使用$setting->foo[subsetting]超过$setting->data[foo[subsetting]]
  • \n
  • 设置类需要授予其他类访问设置数据的权限
  • \n
  • 第一次创建实例时,需要从文件加载设置
  • \n
\n\n

现在,对于一个班级来说,要做的事情相当多。从要求来看,您正在尝试构建一个自我持久的 SingletonRegistry ,其范围从 1(糟糕)到 10(世界末日)是我书中的 11 级想法。

\n\n

根据单一职责原则( SOLID中的 S ),一个类应该有唯一的理由进行更改。如果您查看您的需求,您会发现更改它的理由肯定不止一个。如果您查看GRASP,您会发现您的班级承担了比应有的更多角色。

\n\n

详细地:

\n\n
\n

设置可以保存到文件或数据库中

\n
\n\n

这至少有两个职责:数据库访问和文件访问。有些人可能想进一步区分从文件读取和保存到文件。现在让我们忽略数据库部分,只关注文件访问和目前可能起作用的最简单的事情。

\n\n

您已经说过您的设置数组只是一个愚蠢的键/值存储,这几乎就是 PHP 中的数组。另外,在 PHP 中,您可以include从文件中读取数组,如下所示:

\n\n
<?php // settings.php\nreturn array(\n    \'foo\' => \'bar\'\n);\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以,从技术上讲,你不需要做任何事情,除了

\n\n
$settings = include \'settings.php\';\necho $settings[\'foo\']; // prints \'bar\';\n
Run Code Online (Sandbox Code Playgroud)\n\n

从文件加载并使用您的设置数组。这非常简单,几乎不值得为其编写一个对象,特别是因为您只会在引导程序中加载这些设置一次并将它们分发到需要它们的类。

\n\n

由于var_export和 ,将数组保存为可包含文件并不困难file_put_contents。我们可以轻松地为此创建一个 Service 类,例如

\n\n
class ArrayToFileService\n{\n    public function export($filePath, array $data)\n    {\n        file_put_contents($filePath, $this->getIncludableArrayString($data));\n    }\n    protected function getIncludableArrayString($data)\n    {\n        return sprintf(\'<?php return %s;\', var_export($data, true));\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,尽管该类没有自己的成员可供操作,但我故意没有将这些方法设为静态。静态使用该类会增加该类与该类的任何使用者之间的耦合,这是不希望且不必要的。

\n\n

您现在需要做的就是保存您的设置

\n\n
$arrayToFileService = new ArrayToFileService;\n$arrayToFileService->export(\'settings.php\', $settings);\n
Run Code Online (Sandbox Code Playgroud)\n\n

事实上,这是完全通用的,因此您可以将其重用于任何想要以这种方式持久保存的数组。

\n\n
\n

设置可能需要更新应用程序的其他部分

\n
\n\n

我不确定你为什么需要这个。鉴于我们的设置数组可以保存任意数据,您无法提前知道应用程序的哪些部分可能需要更新。此外,了解如何更新应用程序的其他部分并不是数据容器的责任。我们需要的是一种机制,可以在数组更新时通知应用程序的各个部分。当然,我们不能用普通的旧数组来做到这一点,因为它不是一个对象。幸运的是,PHP 允许我们通过实现 ArrayAccess 来访问像数组这样的对象:

\n\n
class HashMap implements ArrayAccess\n{\n    protected $data;\n\n    public function __construct(array $initialData = array())\n    {\n        $this->data = $initialData;\n    }\n    public function offsetExists($offset)\n    {\n        return isset($this->data[$offset]);\n    }\n    public function offsetGet($offset)\n    {\n        return $this->data[$offset];\n    }\n    public function offsetSet($offset, $value)\n    {\n        $this->data[$offset] = $value;\n    }\n    public function offsetUnset($offset)\n    {\n        unset($this->data[$offset]);\n    }\n    public function getArrayCopy()\n    {\n        return $this->data;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

以开头的方法offset*是接口所必需的。该方法getArrayCopy就在那里,因此我们可以将它与我们的ArrayToFileService. 我们还可以添加IteratorAggregate接口以使对象的行为更像数组,但由于现在这不是必需的,因此我们不需要它。现在为了允许任意更新,我们通过实现添加主题/观察者模式SplSubject:

\n\n
class ObservableHashMap implements ArrayAccess, SplSubject\n\xe2\x80\xa6\n    protected $observers;\n\n    public function __construct(array $initialData = array())\n    {\n        $this->data = $initialData;\n        $this->observers = new SplObjectStorage;\n    }\n    public function attach(SplObserver $observer)\n    {\n        $this->observers->attach($observer);        \n    }\n    public function detach(SplObserver $observer)\n    {\n        $this->observers->detach($observer);        \n    }\n    public function notify()\n    {\n        foreach ($this->observers as $observers) {\n            $observers->update($this);\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这允许我们注册任意对象来实现SplObserver与ObservableHashMap(重命名为HashMap)类的接口以及notify它们的更改。将 Observable 部分作为独立类以便能够将其重用于其他类会更漂亮。为此,我们可以将 Observable 部分制作为Decorator或Trait。我们还可以通过添加EventDispatcher在两者之间进行调解来进一步解耦主题和观察者,但目前这应该足够了。

\n\n

现在要通知观察者,我们必须修改类中应触发通知的所有方法,例如

\n\n
public function offsetSet($offset, $value)\n{\n    $this->data[$offset] = $value;\n    $this->notify();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

每当您调用offsetSet()或使用[]修改 中的值时HashMap,任何注册的观察者都会收到通知并传递整个HashMap实例。然后他们可以检查该实例以查看是否有重要的更改并根据需要做出反应,例如让我们假设SomeComponent

\n\n
class SomeComponent implements SplObserver\n{\n    public function update(SplSubject $subject)\n    {\n        echo \'something changed\';\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后你就这样做

\n\n
$data = include \'settings.php\';\n$settings = new ObservableHashMap($data);\n$settings->attach(new SomeComponent);\n$settings[\'foo\'] = \'foobarbaz\'; // will print \'something changed\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

这样,您的设置类不需要知道值更改时需要发生什么。您可以将其全部保留在其所属的位置:在观察者中。

\n\n
\n

更改设置之前需要验证它们

\n
\n\n

那很容易。您根本不在哈希图/设置对象内执行此操作。鉴于 HashMap 只是一个愚蠢的容器,其中包含应该由其他类使用的任意数据,因此您可以将验证放入使用该数据的那些类中。问题解决了。

\n\n
\n

应该使用$setting->foo[subsetting]超过$setting->data[foo[subsetting]]

\n
\n\n

嗯,是的。正如您可能已经猜到的,上面的实现没有使用这种表示法。它使用$settings[\'foo\'] = \'bar\'而你不能使用$settings[\'foo\'][\'bar\']with ArrayAccess(至少据我所知)。所以这有点限制。

\n\n
\n

设置类需要授予其他类访问设置数据的权限

\n
\n\n

这个要求和下一个要求对我来说就像 Singleton。如果是这样,请再想一想。您所需要的只是在引导程序中实例化一次设置类。您正在那里创建满足请求所需的所有其他类,因此您可以在那里注入所有设置值。Settings 类不需要全局可访问。创建、注入、丢弃。

\n\n
\n

第一次创建实例时,需要从文件加载设置

\n
\n\n

往上看。

\n