从 PHP 中的类外部调用私有方法和私有属性

Posted

技术标签:

【中文标题】从 PHP 中的类外部调用私有方法和私有属性【英文标题】:Call private methods and private properties from outside a class in PHP 【发布时间】:2011-02-13 21:13:09 【问题描述】:

在极少数特定情况下,我想从类外部访问私有方法和变量。

我发现尽管使用了自省,但这是不可能的。

具体案例是下一个:

我想要这样的东西:

class Console

    final public static function run() 

        while (TRUE != FALSE) 
            echo "\n> ";
            $command = trim(fgets(STDIN));

            switch ($command) 
                case 'exit':
                case 'q':
                case 'quit':
                    echo "OK+\n";
                    return;
                default:
                    ob_start();
                    eval($command);
                    $out = ob_get_contents();
                    ob_end_clean();

                    print("Command: $command");
                    print("Output:\n$out");         

                    break;
            
        
    

这个方法应该可以像这样注入到代码中:

Class Demo

    private $a;

    final public function myMethod()
    
        // some code
        Console::run();
        // some other code
    

    final public function myPublicMethod()
    
        return "I can run through eval()";
    

    private function myPrivateMethod()
    
        return "I cannot run through eval()";
    

(这只是一个简化。真正的通过一个套接字,并实现更多的东西......)

所以……

如果您实例化类 Demo 并调用 $demo->myMethod(),您将获得一个控制台:该控制台可以访问第一个方法,编写如下命令:

> $this->myPublicMethod();

但是你不能成功运行第二个:

> $this->myPrivateMethod();

你们有任何想法吗,或者是否有任何 php 库允许您这样做?

非常感谢!

【问题讨论】:

Erm... 谁愿意让标记为私有的方法公开访问?我的意思是...如果您需要从外部访问它,只需使用 public。另外:您的控制台类与您在此处添加的方式毫无意义。它没有单独使用 OOP,基本上只是一个强制进入类的函数。 仅供参考 while(true)for(;;) 是更简洁和常见的循环方法,直到遇到明确的 breakreturn @lamas:正如我之前所说,我将其作为 POC 而不是真实示例。真正的 Console 类或多或少大约有 1k 行,并通过组合扩展了其他一些行。代码的可维护性不是问题,因为它将被用作我们正在工作的主项目之外的独立组件,因此这不仅是“强制进入类的函数”,而且是类的摘录请勿在此处发布,以免引起人们的反感。 :) @meagar:呵呵,我把 while(TRUE != FALSE) 作为一个玩笑,因为 PHP 将 FALSE != 0 验证为 FALSE。无论如何谢谢;) @meagar:而且,我忘了评论它,除非是 PoC,否则我从未在函数内写过 return 或 continue。我认为这不是一个好的做法,这样的事情可能会影响代码的易读性。 @lamas:我完全同意你的看法 回复:范围的选择。但我不同意你的观点,即如果一个类中没有单个 OOP 调用,并且其中没有纯静态函数,那么它就没有意义:它是一种构成库的方式,并且仍然受益来自自动加载器功能,甚至只是为了使代码更清晰,并为您的函数添加一个编目级别。 【参考方案1】:
<?php
$request="email";
$data=[1,2,3,4,5];
$name=new Update($request,$data);

  class Update
      private $request;
      private $data;
      function __construct($request,$data)
          $this->request=$request;
          $this->data=$data;
          if($this->request=='email')
            $this->update_email();
          
          else
              echo "Can't do anything";
          
          
      
      private function update_email()
          echo $this->request;
          echo '\n';
          foreach($this->data as $x)
              echo $x."\n";
          
      
    
?>

【讨论】:

虽然这段代码 sn-p 可以解决问题,但它没有解释为什么或如何回答这个问题。请include an explanation for your code,因为这确实有助于提高您的帖子质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。您可以使用edit 按钮改进此答案以获得更多选票和声誉!【参考方案2】:

编辑: 更新为包含带参数的私有函数调用示例。

PHP 5.4 开始,您可以使用预定义的 Closure 类将类的方法/属性绑定到甚至可以访问私有成员的 delta 函数。

The Closure class

例如,我们有一个带有私有变量的类,我们想在该类之外访问它:

class Foo 
    private $bar = "Foo::Bar";
    private function add_ab($a, $b) 
        return $a + $b;
    

PHP 5.4+

$foo = new Foo;

// Single variable example
$getFooBarCallback = function() 
    return $this->bar;
;

$getFooBar = $getFooBarCallback->bindTo($foo, 'Foo');

echo $getFooBar(); // Prints Foo::Bar

// Function call with parameters example
$getFooAddABCallback = function() 
    // As of PHP 5.6 we can use $this->fn(...func_get_args()) instead of call_user_func_array
    return call_user_func_array(array($this, 'add_ab'), func_get_args());
;

$getFooAddAB = $getFooAddABCallback->bindTo($foo, 'Foo');

echo $getFooAddAB(33, 6); // Prints 39

从 PHP 7 开始,您可以使用新的 Closure::call 方法将对象的任何方法/属性绑定到回调函数,即使是私有成员:

PHP 7+

$foo = new Foo;

// Single variable example
$getFooBar = function() 
    return $this->bar;
;

echo $getFooBar->call($foo); // Prints Foo::Bar

// Function call with parameters example
$getFooAddAB = function() 
    return $this->add_ab(...func_get_args());
;

echo $getFooAddAB->call($foo, 33, 6); // Prints 39

【讨论】:

PHP 7+ 部分很棒!太好了! @NurbolAlpysbayev 谢谢。我刚刚意识到答案不包括带参数的函数调用,而只包括私有变量!更新并添加了 PHP 5.4+ 和 PHP 7+ 的私有函数调用示例。 这很好用!但是您认为这是最佳做法吗,因为他们可能出于某种原因将其设为私有? @DavidJarrin 当然,这不是最好的做法。如果可能,应该避免这种情况;当我们想要访问该私有成员并且 1) 我们不想更改类源文件(可能是在更新后会中断的现成库)和 2) 如果它是您项目的类之一,则将属性/函数更改为公共,或创建其他函数来检索该私有成员。 @ChristosLytras 是的,这就是我的情况,我真的不想更改库,因为它会在更新时中断,感谢您的回复。【参考方案3】:

只需将方法公开即可。但是如果你想变得棘手,你可以试试这个(PHP 5.3):

class LockedGate

    private function open()
    
        return 'how did you get in here?!!';
    


$object = new LockedGate();
$reflector = new ReflectionObject($object);
$method = $reflector->getMethod('open');
$method->setAccessible(true);
echo $method->invoke($object);

【讨论】:

这正是我想要的。我目前在开发环境中使用 PHP 5.2.3,但我们正处于迁移过程中,所以这对我有很大帮助!! +1 @webbiedave:嗯,我也有 final class LockedGate private function __construct() ... ,它似乎不起作用。我想知道“最终”是否会阻止可访问性被更改。 @webbiedave:啊,我想我的问题可能是它是一个静态类。这对我有用$method = $reflector-&gt;getMethod('myStaticPrivate');$method-&gt;setAccessible(true);$method-&gt;invoke(NULL); 在 shell $ php -a 上尝试使用 PHP 5.3.22 失败:PHP Fatal error: Call to private method LockedGate::open() from context '' in php shell code on line 1PHP Stack trace:PHP 1. main() php shell code:0Fatal error: Call to private method LockedGate::open() from context '' in php shell code on line 1Call Stack:5.3208 641904 1. main() php shell code:0 我已经更新了反射代码。但是,我的主要答案仍然是将方法公开。【参考方案4】:

答案公开给方法。无论您要做什么技巧,其他开发人员都无法理解。例如,他们不知道在其他一些代码中,通过查看 Demo 类可以公开访问此函数。

还有一件事。 该控制台可以访问第一个方法,编写如下命令:。这怎么可能?控制台无法使用 $this 访问演示类函数。

【讨论】:

我认为这里所有想要这样做的人都是“开发者伙伴”:)【参考方案5】:

如果您能够在定义方法的类中添加方法,则可以添加在内部使用 call_user_method() 的方法。这也适用于 PHP 5.2.x

<?php
class SomeClass 
    public function callprivate($methodName) 
         call_user_method(array($this, $methodName));
    

    private function somePrivateMethod() 
         echo 'test';
    



$object = new SomeClass();
$object->callprivate('somePrivateMethod');

【讨论】:

【参考方案6】:

以下是其他答案的变体,可用于在一行中进行此类调用:

public function callPrivateMethod($object, $methodName)

    $reflectionClass = new \ReflectionClass($object);
    $reflectionMethod = $reflectionClass->getMethod($methodName);
    $reflectionMethod->setAccessible(true);

    $params = array_slice(func_get_args(), 2); //get all the parameters after $methodName
    return $reflectionMethod->invokeArgs($object, $params);

【讨论】:

【参考方案7】:

如果你真的想执行一些私有方法,我猜反射类是唯一的选择。无论如何,如果您只需要对私有或受保护属性的读取权限,您可以使用以下代码:

<?php
class Demo

    private $foo = "bar";


$demo = new Demo();

// Will return an object with public, private and protected properties in public scope.
$properties = json_decode(preg_replace('/\\\\u([0-9a-f]4)|'.get_class($demo).'/i', '', json_encode((array) $demo)));

?>

【讨论】:

【参考方案8】:

您应该问的第一个问题是,如果您需要从类外部访问它,为什么将其声明为私有?如果它不是您的代码,那么发起者可能有充分的理由将其声明为私有,直接访问它是一种非常糟糕(并且基本上无法维护)的做法。

编辑:正如 Adam V. 在 cmets 中指出的那样,您需要在调用私有方法之前使其可访问。代码示例已更新以包含此内容。不过,我还没有对其进行测试 - 只是在此处添加以保持答案更新。

话虽如此,您可以使用Reflection 来完成此操作。实例化ReflectionClass,为要调用的方法调用getMethod,然后在返回的ReflectionMethod上调用invoke

代码示例(虽然我没有测试过,所以可能有错误)可能看起来像

$demo = new Demo();
$reflection_class = new ReflectionClass("Demo");
$reflection_method = $reflection_class->getMethod("myPrivateMethod");
$reflection_method->setAccessible(true);
$result = $reflection_method->invoke($demo, NULL);

【讨论】:

这应该会导致 ReflectionException('尝试从范围 ReflectionMethod 调用私有方法')。 PHP 反射 API是否 支持调用私有方法。您只需要在 $reflection_method = $reflection_class-&gt;getMethod("myPrivateMethod") 之后添加一个 $reflection_method-&gt;setAccessible(true)【参考方案9】:

我有时也会遇到这些问题,但是我通过我的编码标准解决了这些问题。私有或受保护的函数用前缀下划线表示,即

private function _myPrivateMethod()

然后我只是将函数公开。

public function _myPrivateMethod()

因此,尽管该函数是公共的,但命名约定会发出通知,即公共是私有的,不应真正使用。

【讨论】:

以上是关于从 PHP 中的类外部调用私有方法和私有属性的主要内容,如果未能解决你的问题,请参考以下文章

为啥可以从对象范围之外调用私有方法?

如何从java类外部调用私有方法

对象的私有/公有/静态/特权 属性/方法

Objective-C:(私有/公共属性)为外部类调用设置只读属性,为自调用设置只读属性

Python中的类和方法使用举例

面向对象