Laravel4如何访问"静态方法"像实例方法?

时间:2014-09-06 12:57:40

标签: php laravel inversion-of-control

我对Laravel 4很陌生。到目前为止,我仍在努力了解外观,依赖注入和IoC究竟是什么。 (我一直在阅读很多关于他们的内容,我仍然在那里"试图弄清楚" 阶段。)

我相信如果有人可以向我解释为什么我们可以写(类似于红宝石类的方法),我会突然意识到我是多么愚蠢并且理解一切。

$post = Post::where('slug', '=', $slug)->first();
像这样

$post = $this->post->where('slug', '=', $slug)->first();

之后

protected $post;
public function __construct(Post $post)
{
    parent::__construct();

    $this->post = $post;
}


让我感到困惑的是,我们正在访问静态方法Post::where('slug', '=', $slug)->first(),但在一些实现/魔术之后,我们可以像使用->的实例方法一样访问它。

这就像我们刚刚将一个类放入一个对象中。

但我觉得他们不是真正的静态方法(经过一些我尚未完全理解的文章之后)。

这个外观和/或一些IoC技巧在起作用吗?这是怎么回事?

1 个答案:

答案 0 :(得分:3)

这很简单: Facade ,实际上,服务定位器。它在IoC容器内定位实例化服务(对象),并在动态调用对象方法时转换对Facade的静态调用。所以,你是对的,它们并不是静态的,实际上它们是静态调用,但随后它们被转发为动态调用。我们来看看这个过程:

服务实例

这是由服务提供商完成的,但这只是一个约定,因为您可以在需要时在IoC容器中实例化和填充对象。这是一个基本的服务提供商:

class MailerServiceProvider extends ServiceProvider {

    public function register()
    {
        $this->app->bindShared('mailer-ioc-alias', function($app)
        {
            return new Mailer();
        });
    }

}

如您所见,register方法只是将一个闭包绑定到名为mailer-ioc-alias的IoC容器中。一旦我们的应用程序需要访问我们的邮件程序,就会运行该关闭,实例化我们的邮件程序并将其处理回调用者。此调用与以下内容完全相同:

App::bindShared('mailer-ioc-alias', function($app)
{
   return new Mailer();
});

或者,因为我们的Mailer没有依赖关系并且IoC容器足够聪明,可以按类名实例化对象,甚至

App::bindShared('mailer-ioc-alias', 'Mailer');

但是,如果你的类有依赖关系,你可能会被迫使用那个闭包:

App::bindShared('mailer-ioc-alias', function($app)
{
   $mailService = new Mailgun();

   return new Mailer($mailService);
});

因此,例如,如果您将其中一行放在routes.php文件中,它将完全相同。你甚至不需要服务提供商。设计不好,但也许有助于理解服务提供商所做的事情非常简单。服务提供者和您的服务一样复杂,如果它有很多依赖关系,您的服务提供者可能会有很多实例化调用的方法。

通过Facade执行服务定位和动态方法

如上所述,Facade几乎不执行任何操作,它基本上采用服务别名(mailer-ioc-alias),向IoC Container询问与之相关的实例,并在其收到的实例上调用动态方法。这是一个完整的Facade扩展:

class MailerFacade extends Facade {

    protected static function getFacadeAccessor()
    {
        return 'mailer-ioc-alias';
    }

}

正如您所看到的,它只有一种方法,可以返回您的服务的IoC别名。这个类是Illuminate\Support\Facades\Facade的扩展,它有这个PHP魔术方法,负责将静态调用转换为动态调用:

public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    switch (count($args))
    {
        case 0:
            return $instance->$method();

        case 1:
            return $instance->$method($args[0]);

        case 2:
            return $instance->$method($args[0], $args[1]);

        case 3:
            return $instance->$method($args[0], $args[1], $args[2]);

        case 4:
            return $instance->$method($args[0], $args[1], $args[2], $args[3]);

        default:
            return call_user_func_array(array($instance, $method), $args);
    }
}

它通过getFacadeRoot()获取您的服务实例,该实例将在内部调用您的getFacadeAccessor(),并在其上执行动态方法。

所以,这个静态调用:

Mailer::send($user, 'myview');

实际上是一个动态的电话,非常伪装成静态。

但您可以在系统中的任何位置动态调用您的邮件程序:

App::make('mailer-ioc-alias')->send($user, 'myView');

或者使用app()全局函数,该函数返回应用程序的实例:

app()->make('mailer-ioc-alias')->send($user, 'myView');

在服务提供商和其他一些Laravel课程中,您还可以通过以下方式访问它:

$this->app->make('mailer-ioc-alias')->send($user, 'myView');

所有这些动态调用正是Facade为您内部所做的事情。

雄辩的静态/动态难题

Eloquent模型的“问题”是它们不使用Facades,它们是不使用IoC容器的完整静态类。当你打电话:

$post = Post::where('slug', '=', $slug);

它在内部实例化:

$instance = new static;

相同
$instance = new Post;

并自行返回,to->provide->that->nesting->we->all->love

return $this;

但是如果你看一下代码,你就会看到这个神奇的方法:

public static function __callStatic($method, $parameters)
{
    $instance = new static;

    return call_user_func_array(array($instance, $method), $parameters);
}

这一行

return call_user_func_array(array($instance, $method), $parameters);

只有一个参数,可以翻译成

$instance->{$method}($parameters[0]);

return $instance;

它以这种方式做事,所以你进一步:

$post->where('user_id', $user->id);

因为它总是回归自己,所以你可以嵌套来电:

$post->where('user_id', $user->id)->where('region', 'US');

它是有状态的,所以你可以继续这样做:

$post->orderBy('region');

foreach(Input::all() as $property => $value)
{
    $post->where($property, 'LIKE', "%{$value}%");
}

直到你:

$result = $post->get();

或者

$result = $post->first();

当它构建并执行查询时,返回集合或模型。

因此,第一次调用它实际上是静态的,但所有后续调用都被发送到实例化对象。