访问非公共成员


Accessing non-public members

我正在创建域对象。我希望这些对象具有setter,它将检查要分配给每个属性的值的有效性,但如果假设数据正确,我也希望能够在不执行检查的情况下设置此属性。

我可以做以下事情:

class DO_Group
{
    protected $_name = null;
    public function getName()
    {
        return $this->_name;
    }
    public function setName( $name )
    {
        $length = strlen( $name );
        if( 4 <= $length && $length <= 16 && preg_match( '^[A-Z]*(_[A-Z]*)*$', $name ) == 1 )
            $this->_name = $name;
        else
            throw new Exception( '::DO_Group::setName() - Invalid name.' );
    }
}

但是,在已知传递给setName()的$name有效的情况下,我必须调用setName,从而执行不必要的检查来设置该属性。

我可以使用反射API或调试跟踪破解,但我不想使用这两种方法,因为它们在多次使用时性能较差(而且很脏,IMO)。

我想添加一个不检查值的方法:

public function setNameNoCheck( $name )
{
    $this->_name = $name;
}

但在这种情况下,将房产公开是没有意义的。如果我选择公开它,代码用户可能会忘记在需要时检查它。

我能做什么吗:

abstract class BaseClass
{
    public function setProperty( $name, $value )
    {
        $this->$name = $value;
    }
}

并使我的域对象扩展这个类?PHP引用未声明的属性是正确的吗?(因为它们不存在于基类中)

我想我的建议是对这两个实例都使用现在的方法:

public function setName( $name )
{
        $length = strlen( $name );
        if( 4 <= $length && $length <= 16 && preg_match( '^[A-Z]*(_[A-Z]*)*$', $name ) == 1 )
            $this->_name = $name;
        else
            throw new Exception( '::DO_Group::setName() - Invalid name.' );
}

在这种情况下,您可以确信您的数据库将始终保持其完整性。即使在少数情况下,如果值为正数,开销也会很小。这似乎是一个干净的解决方案,保证有效。

编辑:

如果对象已经存在于数据库中,为什么不创建一个类方法来处理它呢?然后在直接从数据库中提取值时调用它。。

public function populateFields( $row )
{
    $this->_name = $row['name'];
    // ... populate other fields here
}

这将一次填充所有字段,而不必为每个属性编写和调用单独的函数。

如果您使用PHP 5.4+,那么这里有一个很好的代码。我几个月前就想到了这个。如果您使用的是PHP 5.3或更低版本,您仍然可以使用call_user_func使其工作。然而,在匿名函数的帮助下,它变得更加干净和详细。

这个想法是制作一种friend对象。类似于C++中的friend类,但操作特定对象而不是类。

让我们开始吧。首先,我们需要为你的课程做一点准备:

class DoGroup
{
    protected $_name = null;
    public function makeFriend(IDoGroupFriend $newFriend)
    {
        $newFriend->plugNameSetter(function($name){
            $this->_doSetName($name);
        });
    }
    public function setName( $name )
    {
        // do the costly checking here...
        /// and set the variable:
        $this->_doSetName($name);
    }
    protected function _doSetName($name)
    {
        $this->_name = $name;
    }
}

接下来,我们将定义朋友接口:

interface IDoGroupFriend
{
    public function plugNameSetter(callable $nameSetterFn);
}

现在我们可以创建朋友:

class ExampleFriend implements IDoGroupFriend
{
    private $_nameSetterFn = null;
    public function plugNameSetter(callable $nameSetterFn)
    {
        $this->_nameSetterFn = $nameSetterFn;
    }
    public function setTheNameFromFriend($name)
    {
        $nameSetterFn = $this->_nameSetterFn;
        $nameSetterFn($name);
    }
}

最后,我们可以使用以下朋友:

$Main = new DoGroup();
$Friend = new ExampleFriend();
$Main->makeFriend($Friend);
// tadam!
$Friend->setTheNameViaFriend('name via friend');

当然,你真正的实现可能比盲目地传递名称要复杂得多。如果$name的源(在这种情况下是ExampleFriend对象)是可信的,那么我假设它来自不同的可信源。在这种情况下,您将以不同的方式实现setTheNameFromFriend,例如作为databese fetch方法。

此外,您可能不希望将DoGroup::makeFriend()方法公开。我通常只从DoGroup类的构造函数调用这个方法,所以我喜欢将它设为私有方法。

创建安全模式($this->safeMode=false)和

       public function setName( $name )
    {
if ($this->safeMode===true){ $this->_name = $name; return;}
            $length = strlen( $name );
            if( 4 <= $length && $length <= 16 && preg_match( '^[A-Z]*(_[A-Z]*)*$', $name ) == 1 )
                $this->_name = $name;
            else
                throw new Exception( '::DO_Group::setName() - Invalid name.' );
    }

如果数据来自数据库或其他"安全"源(或类扩展数据库类)

,则将safeMode设置为true