1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Sync\Command; |
4: | |
5: | use Salient\Cli\Exception\CliInvalidArgumentsException; |
6: | use Salient\Cli\CliOption; |
7: | use Salient\Contract\Cli\CliOptionType; |
8: | use Salient\Contract\Cli\CliOptionValueType; |
9: | use Salient\Contract\Http\HttpRequestMethod as Method; |
10: | use Salient\Core\Facade\Console; |
11: | use Salient\Sync\Http\HttpSyncProvider; |
12: | use Salient\Utility\Arr; |
13: | use Salient\Utility\Get; |
14: | use Salient\Utility\Inflect; |
15: | use Salient\Utility\Json; |
16: | use Salient\Utility\Str; |
17: | use InvalidArgumentException; |
18: | use LogicException; |
19: | |
20: | |
21: | |
22: | |
23: | final class SendHttpSyncProviderRequest extends AbstractSyncCommand |
24: | { |
25: | private string $ProviderBasename = ''; |
26: | |
27: | private string $Provider = HttpSyncProvider::class; |
28: | private string $Endpoint = ''; |
29: | |
30: | private array $Query = []; |
31: | private ?string $Data = null; |
32: | private bool $Paginate = false; |
33: | private bool $Stream = false; |
34: | |
35: | |
36: | |
37: | |
38: | private string $Method; |
39: | |
40: | public function getDescription(): string |
41: | { |
42: | return sprintf( |
43: | 'Send a %s request to an HTTP provider', |
44: | $this->getMethod(), |
45: | ); |
46: | } |
47: | |
48: | protected function getOptionList(): iterable |
49: | { |
50: | $method = $this->getMethod(); |
51: | $builder = CliOption::build() |
52: | ->name('provider') |
53: | ->required(); |
54: | |
55: | if ($this->HttpProviders) { |
56: | yield $builder |
57: | ->optionType(CliOptionType::ONE_OF_POSITIONAL) |
58: | ->allowedValues(array_keys($this->HttpProviders)) |
59: | ->bindTo($this->ProviderBasename); |
60: | } else { |
61: | yield $builder |
62: | ->description('The fully-qualified name of the HTTP provider to use') |
63: | ->optionType(CliOptionType::VALUE_POSITIONAL) |
64: | ->bindTo($this->Provider); |
65: | } |
66: | |
67: | yield from [ |
68: | CliOption::build() |
69: | ->name('endpoint') |
70: | ->description("The endpoint to request, e.g. '/posts'") |
71: | ->optionType(CliOptionType::VALUE_POSITIONAL) |
72: | ->required() |
73: | ->bindTo($this->Endpoint), |
74: | CliOption::build() |
75: | ->long('query') |
76: | ->short('q') |
77: | ->valueName('field=value') |
78: | ->description('A query parameter to apply to the request') |
79: | ->optionType(CliOptionType::VALUE) |
80: | ->multipleAllowed() |
81: | ->bindTo($this->Query), |
82: | ]; |
83: | |
84: | if (!($method === Method::HEAD || $method === Method::GET)) { |
85: | yield CliOption::build() |
86: | ->long('data') |
87: | ->short('J') |
88: | ->valueName('file') |
89: | ->description('The path to JSON-serialized data to submit with the request') |
90: | ->optionType(CliOptionType::VALUE) |
91: | ->valueType(CliOptionValueType::FILE_OR_DASH) |
92: | ->bindTo($this->Data); |
93: | } |
94: | |
95: | if ($method === Method::GET || $method === Method::POST) { |
96: | yield from [ |
97: | CliOption::build() |
98: | ->long('paginate') |
99: | ->short('P') |
100: | ->description('Use pagination to iterate over the response') |
101: | ->bindTo($this->Paginate), |
102: | CliOption::build() |
103: | ->long('stream') |
104: | ->short('s') |
105: | ->description('Output a stream of entities when pagination is used') |
106: | ->bindTo($this->Stream), |
107: | ]; |
108: | } |
109: | |
110: | yield from $this->getGlobalOptionList(); |
111: | } |
112: | |
113: | protected function run(string ...$args) |
114: | { |
115: | $this->startRun(); |
116: | |
117: | if ($this->HttpProviders) { |
118: | $provider = $this->HttpProviders[$this->ProviderBasename]; |
119: | } else { |
120: | $provider = $this->Provider; |
121: | |
122: | if (!is_a( |
123: | $this->App->getName($provider), |
124: | HttpSyncProvider::class, |
125: | true, |
126: | )) { |
127: | throw new CliInvalidArgumentsException(sprintf( |
128: | '%s does not inherit %s', |
129: | $provider, |
130: | HttpSyncProvider::class, |
131: | )); |
132: | } |
133: | |
134: | if (!$this->App->has($provider)) { |
135: | $this->App->singleton($provider); |
136: | } |
137: | } |
138: | |
139: | $provider = $this->App->get($provider); |
140: | |
141: | try { |
142: | $query = Get::filter($this->Query); |
143: | } catch (InvalidArgumentException $ex) { |
144: | throw new CliInvalidArgumentsException(sprintf( |
145: | 'invalid query (%s)', |
146: | $ex->getMessage(), |
147: | )); |
148: | } |
149: | |
150: | $data = $this->Data !== null |
151: | ? $this->getJson($this->Data, false) |
152: | : null; |
153: | |
154: | $curler = $provider->getCurler($this->Endpoint); |
155: | if ($this->Paginate && $curler->getPager() === null) { |
156: | throw new CliInvalidArgumentsException(sprintf( |
157: | '%s does not support pagination', |
158: | $provider->getName(), |
159: | )); |
160: | } |
161: | |
162: | switch ($this->getMethod()) { |
163: | case Method::HEAD: |
164: | $result = $curler->head($query); |
165: | break; |
166: | |
167: | case Method::GET: |
168: | $result = $this->Paginate |
169: | ? $curler->getP($query) |
170: | : $curler->get($query); |
171: | break; |
172: | |
173: | case Method::POST: |
174: | $result = $this->Paginate |
175: | ? $curler->postP($data, $query) |
176: | : $curler->post($data, $query); |
177: | break; |
178: | |
179: | case Method::PUT: |
180: | $result = $curler->put($data, $query); |
181: | break; |
182: | |
183: | case Method::DELETE: |
184: | $result = $curler->delete($data, $query); |
185: | break; |
186: | |
187: | case Method::PATCH: |
188: | $result = $curler->patch($data, $query); |
189: | break; |
190: | } |
191: | |
192: | if (!$this->Paginate) { |
193: | echo Json::prettyPrint($result) . \PHP_EOL; |
194: | return; |
195: | } |
196: | |
197: | |
198: | $count = 0; |
199: | |
200: | if ($this->Stream) { |
201: | foreach ($result as $entity) { |
202: | $count++; |
203: | echo Json::prettyPrint($entity) . \PHP_EOL; |
204: | } |
205: | } else { |
206: | $indent = ' '; |
207: | foreach ($result as $entity) { |
208: | if (!$count++) { |
209: | echo '[' . \PHP_EOL; |
210: | } else { |
211: | echo ',' . \PHP_EOL; |
212: | } |
213: | echo $indent . Json::prettyPrint($entity, 0, \PHP_EOL . $indent); |
214: | } |
215: | if ($count) { |
216: | echo \PHP_EOL . ']' . \PHP_EOL; |
217: | } else { |
218: | echo '[]' . \PHP_EOL; |
219: | } |
220: | } |
221: | |
222: | Console::summary(Inflect::format( |
223: | $count, |
224: | '{{#}} {{#:entity}} retrieved', |
225: | )); |
226: | } |
227: | |
228: | |
229: | |
230: | |
231: | private function getMethod(): string |
232: | { |
233: | if (isset($this->Method)) { |
234: | return $this->Method; |
235: | } |
236: | |
237: | $method = Str::upper((string) Arr::last($this->getNameParts())); |
238: | if (!( |
239: | $method === Method::HEAD |
240: | || $method === Method::GET |
241: | || $method === Method::POST |
242: | || $method === Method::PUT |
243: | || $method === Method::DELETE |
244: | || $method === Method::PATCH |
245: | )) { |
246: | throw new LogicException(sprintf('Invalid method: %s', $method)); |
247: | } |
248: | |
249: | return $this->Method = $method; |
250: | } |
251: | } |
252: | |