Easywechat source code analysis

I. Component directory

SRC - BasicService - BasicService . │ ├─ Anti-Flag - Application. PHP Anti-Flag -... In the middle are services that have the same directory structure as the underlying service, For example, small program services, open platform services, etc. - OfficialAccount public number, Auth │ ├─AccessToken class, AccessToken class, │ ├─AccessToken class, ServiceProvider container class Application. PHP Public Number Entry - Kernel Core Class Library

Take the public account service as an example to analyze the source code of Easywechat

Two, EasyWechat Factory class source code analysis

<? php namespace EasyWeChat; class Factory { 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); }}

Use the component public account service

<? php use EasyWeChat\Factory; $config = [ ... ] ; $app = Factory::officialAccount($config);

$app=new EASYWECHAT \ officialAccount \Application($config)

Instantiation process:

  1. callEasyWeChat\FactoryStatic methods of the class OfficialAccount, becauseEasyWeChat\FactoryThere are no static methods on the class, so __callStatic is called, and the name is called OfficialAccount;
  2. The __CallStatic method is calledEasyWeChat\FactoryClass make method; Meke method returns new $application($config)

Three, EasyWeChat\OfficialAccount\Application class source code analysis

<? php namespace EasyWeChat\OfficialAccount; use EasyWeChat\BasicService; use EasyWeChat\Kernel\ServiceContainer; class Application extends ServiceContainer { protected $providers = [ Auth\ServiceProvider::class, ... BasicService\Jssdk\ServiceProvider::class, ]; }

Operation: $app=new EasyWeChat\ officialAccount \Application($config); But it inherits the Easywechat Kernel ServiceContainer, we go to see the Easywechat Kernel ServiceContainer source code.

== Special attention: Since EasyWeChat\OfficialAccount\Application inherits EasyWeChat\Kernel\ServiceContainer, All operations at this point are executing an object of the EasyWechat officialAccount \Application class. = =

Instantiation process:

  1. To perform theEasyWeChat\Kernel\ServiceContainerClass constructor;
  2. To perform theEasyWeChat\Kernel\ServiceContainerClass’s registerProviders method; $this->getProviders() returns an array that combines all services and components that must be registered with the public account into an array and passes it to the method that registers the service.
<? php namespace EasyWeChat\Kernel; . class ServiceContainer extends Container { ... public function __construct(array $config = [], array $prepends = [], String $id = null) {//$app=new EasyWechat \ officialAccount \Application($config) {$this->userConfig = $config; parent::__construct($prepends); $this->id = $id; $this->id = $id; $this->registerProviders($this->getProviders()); $this->aggregate(); $this->events->dispatch(new Events\ApplicationInitialized($this)); } public function getProviders() { return array_merge([ ConfigServiceProvider::class, LogServiceProvider::class, RequestServiceProvider::class, HttpClientServiceProvider::class, ExtensionServiceProvider::class, EventDispatcherServiceProvider::class, ], $this->providers); } public function __get($app->) {return if ($this->shouldDelegate($id)) {return if ($this->shouldDelegate($id)) {return if ($app->) {return if ($this->shouldDelegate($id)) $this->delegateTo($id); } return $this->offsetGet($id); } public function __set($id, $value) {$this->offsetSet($id, $value); $app->property=$value; } public function registerProviders(array $providers) { foreach ($providers as $provider) { parent::register(new $provider()); }}}

RegisterProviders of EasyWechat Kernel ServiceContainer class

  1. The variable $providers in the registerProviders method
  2. Loop the $providers variable to register the service into the container; This action is equivalent to adding attributes to the $app object. See 4 for specific realization

    $providers = [ ConfigServiceProvider::class, LogServiceProvider::class, Menu\ServiceProvider::class, ... BasicService\Url\ServiceProvider::class, BasicService\Jssdk\ServiceProvider::class, ]; // The $providers variable merges the $providers property of the EasyWeChat officialAccount \Application class with the getProviders property of the EasyWeChat Kernel ServiceContainer class

Pimple\Container class source code analysis

The EasyWeChat\ officialAccount \Application class inherits from the EasyWeChat\Kernel\ServiceContainer class inherits from the Pimple\Container The object $app of the EasyWechat officialAccount \Application class has the methods and properties of the ServiceContainer and Container classes, The actions in both the ServiceContainer and Container classes are equivalent to applying the $app object.

<? php namespace Pimple; . class Container implements \ArrayAccess { private $values = []; private $factories; private $protected; private $frozen = []; private $raw = []; private $keys = []; public function __construct(array $values = []) { $this->factories = new \SplObjectStorage(); $this->protected = new \SplObjectStorage(); foreach ($values as $key => $value) { $this->offsetSet($key, $value); } } public function offsetSet($id, $value) { if (isset($this->frozen[$id])) { throw new FrozenServiceException($id); } $this->values[$id] = $value; $this->keys[$id] = true; } public function offsetGet($id) { if (! isset($this->keys[$id])) { throw new UnknownIdentifierException($id); } if ( isset($this->raw[$id]) || ! \is_object($this->values[$id]) || isset($this->protected[$this->values[$id]]) || ! \method_exists($this->values[$id], '__invoke') ) { return $this->values[$id]; } if (isset($this->factories[$this->values[$id]])) { return $this->values[$id]($this); } $raw = $this->values[$id]; $val = $this->values[$id] = $raw($this); $this->raw[$id] = $raw; $this->frozen[$id] = true; return $val; } public function register(ServiceProviderInterface $provider, array $values = []) { $provider->register($this); foreach ($values as $key => $value) { $this[$key] = $value; } return $this; }}

Instantiation process:

  1. EasyWeChat\Kernel\ServiceContainerThe registerProviders method of the class is calledContainerRegister method of the class;
  2. $provider->register($this). $this is the $app object

    <?php
    namespace EasyWeChat\OfficialAccount\Menu;
    use Pimple\Container;
    use Pimple\ServiceProviderInterface;
    
    class ServiceProvider implements ServiceProviderInterface
    {
        public function register(Container $app)
        {
            $app['menu'] = function ($app) {
                return new Client($app);
            };
        }
    }

$provider = new EasyWechat \ officialAccount \Menu\ServiceProvider(); Because the EasyWechat Kernel ServiceContainer class inherits the Pimple Container class, the EasyWechat Kernel ServiceContainer class inherits the Pimple Container class, the EasyWechat Kernel ServiceContainer class inherits the Pimple Container class. The Pimple\Container class implements the \ArrayAccess interface, so an assignment using the $app[‘menu’] syntax executes the offsetSet method of the Pimple\Container class.

  1. The offsetSet method of the Pimple\Container class

    public function offsetSet($id, $value) { if (isset($this->frozen[$id])) { throw new FrozenServiceException($id); } $this->values[$id] = $value; $this->keys[$id] = true; $id=menu, $value=function ($app) {return new Client($app); $app =menu; }; // As to why id and value are like this, see ArrayAccess source analysis
  2. The offsetGet method of the Pimple\Container class

    $app->menu = $app->menu = $app->menu = $app->menu = $app->menu = $app->menu; // $app->menu calls the EasyWechat Kernel ServiceContainer class __get magic method; //3, The EasyWechat Kernel ServiceContainer class __get magic method calls offsetGet; $app->offsetGet('menu') public $app->offsetGet('menu') public = $app->__get('menu'); $app->offsetGet('menu') public function offsetGet($id) { if (! Isset ($this - > keys ($id))) {/ / was offsetSet set at this time to true throw new UnknownIdentifierException ($id); } the if (isset ($this - > raw / $id) / / for the first time, not at this time due to offsetSet method to false | |! \ is_object ($this - > values / $id) | | isset ($this - > protected [$this - > values [$id]]) / / for the first time, not at this time due to offsetSet method to false | |! \method_exists($this->values[$id], '__invoke') ) { return $this->values[$id]; } the if (isset ($this - > factories [$this - > values [$id]])) {/ / for the first time, False return $this->values[$id]($this); } $raw = $this->values[$id]; $val = $this->values[$id] = $raw($this); $this->raw[$id] = $raw; $this->frozen[$id] = true; return $val; }

An anonymous function is an object, and there is an __invoke method, so when you use the offsetGet method to get the value, you can use the __invoke method. \is_object($this->values[$id]), ! \method_exists($this->values[$id], ‘__invoke’) is false;

  1. $this->values[$id] = $raw($this)

    $this->values[$id] = $this->values[‘menu’]; $this = function ($app) {return new Client($app); }.

    $this- BBB values[‘menu’] = new Client($app); $this- BBB values[‘menu’] = new Client($app); $app->menu->list(); $app->menu->list(); $app->menu->list(); For this operation, he only uses the Menu function, not the User function, etc. At this time, it is completely unnecessary if we all instantiate.

When and where is the AccessToken acquired

Take the Menu menu function as an example

$list = $app->menu->list(); $list = $app->menu->list();

$app->menu returns an instance of the EasyWeChat\ officialAccount \ menu \Client class <? php namespace EasyWeChat\OfficialAccount\Menu; use Pimple\Container; use Pimple\ServiceProviderInterface; class ServiceProvider implements ServiceProviderInterface { public function register(Container $app) { $app['menu'] = function ($app) { return new Client($app); }; }}
/ / EasyWeChat \ OfficialAccount \ Menu \ Client class <? php namespace EasyWeChat\OfficialAccount\Menu; use EasyWeChat\Kernel\BaseClient; class Client extends BaseClient { public function list() { return $this->httpGet('cgi-bin/menu/get'); }... }

Instantiation steps:

  1. Execute the HttpGet in the Easywechat Kernel BaseClient class, and finally locate to execute the Request method in the Easywechat Kernel BaseClient class;
  2. Request method of EasyWeChat Kernel BaseClient class

    <? php namespace EasyWeChat\Kernel; . class BaseClient { public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null) { $this->app = $app; $this->accessToken = $accessToken ?? $this->app['access_token']; } public function httpGet(string $url, array $query = []) { return $this->request($url, 'GET', ['query' => $query]); } public function request(string $url, string $method = 'GET', array $options = [], $this-> registerHttpMiddleWares () {$this-> registerHttpMiddleWares (); } $response = $this-> PerformRequest ($url, $method, $options); $this->app->events->dispatch(new Events\HttpResponseCreated($response)); return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); } protected function registerHttpMiddlewares() { // retry $this->pushMiddleware($this->retryMiddleware(), 'retry'); // access token $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token'); $this->pushMiddleware($this->logMiddleware(), 'log'); } protected function accessTokenMiddleware() { return function (callable $handler) { return function (RequestInterface $request, array $options) use ($handler) {if ($this->accessToken) {//3, accessToken, $request = $this->accessToken->applyToRequest($request, $options); $this->accessToken->applyToRequest($request, $options); } return $handler($request, $options); }; }; } protected function retryMiddleware() { return Middleware::retry(function ( $retries, RequestInterface $request, ResponseInterface $response = null ) { if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) { $response = json_decode($body, true); if (! Empty ($response['errcode']) &&in_array (abs($response['errcode']), [40001, 40014, 42001], true)) { If the request fails, the token will be re-requested. If you set the token directly, you can set the http.max_retries parameter to cancel and retrieve token $this-> accesstoken-> refresh(); $this->app['logger']->debug('Retrying with refreshed access token.'); return true; } } return false; }, function () { return abs($this->app->config->get('http.retry_delay', 500)); }); }}

6. About setting AccessToken directly

The public number to get the AccessToken method is finally called EasyWechat Kernel AccessToken class getToken method

<? php namespace EasyWeChat\Kernel; . abstract class AccessToken implements AccessTokenInterface { ... public function getToken(bool $refresh = false): array { $cacheKey = $this->getCacheKey(); $cache = $this->getCache(); if (! $refresh && $cache->has($cache ->get($cacheKey)) && $result = $cache->get($cacheKey)); } /** @var array $token */ $token = $this->requestToken($this->getCredentials(), true); $token['expires_in']?? $token['expires_in']?? 7200); $this->app->events->dispatch(new Events\AccessTokenRefreshed($this)); return $token; }... }

So if you don’t want to get the token through appid and secret or you just need to set the token before you use it, you can do that

$app = Factory::officialAccount($config); $app['access_token']->setToken('ccfdec35bd7ba359f6101c2da321d675'); / / or specified expiration time $app [' access_token '] - > setToken (' ccfdec35bd7ba359f6101c2da321d675, 3600); // Unit: seconds