├── .gitignore ├── README.md ├── composer.json ├── examples └── laravel │ ├── README.md │ ├── apollo.php │ └── env │ └── .env_tpl.php └── src └── ApolloClient.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | apolloConfig.* 4 | test.php 5 | vendor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [携程Apollo](https://github.com/ctripcorp/apollo)的PHP客户端 2 | 3 | ## install 4 | php version >= 7.0 5 | ```bash 6 | $ composer require multilinguals/apollo-client 7 | ``` 8 | php version >= 5.4 , <7.0 9 | ```bash 10 | $ composer require multilinguals/apollo-client --ignore-platform-reqs 11 | ``` 12 | 13 | ## Features 14 | - 支持apollo配置变更的实时获取 15 | - 支持拉取配置后自定义的回调处理 16 | 17 | ## Usage 18 | 客户端以cli的方式后台启动执行,支持apollo配置的适时获取,并将配置保存在指定的目录供应用程序读取解析 19 | 20 | ### 客户端示例代码 21 | ```php 22 | #!/usr/bin/env php 23 | setClientIp($clientIp); 41 | } 42 | 43 | ini_set('memory_limit','128M'); 44 | $pid = getmypid(); 45 | echo "start [$pid]\n"; 46 | $restart = true; //auto start if failed 47 | do { 48 | $error = $apollo->start(); 49 | if ($error) echo('error:'.$error."\n"); 50 | }while($error && $restart); 51 | ``` 52 | 53 | ### 配置管理 54 | 55 | 拉取的配置默认保存在脚本所在目录,每个namespace的配置以`apolloConfig.{$namespaceName}.php`的方式命名保存 56 | 57 | ### Docker环境客户端自启动 58 | 59 | 在docker的启动脚本中加入启动代码,一般的php容器启动脚本是docker-php-entrypoint 60 | ```bash 61 | if [ -f "/path/to/start.php" ]; then 62 | apollo_ps=$(ps -aux | grep -c "php /path/to/start.php") 63 | if [ $apollo_ps -eq 1 ]; then 64 | php /path/to/start.php & 65 | fi 66 | fi 67 | ``` 68 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multilinguals/apollo-client", 3 | "description": "apollo client for php", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "Apollo", 8 | "Client" 9 | ], 10 | "homepage": "https://github.com/multilinguals/apollo-php-client", 11 | "require": { 12 | "php": "~7.0" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Org\\Multilinguals\\Apollo\\Client\\": "src/" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /examples/laravel/README.md: -------------------------------------------------------------------------------- 1 | ### apollo client for laravel 2 | 3 | #### 启动apollo客户端 4 | php apollo.php -------------------------------------------------------------------------------- /examples/laravel/apollo.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setClientIp($clientIp); 49 | * } 50 | */ 51 | 52 | //从apollo上拉取的配置默认保存在脚本目录,可自行设置保存目录 53 | $apollo->save_dir = SAVE_DIR; 54 | 55 | ini_set('memory_limit','128M'); 56 | $pid = getmypid(); 57 | echo "start [$pid]\n"; 58 | $restart = false; //失败自动重启 59 | do { 60 | $error = $apollo->start($callback); //此处传入回调 61 | if ($error) echo('error:'.$error."\n"); 62 | }while($error && $restart); 63 | -------------------------------------------------------------------------------- /examples/laravel/env/.env_tpl.php: -------------------------------------------------------------------------------- 1 | configServer = $configServer; 25 | $this->appId = $appId; 26 | foreach ($namespaces as $namespace) { 27 | $this->notifications[$namespace] = ['namespaceName' => $namespace, 'notificationId' => -1]; 28 | } 29 | $this->save_dir = dirname($_SERVER['SCRIPT_FILENAME']); 30 | } 31 | 32 | public function setCluster($cluster) 33 | { 34 | $this->cluster = $cluster; 35 | } 36 | 37 | public function setClientIp($ip) 38 | { 39 | $this->clientIp = $ip; 40 | } 41 | 42 | public function setPullTimeout($pullTimeout) { 43 | $pullTimeout = intval($pullTimeout); 44 | if ($pullTimeout < 1 || $pullTimeout > 300) { 45 | return; 46 | } 47 | $this->pullTimeout = $pullTimeout; 48 | } 49 | 50 | public function setIntervalTimeout($intervalTimeout) { 51 | $intervalTimeout = intval($intervalTimeout); 52 | if ($intervalTimeout < 1 || $intervalTimeout > 300) { 53 | return; 54 | } 55 | $this->intervalTimeout = $intervalTimeout; 56 | } 57 | 58 | private function _getReleaseKey($config_file) { 59 | $releaseKey = ''; 60 | if (file_exists($config_file)) { 61 | $last_config = require $config_file; 62 | is_array($last_config) && isset($last_config['releaseKey']) && $releaseKey = $last_config['releaseKey']; 63 | } 64 | return $releaseKey; 65 | } 66 | 67 | //获取单个namespace的配置文件路径 68 | public function getConfigFile($namespaceName) { 69 | return $this->save_dir.DIRECTORY_SEPARATOR.'apolloConfig.'.$namespaceName.'.php'; 70 | } 71 | 72 | //获取单个namespace的配置-无缓存的方式 73 | public function pullConfig($namespaceName) { 74 | $base_api = rtrim($this->configServer, '/').'/configs/'.$this->appId.'/'.$this->cluster.'/'; 75 | $api = $base_api.$namespaceName; 76 | 77 | $args = []; 78 | $args['ip'] = $this->clientIp; 79 | $config_file = $this->getConfigFile($namespaceName); 80 | $args['releaseKey'] = $this->_getReleaseKey($config_file); 81 | 82 | $api .= '?' . http_build_query($args); 83 | 84 | $ch = curl_init($api); 85 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->pullTimeout); 86 | curl_setopt($ch, CURLOPT_HEADER, false); 87 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 88 | 89 | $body = curl_exec($ch); 90 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 91 | $error = curl_error($ch); 92 | curl_close($ch); 93 | 94 | if ($httpCode == 200) { 95 | $result = json_decode($body, true); 96 | $content = 'configServer, '/').'/configs/'.$this->appId.'/'.$this->cluster.'/'; 111 | $query_args = []; 112 | $query_args['ip'] = $this->clientIp; 113 | foreach ($namespaceNames as $namespaceName) { 114 | $request = []; 115 | $config_file = $this->getConfigFile($namespaceName); 116 | $request_url = $base_url.$namespaceName; 117 | $query_args['releaseKey'] = $this->_getReleaseKey($config_file); 118 | $query_string = '?'.http_build_query($query_args); 119 | $request_url .= $query_string; 120 | $ch = curl_init($request_url); 121 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->pullTimeout); 122 | curl_setopt($ch, CURLOPT_HEADER, false); 123 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 124 | $request['ch'] = $ch; 125 | $request['config_file'] = $config_file; 126 | $request_list[$namespaceName] = $request; 127 | curl_multi_add_handle($multi_ch, $ch); 128 | } 129 | 130 | $active = null; 131 | // 执行批处理句柄 132 | do { 133 | $mrc = curl_multi_exec($multi_ch, $active); 134 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 135 | 136 | while ($active && $mrc == CURLM_OK) { 137 | if (curl_multi_select($multi_ch) == -1) { 138 | usleep(100); 139 | } 140 | do { 141 | $mrc = curl_multi_exec($multi_ch, $active); 142 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 143 | 144 | } 145 | 146 | // 获取结果 147 | $response_list = []; 148 | foreach ($request_list as $namespaceName => $req) { 149 | $response_list[$namespaceName] = true; 150 | $result = curl_multi_getcontent($req['ch']); 151 | $code = curl_getinfo($req['ch'], CURLINFO_HTTP_CODE); 152 | $error = curl_error($req['ch']); 153 | curl_multi_remove_handle($multi_ch,$req['ch']); 154 | curl_close($req['ch']); 155 | if ($code == 200) { 156 | $result = json_decode($result, true); 157 | $content = 'configServer, '/').'/notifications/v2?'; 170 | $params = []; 171 | $params['appId'] = $this->appId; 172 | $params['cluster'] = $this->cluster; 173 | do { 174 | $params['notifications'] = json_encode(array_values($this->notifications)); 175 | $query = http_build_query($params); 176 | curl_setopt($ch, CURLOPT_URL, $base_url.$query); 177 | $response = curl_exec($ch); 178 | $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE); 179 | $error = curl_error($ch); 180 | if ($httpCode == 200) { 181 | $res = json_decode($response, true); 182 | $change_list = []; 183 | foreach ($res as $r) { 184 | if ($r['notificationId'] != $this->notifications[$r['namespaceName']]['notificationId']) { 185 | $change_list[$r['namespaceName']] = $r['notificationId']; 186 | } 187 | } 188 | $response_list = $this->pullConfigBatch(array_keys($change_list)); 189 | foreach ($response_list as $namespaceName => $result) { 190 | $result && ($this->notifications[$namespaceName]['notificationId'] = $change_list[$namespaceName]); 191 | } 192 | //如果定义了配置变更的回调,比如重新整合配置,则执行回调 193 | ($callback instanceof \Closure) && call_user_func($callback); 194 | }elseif ($httpCode != 304) { 195 | throw new \Exception($response ?: $error); 196 | } 197 | }while (true); 198 | } 199 | 200 | /** 201 | * @param $callback 监听到配置变更时的回调处理 202 | * @return mixed 203 | */ 204 | public function start($callback = null) { 205 | $ch = curl_init(); 206 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->intervalTimeout); 207 | curl_setopt($ch, CURLOPT_HEADER, false); 208 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 209 | try { 210 | $this->_listenChange($ch, $callback); 211 | }catch (\Exception $e) { 212 | curl_close($ch); 213 | return $e->getMessage(); 214 | } 215 | } 216 | } 217 | --------------------------------------------------------------------------------