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: | |
114: | protected function run(string ...$args) |
115: | { |
116: | $this->startRun(); |
117: | |
118: | if ($this->HttpProviders) { |
119: | $provider = $this->HttpProviders[$this->ProviderBasename]; |
120: | } else { |
121: | $provider = $this->Provider; |
122: | |
123: | if (!is_a( |
124: | $this->App->getName($provider), |
125: | HttpSyncProvider::class, |
126: | true, |
127: | )) { |
128: | throw new CliInvalidArgumentsException(sprintf( |
129: | '%s does not inherit %s', |
130: | $provider, |
131: | HttpSyncProvider::class, |
132: | )); |
133: | } |
134: | |
135: | if (!$this->App->has($provider)) { |
136: | $this->App->singleton($provider); |
137: | } |
138: | } |
139: | |
140: | $provider = $this->App->get($provider); |
141: | |
142: | try { |
143: | $query = Get::filter($this->Query); |
144: | } catch (InvalidArgumentException $ex) { |
145: | throw new CliInvalidArgumentsException(sprintf( |
146: | 'invalid query (%s)', |
147: | $ex->getMessage(), |
148: | )); |
149: | } |
150: | |
151: | $data = $this->Data !== null |
152: | ? $this->getJson($this->Data, false) |
153: | : null; |
154: | |
155: | $curler = $provider->getCurler($this->Endpoint); |
156: | if ($this->Paginate && $curler->getPager() === null) { |
157: | throw new CliInvalidArgumentsException(sprintf( |
158: | '%s does not support pagination', |
159: | $provider->getName(), |
160: | )); |
161: | } |
162: | |
163: | switch ($this->getMethod()) { |
164: | case Method::HEAD: |
165: | $result = $curler->head($query); |
166: | break; |
167: | |
168: | case Method::GET: |
169: | $result = $this->Paginate |
170: | ? $curler->getP($query) |
171: | : $curler->get($query); |
172: | break; |
173: | |
174: | case Method::POST: |
175: | $result = $this->Paginate |
176: | ? $curler->postP($data, $query) |
177: | : $curler->post($data, $query); |
178: | break; |
179: | |
180: | case Method::PUT: |
181: | $result = $curler->put($data, $query); |
182: | break; |
183: | |
184: | case Method::DELETE: |
185: | $result = $curler->delete($data, $query); |
186: | break; |
187: | |
188: | case Method::PATCH: |
189: | $result = $curler->patch($data, $query); |
190: | break; |
191: | } |
192: | |
193: | if (!$this->Paginate) { |
194: | echo Json::prettyPrint($result) . \PHP_EOL; |
195: | return; |
196: | } |
197: | |
198: | |
199: | $count = 0; |
200: | |
201: | if ($this->Stream) { |
202: | foreach ($result as $entity) { |
203: | $count++; |
204: | echo Json::prettyPrint($entity) . \PHP_EOL; |
205: | } |
206: | } else { |
207: | $indent = ' '; |
208: | foreach ($result as $entity) { |
209: | if (!$count++) { |
210: | echo '[' . \PHP_EOL; |
211: | } else { |
212: | echo ',' . \PHP_EOL; |
213: | } |
214: | echo $indent . Json::prettyPrint($entity, 0, \PHP_EOL . $indent); |
215: | } |
216: | if ($count) { |
217: | echo \PHP_EOL . ']' . \PHP_EOL; |
218: | } else { |
219: | echo '[]' . \PHP_EOL; |
220: | } |
221: | } |
222: | |
223: | Console::summary(Inflect::format( |
224: | $count, |
225: | '{{#}} {{#:entity}} retrieved', |
226: | )); |
227: | } |
228: | |
229: | |
230: | |
231: | |
232: | private function getMethod(): string |
233: | { |
234: | if (isset($this->Method)) { |
235: | return $this->Method; |
236: | } |
237: | |
238: | $method = Str::upper((string) Arr::last($this->getNameParts())); |
239: | if (!( |
240: | $method === Method::HEAD |
241: | || $method === Method::GET |
242: | || $method === Method::POST |
243: | || $method === Method::PUT |
244: | || $method === Method::DELETE |
245: | || $method === Method::PATCH |
246: | )) { |
247: | throw new LogicException(sprintf('Invalid method: %s', $method)); |
248: | } |
249: | |
250: | return $this->Method = $method; |
251: | } |
252: | } |
253: | |