Just to recap, previously we'd have this sort of thing:
namespace me\adamcameron\testApp; use GuzzleHttp\Client; class GuzzleAdapter { private $client; private $endPoint; public function __construct($endPoint){ $this->endPoint = $endPoint; $this->client = new Client(); } public function get($id){ $response = $this->client->requestAsync( "get", $this->endPoint . $id ); return $response; } }
And say a logging decorator for that:
namespace me\adamcameron\testApp; class LoggedGuzzleAdapter { private $adapter; private $logger; public function __construct($adapter, $logger) { $this->adapter = $adapter; $this->logger = $logger; } public function get($id){ $this->logger->logMessage(sprintf("Requesting for %s", $id)); $response = $this->adapter->get($id); $body = $response->getBody(); $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body)); return $response; } }
And we'd init our adapter thus:
$endPoint = "http://cf2016.local:8516/cfml/misc/guzzleTestEndpoints/getById.cfm?id="; $guzzleAdapter = new GuzzleAdapter($endPoint); $logger = new LoggingService(); $adapter = new LoggedGuzzleAdapter($guzzleAdapter, $logger);
So the underlying GuzzleAdapter handles the Guzzle stuff, the LoggedGuzzleAdapter handles just the logging stuff, but defers to its GuzzleAdapter to do its part of the job, and keeps all the moving parts and bits of functionality sensibly separated. And it's pretty simple. And as detailed in those earlier articles, we can keep layering decorators around an adapter to add caching or what-have-you in a similar way. Easy. Nice.
However this only works cos the call to Guzzle actually returns the result on the spot. And this is cos we were using it synchronously: we make a call to it, it blocks until it gets the answer back from the target and gives us the answer.
Now that we're using async calls, Guzzle doesn't give us the answer, it just gives us a Promise which will eventually resolve to be the answer. This is in theory good cos it means the calling code can make a bunch of HTTP calls, and not wait around for each of them to resolve in series: Guzzle will actually make them all in parallel. I have a look at this in article " PHP: async requests using Guzzle and request pools ".
If we go back to our decorator we can see the problem :
public function get($id){ $this->logger->logMessage(sprintf("Requesting for %s", $id)); $response $promisedResponse = $this->adapter->get($id); $body = $response $promisedResponse->getBody(); $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body)); return $response $promisedResponse; }
At the point at which our decorator needs the body ... we don't have it yet. All we have is a promise that at some point we'll have a body (or we'll have response object via the resolved promise, anyhow; and the response object will have the body ).
To get the body we first need to wait for the promise to resolve... which is a blocking operation and kinda defeats the purpose of using the async approach in the first place. IE: we could do this sort of thing :
public function get($id){ $this->logger->logMessage(sprintf("Requesting for %s", $id)); $promisedResponse = $this->adapter->get($id); $response = $promisedResponse->wait(); $body = $response->getBody(); $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body)); return $response; }
But doing the wait immediately defeats the purpose of making the call async in the first place. We want the wait call to... err... wait ... until the calling code says "right I need to actually use that data now".
Our initial attempt to sort this out was to analyse the issue as being one of "well we don't have the data we need until we call wait , so then we need to do the logging then... which means we need to intercept the wait call... which means we need to return our own decorated version of the response from the call to the adapter with its own wait and have a LoggedResponse, and return that from the code above..." But... and I hope my colleague doesn't mind me saying... when I saw this code in code review I kinda went "there must be a better, more semantic way of doing this". I won't repeat the code as I cannot use our actual work code in my blog, and I am not in the office right now and can't remember the detail of the implementation anyhow. But it's not ideal, so I don't want to share it anyhow.
I've been off sick for the last coupla days, which gives me a lot of time to deliberate and mess around with stuff and google a lot more than I allow myself time for when I'm in the office. With a wee bit of "standing back and having another think about it", the solution became clear. We don't need to explicitly override the wait method before we call it... the promise builds that capability in! At the time we're making the call, we get to tell the Promise what happens when it gets resolved. So I've knocked together this proof of concept:
public function get($id){ $this->logger->logMessage(sprintf("(%s) Requesting for %s", $this->thisFile, $id)); $response = $this->adapter->get($id); $response->then(function($response) use ($id){ $body = $response->getBody(); $this->logger->logMessage(sprintf("(%s) Response for %s: %s", $this->thisFile, $id, $body)); $body->rewind(); }); return $response; }
默认路由从url映射到文件的应用层级可通过配置项:route_app_hierarchy=>1,来修改。即应用n层*控制器n层来处理复杂的业务分层
控制器后缀支持自定义配置.默认还是Controller
mysql支持关闭数据缓存
CmlPHP 是基于php5.3+(v2.7+要求php5.4+)版本(已经测试过php7)开发的MVC/HMVC/MVSC/HMVSC框架,支持composer、分布式数据库、分布式缓存,支持文件、memcache、redis、apc等缓存,thg支持多种url模式、URL路由[RESTful],支持多项目集成、第三方扩展、支持插件。