connectors
object
%Connector
/phlo/resources/connectors/Connector.phlo
const
Connector :: section
line 10
voidconst
Connector :: api
line 11
voidmethod
%Connector -> __construct (?array $config = null)
line 13
if ($config === null){
$section = static::section
$creds = $section ? %creds->{$section} : null
$config = $creds ? (array)$creds->toArray : []
}
$this->config = $config
$this->timeout = 15
$this->retries = 0static
Connector :: make (?array $config = null):static
line 24
new static($config)method
%Connector -> base
line 26
static::apimethod
%Connector -> headers
line 27
[]static
Connector :: fields
line 29
[]method
%Connector -> configured (...$keys):bool
line 31
foreach ($keys AS $key){
if (($this->config[$key] ?? void) === void) return false
}
return truemethod
%Connector -> missing (...$keys):?obj
line 38
return $this->configured(...$keys) ? null : static::fail(static::section.' credentials not configured ('.implode(', ', $keys).')')static
Connector :: bearer ($token):string
line 42
'Authorization: Bearer '.$tokenstatic
Connector :: basic ($user, $pass):string
line 43
'Authorization: Basic '.base64_encode($user.colon.$pass)static
Connector :: build (string $method, string $url, ?array $query = null, array $headers = [], mixed $json = null, mixed $form = null):array
line 45
if ($query) $url .= (str_contains($url, qm) ? '&' : qm).http_build_query($query)
$body = null
if ($json !== null){
$body = is_string($json) ? $json : json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
$headers[] = 'Content-Type: application/json'
}
elseif ($form !== null){
$body = is_string($form) ? $form : http_build_query($form)
$headers[] = 'Content-Type: application/x-www-form-urlencoded'
}
$headers[] = 'Accept: application/json'
return ['method' => strtoupper($method), 'url' => $url, 'headers' => $headers, 'body' => $body]static
Connector :: ok ($data, int $status = 200):obj
line 60
obj(ok: true, status: $status, data: $data)static
Connector :: fail ($error, int $status = 0):obj
line 61
obj(ok: false, status: $status, error: $error)static
Connector :: errorMessage ($data, string $raw, int $status):string
line 63
if (is_object($data)){
if (isset($data->error->message)) return (string)$data->error->message
if (isset($data->error) && is_string($data->error)) return $data->error
if (isset($data->message)) return (string)$data->message
if (isset($data->errors)){
$errors = $data->errors
if (is_string($errors)) return $errors
if (is_array($errors)) return is_string($errors[0] ?? null) ? $errors[0] : json_encode($errors)
if (is_object($errors)) return json_encode($errors)
}
}
return $raw !== void ? $raw : 'HTTP '.$statusstatic
Connector :: parse ($raw, int $status = 200):obj
line 78
$raw = (string)$raw
$data = $raw === void ? null : json_decode($raw)
if ($status < 200 || $status >= 300) return static::fail(static::errorMessage($data, $raw, $status), $status)
return obj(ok: true, status: $status, data: $data ?? $raw)static
Connector :: retryable ($method, int $status):bool
line 85
in_array($method, ['GET', 'HEAD']) && ($status === 429 || $status >= 500)static
Connector :: backoff (int $attempt, $response):int
line 87
$after = (int)($response->headers['retry-after'] ?? 0)
return $after > 0 ? min($after, 30) * 1000000 : 200000 * $attemptmethod
%Connector -> dispatch (array $req):obj
line 92
$method = $req['method']
$body = $req['body']
$attempt = 0
$response = null
while (true){
try {
if ($method === 'GET') $raw = HTTP($req['url'], $req['headers'], cookies: false, timeout: $this->timeout, response: $response)
elseif ($method === 'DELETE') $raw = HTTP($req['url'], $req['headers'], DELETE: true, cookies: false, timeout: $this->timeout, response: $response)
elseif ($method === 'PUT') $raw = HTTP($req['url'], $req['headers'], PUT: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
elseif ($method === 'PATCH') $raw = HTTP($req['url'], $req['headers'], PATCH: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
else $raw = HTTP($req['url'], $req['headers'], POST: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
}
catch (\Throwable $e){
return static::fail($e->getMessage(), 0)
}
$status = $response->status ?? 0
if (($status >= 200 && $status < 300) || !static::retryable($method, $status) || $attempt >= $this->retries) break
usleep(static::backoff(++$attempt, $response))
}
$result = static::parse($raw, $status)
$result->headers = $response->headers ?? []
return $resultmethod
%Connector -> request (string $method, string $url, ?array $query = null, array $headers = [], mixed $json = null, mixed $form = null):obj
line 117
if (!str_starts_with($url, 'http')) $url = rtrim((string)$this->base, slash).slash.ltrim($url, slash)
$headers = array_merge((array)$this->headers, $headers)
return $this->dispatch(static::build($method, $url, $query, $headers, $json, $form))method
%Connector -> get (string $url, ?array $query = null, array $headers = []):obj
line 123
$this->request('GET', $url, query: $query, headers: $headers)method
%Connector -> post (string $url, mixed $json = null, array $headers = []):obj
line 124
$this->request('POST', $url, headers: $headers, json: $json)method
%Connector -> put (string $url, mixed $json = null, array $headers = []):obj
line 125
$this->request('PUT', $url, headers: $headers, json: $json)method
%Connector -> patch (string $url, mixed $json = null, array $headers = []):obj
line 126
$this->request('PATCH', $url, headers: $headers, json: $json)method
%Connector -> del (string $url, array $headers = []):obj
line 127
$this->request('DELETE', $url, headers: $headers)method
%Connector -> form (string $url, array $fields, array $headers = []):obj
line 128
$this->request('POST', $url, headers: $headers, form: $fields)method
%Connector -> paginate (string $url, callable $extract, ?array $query = null, string $param = 'page', int $start = 1, int $max = 0):array
line 130
$items = []
$page = $start
while (true){
$res = $this->get($url, ($query ?? []) + [$param => $page])
if (!$res->ok) break
$batch = $extract($res->data)
if (!$batch) break
foreach ($batch AS $item) $items[] = $item
if ($max && count($items) >= $max) break
$page++
}
return $itemsobject
%OAuthConnector
/phlo/resources/connectors/OAuthConnector.phlo
const
OAuthConnector :: tokenUrl
line 11
voidmethod
%OAuthConnector -> oauthKey
line 13
static::sectionprop
%OAuthConnector -> token
line 15
TokenStore::access($this->oauthKey, static::tokenUrl, $this->config['client_id'] ?? void, $this->config['client_secret'] ?? void, ['refresh_token' => $this->config['refresh_token'] ?? null])method
%OAuthConnector -> headers
line 17
[static::bearer((string)$this->token)]method
%OAuthConnector -> authed
line 19
(string)$this->token !== voidobject
%TokenStore
/phlo/resources/connectors/TokenStore.phlo
static
TokenStore :: path ($key):string
line 10
data.'tokens/'.preg_replace('/[^a-z0-9_.-]+/i', us, (string)$key).'.json'static
TokenStore :: read ($key):array
line 12
$file = static::path($key)
if (!is_file($file)) return []
@chmod(data.'tokens', 0700)
@chmod($file, 0600)
return (array)json_read($file, true)static
TokenStore :: write ($key, array $token):void
line 20
$dir = data.'tokens'
is_dir($dir) || mkdir($dir, 0700, true)
@chmod($dir, 0700)
$file = static::path($key)
json_write($file, $token)
@chmod($file, 0600)static
TokenStore :: valid (array $token):bool
line 29
($token['access_token'] ?? void) !== void && (int)($token['expires_at'] ?? 0) > time() + 30static
TokenStore :: store ($res, $refresh):array
line 31
$token = [
'access_token' => $res['access_token'],
'refresh_token' => $res['refresh_token'] ?? $refresh,
'expires_at' => time() + (int)($res['expires_in'] ?? 3600),
]
return $tokenstatic
TokenStore :: lock ($key)
line 42
$dir = data.'tokens'
is_dir($dir) || mkdir($dir, 0700, true)
$lock = @fopen(static::path($key).'.lock', 'c')
if (!$lock || !flock($lock, LOCK_EX)){
$lock && fclose($lock)
return null
}
return $lockstatic
TokenStore :: access ($key, $tokenUrl, $clientId, $clientSecret, array $seed = []):?string
line 53
$token = static::read($key)
if (static::valid($token)) return $token['access_token']
$lock = static::lock($key)
if (!$lock) return null
try {
$token = static::read($key)
if (!($token['refresh_token'] ?? null) && ($seed['refresh_token'] ?? null)){
$token = ['refresh_token' => $seed['refresh_token']]
static::write($key, $token)
}
if (static::valid($token)) return $token['access_token']
$refresh = $token['refresh_token'] ?? null
if (!$refresh || !$tokenUrl || !$clientId) return null
$res = OAuth2::refresh($tokenUrl, $clientId, $clientSecret, $refresh)
if (!($res['access_token'] ?? null)) return null
$token = static::store($res, $refresh)
static::write($key, $token)
return $token['access_token']
} finally {
flock($lock, LOCK_UN)
fclose($lock)
}