【问题标题】:Is it possible to curry method calls in PHP?是否可以在 PHP 中进行方法调用?
【发布时间】:2009-10-22 21:12:50
【问题描述】:

我有一个为 WSDL 文件生成的 SoapClient 实例。除了一个方法调用之外,所有方法调用都需要传递用户名和密码。

有什么方法可以对方法调用进行柯里化,以便我可以省略用户名和密码?

【问题讨论】:

  • 你能举个例子说明你在想什么吗?

标签: php soap currying


【解决方案1】:

从 php 5.3 开始,您可以将 anonymous function 存储在变量中。这个匿名函数可以调用带有一些预定义参数的“原始”函数。

function foo($x, $y, $z) {
  echo "$x - $y - $z";
}

$bar = function($z) {
  foo('A', 'B', $z);
};

$bar('C');

编辑:您还可以使用闭包来参数化匿名函数的创建

function foo($x, $y, $z) {
  echo "$x - $y - $z";
}

function fnFoo($x, $y) {
  return function($z) use($x,$y) {
    foo($x, $y, $z);
  };
}

$bar = fnFoo('A', 'B');
$bar('C');

edit2:这也适用于对象

class Foo {
  public function bar($x, $y, $z) {
    echo "$x - $y - $z";
  }
}

function fnFoobar($obj, $x, $z) {
  return function ($y) use ($obj,$x,$z) {
    $obj->bar($x, $y, $z);
  };
}

$foo = new Foo;
$bar = fnFoobar($foo, 'A', 'C');
$bar('B');

但如果您想“增强”一个完整的类,使用 __call() 和包装类的其他建议可能会更好。

【讨论】:

  • 太棒了!我对 PHP 有点生疏——我知道我可以枚举对象的方法,但我可以拦截它们吗?
  • 你的意思是像magicTrampoline(array($obj,'methodName'), $myInterceptor) 这样每当$obj->methodName 被调用时,你在$myInterceptor 中的回调就会被调用(没有$obj 显式实现这样的功能)?是否允许magicTrampoline() 返回一个新对象?
  • 如果 magicTrampoline() 能够添加到传递给 $obj->methodName(...) 的参数,是的。
  • 这是处理问题的好方法。但是,这不是咖喱。如果您想了解如何在 PHP 中进行 Curry,请参阅此答案:stackoverflow.com/questions/1609985/…
【解决方案2】:

这是一个实现自动柯里化和部分应用的类:

class lambda
{
    private $f;
    private $args;
    private $count;
    public function __construct($f, $args = [])
    {
        if ($f instanceof lambda) {
            $this->f = $f->f;
            $this->count = $f->count;
            $this->args = array_merge($f->args, $args);
        }
        else {
            $this->f = $f;
            $this->count = count((new ReflectionFunction($f))->getParameters());
            $this->args = $args;
        }
    }

    public function __invoke()
    {
        if (count($this->args) + func_num_args() < $this->count) {
            return new lambda($this, func_get_args());
        }
        else {
            $args = array_merge($this->args, func_get_args());
            $r = call_user_func_array($this->f, array_splice($args, 0, $this->count));
            return is_callable($r) ? call_user_func(new lambda($r, $args)) : $r;
        }
    }
}
function lambda($f)
{
    return new lambda($f);
}

例子:

$add = lambda(function($a, $b) { 
    return $a + $b; 
});
$add1 = $add(1);
echo $add1(2); // 3

你也可以这样做:

$int1 = lambda(function($f, $x) {
    return $f($x);
});

$successor = lambda(function($p, $f, $x) {
    return $f($p($f, $x));
}); 

$add = lambda(function($p, $q, $f, $x) {
    return $p($f, $q($f, $x));
}); 

$mul = lambda(function($p, $q, $x) {
    return $p($q($x));
}); 

$exp = lambda(function($m, $n) {
    return $n($m);
});

$int2 = $successor($int1);
$int3 = $add($int1, $int2);
$int6 = $mul($int3, $int2);
$int8 = $exp($int2, $int3);

【讨论】:

    【解决方案3】:

    PHP 本身没有柯里化,但您可以通过多种方式执行类似的操作。在您的具体情况下,这样的事情可能会起作用:

    class MySoapClient extends SoapClient {
      ...
      public function __call($meth,$args) {
        if (substr($method,0,5) == 'curry') {
          array_unshift($args,PASSWORD);
          array_unshift($args,USERNAME);
          return call_user_func_array(array($this,substr($meth,5)),$args);
        } else {
          return parent::__call($meth,$args);
        }
      }
    }
    $soapClient = new MySoapClient();
    ...
    // now the following two are equivalent
    $soapClient->currysomeMethod($additionalArg);
    $soapClient->someMethod(USERNAME,PASSWORD,$additionalArg);
    

    虽然这里有一个更通用的 PHP >= 5.3 柯里化解决方案:

    $curriedMethod = function ($additionalArg) use ($soapClient) { return $soapClient->method(USERNAME,PASSWORD,$additionalArg); }
    
    $result = $curriedMethod('some argument');
    

    【讨论】:

      【解决方案4】:

      我今天对此做了一些相关的研究。这是我能得到的最接近的:

      function curryAdd($x)
      {
        return function($y = null) use ($x)
        {
          if (is_null($y)) return $x;
          else return curryAdd($x + $y);
        };
      }
      
      // echo curryAdd(1)(2)(3)(4);
      echo curryAdd(1)
        ->__invoke(2)
        ->__invoke(3)
        ->__invoke(4)
        ->__invoke();
      

      主要问题是 PHP 不允许您直接在返回值上执行闭包(很像 PHP 不允许在未绑定对象上执行方法)。但是,由于闭包是 Closure 类型的对象,它有一个内置的方法 __invoke(),所以上面的方法可以工作。

      【讨论】:

        【解决方案5】:

        虽然不是一个很好的解决方案,但您可以编写一个基本的包装类,使用 PHP magic methods(特别是 __call)来调用实际函数,但将用户名和密码附加到参数列表中。

        基本示例:

        class SC
        {
            private $user;
            private $pass;
        
            public function __construct($user, $pass)
            {
                $this->user = $user;
                $this->pass = $pass;
            }
        
            public function __call($name, $arguments) 
            {
                $arguments = array_merge(array($this->user, $this->pass), $arguments);  
                call_user_func_array($name, $arguments);
            }
        }
        

        【讨论】:

        • 谢谢。那是 __call 方法 IIRC 吗?
        • call_user_func 接受参数列表,而不是数组。您的解决方案只会将咖喱参数作为数组发送给函数,而不是参数列表。但是,使用 call_user_func_array() 将起作用:)
        • @JohnKurlak 为你解决了这个问题。 ;-)
        【解决方案6】:

        您可以使用来自Non-standard PHP librarypartial applicationcurry 函数。

        【讨论】:

          【解决方案7】:

          正如 Ihor 所提到的,非标准 PHP 库很有趣。 我已经实现了同样的方法,和Ihor的curried()函数有点不同

          function curryfy($f, $args = []) {
              $reflexion = new ReflectionFunction($f);
              $nbParams = $reflexion->getNumberOfParameters();
          
              return function (...$arguments) use ($f, $reflexion, $nbParams, $args) {
                  if (count($args) + count($arguments) >= $nbParams) {
                      return $reflexion->invokeArgs(array_merge($args, $arguments));
                  }
          
                  return curryfy($f, array_merge($args, $arguments));
              };
          }
          

          用法:

          function display4 ($a, $b, $c, $d) {
              echo "$a, $b, $c, $d\n";
          };
          $curry4 = curryfy('display4');
          display4(1, 2, 3, 4);
          $curry4(1)(2)(3)(4);
          

          【讨论】:

            【解决方案8】:

            这个答案Is it possible to curry method calls in PHP? 没有显示柯里化。该答案显示了部分应用。可以在这里看到一个很好的教程来解释这些概念之间的区别:http://allthingsphp.blogspot.com/2012/02/currying-vs-partial-application.html

            这是柯里化:

            function sum3($x, $y, $z) {
                return $x + $y + $z;
            }
            
            // The curried function    
            function curried_sum3($x) {
                return function ($y) use ($x) {
                    return function ($z) use ($x, $y) {
                        return sum3($x, $y, $z);
                    };
                };
            }
            

            在 PHP 7 中调用柯里化函数

            $result = curried_sum3(1)(2)(3); 
            var_dump($result); // int 6
            

            在 PHP 5 中调用柯里化函数

            $f1 = curried_sum3(6);
            $f2 = $f1(6);
            $result = $f2(6);
            var_dump($result);
            
            //OUTPUT:
            int 18
            

            这是部分申请:

            function sum3($x, $y, $z) {
                return $x + $y + $z;
            }
            
            function partial_sum3($x) {
                return function($y, $z) use($x) {
                    return sum3($x, $y, $z);
                };
            }
            
            //create the partial
            $f1 = partial_sum3(6);
            //execute the partial with the two remaining arguments
            $result = $f1(6, 6);
            
            var_dump($result);
            
            //OUTPUT:
            int 18
            

            【讨论】:

            • 从 PHP 7 开始可以使用短柯里化。所以 $result = curried_sum3(1)(2)(3); 可以工作。
            • @Alfred 我在 php 7.0.1 中对其进行了测试。你是 100% 正确的。我会改变我的答案。无论如何,我认为在 php.net/manual/en/migration70.new-features.php 的新功能页面中未提及此功能是不合适的。你能解释一下为什么php7的制造者没有提到这个吗?
            • 我也没有在新功能列表中找到它。我通过对不同版本的测试发现了这一点。我认为这是在 PHP7 中使用闭包的一个特性。
            • @Alfred 我在php.net/manual/en/functions.anonymous.php#122182 的评论部分找到了这个。不过,当我想了解 php 的新功能时,这并不是我首先要寻找的地方。但有趣的是,函数式编程如何在 php 7 中变得更易于访问。
            猜你喜欢
            • 2010-10-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多