easywechat源码学习

easywechat源码学习

  • 目的
  • 代码结构
  • Factory
  • Application
  • 服务提供者
  • 利用魔术方法重载
  • ArrayAccess
  • 结合其他组件/库的使用
  • 异常
  • 更多

目的

通过阅读好的代码,学习其中的设计思想和对设计模式的运用,来提升自我代码水平。

代码版本:https://github.com/overtrue/wechat/archive/4.2.8.zip

代码结构

├── Factory.php // 库入口,需要初始化哪个组件就生成哪个组件,例如 Factory::officialAccount($config)
├── BasicService // 基础服务
│   ├── Application.php // 注册了基础服务的容器
│   ├── ContentSecurity
│   ├── Jssdk
│   ├── Media
│   ├── QrCode
│   └── Url
├── Kernel // 核心,包含模块的基础类,以及运行模块所需要的支持
│   ├── AccessToken.php
│   ├── BaseClient.php
│   ├── Clauses
│   ├── Config.php
│   ├── Contracts
│   ├── ...
├── OfficialAccount // 公众号模块
│   ├── Application.php // 调用本公众号模块中所有服务下的ServiceProvider.php,通过服务提供者的方式将整个公众号模块的所有服务注册到服务容器。
│   ├── Auth // auth服务,每个服务下都有一个ServiceProvider.php,用于将自己的类绑定到容器。
│   ├── AutoReply
│   ├── Base
│   ├── Broadcasting
│   ├── Card
│   ├── Comment
│   ├── ...
├── OpenPlatform // 开放平台模块,与公众号模块同理
│   ├── Application.php
│   ├── Auth
│   ├── Authorizer
│   ├── ...
├── ...

使用示例:

$config = [
    'app_id' => 'xxx',
    'secret' => 'xxxxxxxxxx',
    'response_type' => 'array',

    //...
];
$app = Factory::officialAccount($config);
$current = $app->menu->current();

Factory

class Factory
{
    /**
     * @param string $name
     * @param array  $config
     *
     * @return \EasyWeChat\Kernel\ServiceContainer
     */
    public static function make($name, array $config)
    {
        $namespace = Kernel\Support\Str::studly($name);
        $application = "\\EasyWeChat\\{$namespace}\\Application";

        return new $application($config);
    }
    
    public static function __callStatic($name, $arguments)
    {
        return self::make($name, ...$arguments);
    }
}

整个组件的入口,利用魔术方法初始化对应模块,例如 Factory::officialAccount($config)是初始化公众号模块,
会返回\EasyWeChat\OfficialAccount\Application这个类的实例。
这种设计可以很优雅的初始化各个模块,否则的话,使用的时候就得根据模块路径不同而use不同模块。

Application

每个模块下都有一个Application容器类,并负责将本模块下所有服务注册到服务容器中。
例如EasyWeChat\OfficialAccount\Application

class Application extends ServiceContainer
{
    /**
     * @var array
     */
    protected $providers = [
        Auth\ServiceProvider::class,
        Server\ServiceProvider::class,
        User\ServiceProvider::class,
        ...
        // Base services
        BasicService\QrCode\ServiceProvider::class,
        ...
        
    ];
}

这样设计的好处是,可以很方便的管理该模块的所有服务。
假设新增一个服务的话,只需要在模块下创建该服务的文件夹,然后写该服务的一些类,以及一个服务提供者,用于将类注册到服务容器。然后再在Application中增加注册这个新服务,就完成了。

服务提供者

举例公众号模块中Auth服务的服务提供者类Auth\ServiceProvider::class

class ServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}.
     */
    public function register(Container $app)
    {
        !isset($app['access_token']) && $app['access_token'] = function ($app) {
            return new AccessToken($app);
        };
    }
}

在容器类的构造方法中,会通过 registerProviders()方法,最终调用Pimple\Container容器的register方法,将定义的服务注册到服务容器中。
可见是调用了服务提供者的register方法,并将容器本身传递给服务提供者,和上面的代码片段相呼应。

public function register(ServiceProviderInterface $provider, array $values = array())
{
    $provider->register($this);

    foreach ($values as $key => $value) {
        $this[$key] = $value;
    }

    return $this;
}

利用魔术方法重载

比如这段代码:

$app->menu->current();

$app是公众号模块的容器实例,menu是公众号模块下的一个服务,而该容器中并没有提供menu方法,这里就利用到了 __get()对调用menu方法的这个行为进行重载。

    /**
     * Magic get access.
     *
     * @param string $id
     *
     * @return mixed
     */
    public function __get($id)
    {
        if ($this->shouldDelegate($id)) {
            return $this->delegateTo($id);
        }

        return $this->offsetGet($id);
    }

这里的offsetGet()方法最终会返回menu这个服务的对象,然后调用该对象的current()方法来获取公众号的当前菜单。

ArrayAccess

ArrayAccess是一组PHP内置接口,实现该接口的类需要实现offsetExists、offsetGet、offsetSet、offsetUnset四个方法。
当以数组方式调用类时,PHP就会引导到以上实现的方法中,这样就实现了以数组的方式操作类。
例如:
在ServiceContainer中use了WithAggregator这个trait类,其中有一段代码:

/**
  * Aggregate.
  */
 protected function aggregate()
 {
     foreach (EasyWeChat::config() as $key => $value) {
         $this['config']->set($key, $value);
     }
 }

 /**
  * @return bool
  */
 public function shouldDelegate($id)
 {
     return $this['config']->get('delegation.enabled')
         && $this->offsetGet($id) instanceof BaseClient;
 }

ServiceContainer的继承关系:ServiceContainer extends Pimple\Container extends \ArrayAccess
所以这个trait类的this指的就是Container这个类的实例,因为Container类实现了ArrayAccess接口,所以这个实例支持数组方式调用。

结合其他组件/库的使用

https://symfony.com/components
symfony的组件被广泛使用,这里easywechat使用了其中的三个组件,分别是:

symfony/cache
缓存组件,提供了多种缓存方式的实现,遵循了php-fig组织制定的psr-6缓存标准。

symfony/event-dispatcher
Mediator模式的实现

symfony/http-foundation
在PHP中,所述请求是由一些全局变量表示($_GET, $_POST,$_FILES,$_COOKIE,$_SESSION,...)并且通过一些功能生成的响应(echo,header(),setcookie(),...)。
HttpFoundation组件通过面向对象层替换了这些默认的PHP全局变量和函数。


然后还使用了其他的一些组件,如下:

GuzzleHttp\Client
封装了curl请求

Monolog\Logger
写log的组件,遵循了psr-3日志标准。

pimple/pimple
ioc服务容器

异常

TODO

更多

TODO

你可能感兴趣的:(PHP)