Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

493 lines
12KB

  1. <?php
  2. namespace App\Kintone;
  3. use App\Exceptions\AppCommonException;
  4. use App\Exceptions\ConfigException;
  5. use App\Files\TmpFile;
  6. use App\Kintone\Models\KintoneModel;
  7. use Exception;
  8. use Illuminate\Database\Eloquent\ModelNotFoundException;
  9. use Illuminate\Http\UploadedFile;
  10. use Illuminate\Support\Collection;
  11. use Illuminate\Support\Facades\Http;
  12. use Log;
  13. /**
  14. * @template TValue of KintoneModel
  15. */
  16. class KintoneAccess
  17. {
  18. private string $appName;
  19. private ?string $modelName;
  20. private string $host;
  21. private string $apiToken = "";
  22. private int $appId;
  23. private const DEFAULT_FIELDS = [
  24. "作成日時",
  25. "更新日時",
  26. '$id',
  27. '$revision',
  28. ];
  29. private array $fields = [];
  30. private string|null $cursor = null;
  31. private int $cursorDataCount = 0;
  32. private bool $hasNext = false;
  33. private const URL_RECORD = "/k/v1/record.json";
  34. private const URL_RECORDS = "/k/v1/records.json";
  35. private const URL_CURSOL = "/k/v1/records/cursor.json";
  36. private const URL_FILE = "/k/v1/file.json";
  37. private const URL_APP_FORM_FIELDS = "/k/v1/app/form/fields.json";
  38. public function __construct(string $appName, ?string $modelName)
  39. {
  40. $this->appName = $appName;
  41. $this->modelName = $modelName;
  42. $key = "kintone.host";
  43. $host = config($key);
  44. if (!$host) {
  45. throw new ConfigException("kintone.host", $host);
  46. }
  47. $key = "kintone.applications." . $appName . ".apiToken";
  48. $apiToken = config($key);
  49. if (!$apiToken) {
  50. throw new ConfigException($key, $apiToken);
  51. }
  52. $key = "kintone.applications." . $appName . ".appId";
  53. $appId = config($key);
  54. if (!$appId) {
  55. throw new ConfigException($key, $appId);
  56. }
  57. $this->addAppToken($appName);
  58. $this->host = $host;
  59. $this->appId = $appId;
  60. }
  61. public function addAppToken(string $appName): static
  62. {
  63. $key = "kintone.applications." . $appName . ".apiToken";
  64. $apiToken = config($key);
  65. if (!$apiToken) {
  66. throw new ConfigException($key, $apiToken);
  67. }
  68. $this->apiToken = $this->apiToken ? $this->apiToken . "," . $apiToken : $apiToken;
  69. return $this;
  70. }
  71. public function setFields(array $fields): static
  72. {
  73. $this->fields = [
  74. ...$fields,
  75. ...self::DEFAULT_FIELDS,
  76. ];
  77. return $this;
  78. }
  79. public function __destruct()
  80. {
  81. $this->deleteCursor();
  82. }
  83. /**
  84. * @param integer $id
  85. * @return TValue
  86. */
  87. public function find(int $id)
  88. {
  89. $response = Http::withHeaders([
  90. "X-Cybozu-API-Token" => $this->apiToken,
  91. ])->get($this->getRecordUrl(), [
  92. "app" => $this->appId,
  93. "id" => $id,
  94. ]);
  95. if ($response->failed()) {
  96. $e = $response->toException();
  97. if ($e instanceof Exception) {
  98. Log::error($e->getMessage());
  99. throw $e;
  100. }
  101. }
  102. $result = $this->modelName ? new $this->modelName() : new $this->appName();
  103. $result->setDataFromRecordResponse($response['record']);
  104. return $result;
  105. }
  106. /**
  107. * @param KintoneRecordQuery|null|null $query
  108. * @return Collection<string,TValue>
  109. */
  110. public function some(KintoneRecordQuery|null $query = null)
  111. {
  112. $data = [
  113. "app" => $this->appId,
  114. 'fields' => $this->fields,
  115. ];
  116. if ($query !== null) {
  117. $data["query"] = $query->toQuery();
  118. }
  119. $response = Http::withHeaders([
  120. "X-Cybozu-API-Token" => $this->apiToken,
  121. ])->get($this->getRecordsUrl(), $data);
  122. if ($response->failed()) {
  123. $e = $response->toException();
  124. if ($e instanceof Exception) {
  125. Log::error($e->getMessage());
  126. throw $e;
  127. }
  128. }
  129. $ret = collect();
  130. foreach ($response["records"] as $data) {
  131. /**
  132. * @var KintoneModel $model
  133. */
  134. $model = $this->modelName ? new $this->modelName() : new $this->appName();
  135. $model->setDataFromRecordResponse($data);
  136. $ret->put($model->getRecordId(), $model);
  137. }
  138. return $ret;
  139. }
  140. /**
  141. * @param KintoneRecordQuery|null|null $query
  142. * @return TValue
  143. */
  144. public function first(KintoneRecordQuery|null $query = null)
  145. {
  146. $list = $this->some($query);
  147. if ($list->count() !== 1) {
  148. throw new ModelNotFoundException(sprintf("モデル取得数エラー %s count:%d queey:<%s>", $this->appName, $list->count(), $query->toQuery()));
  149. }
  150. return $list->first();
  151. }
  152. /**
  153. * @param TValue $model
  154. * @return void
  155. */
  156. public function update(KintoneModel &$model)
  157. {
  158. $sendData = [
  159. "app" => $this->appId,
  160. "id" => $model->getRecordId(),
  161. "record" => $model->getApiLayout(),
  162. "revision" => $model->getRevision(),
  163. ];
  164. // logger([
  165. // "ACTION" => "KINTONE UPDATE",
  166. // "APP" => $this->appName,
  167. // "RECORD_NO" => $model->getRecordId(),
  168. // "SEND_DATA" => $sendData,
  169. // "JSON" => json_encode($sendData, JSON_UNESCAPED_UNICODE),
  170. // ]);
  171. $response = Http::withHeaders([
  172. "X-Cybozu-API-Token" => $this->apiToken,
  173. ])->put($this->getRecordUrl(), $sendData);
  174. if ($response->failed()) {
  175. $e = $response->toException();
  176. if ($e instanceof Exception) {
  177. Log::error($e->getMessage());
  178. throw $e;
  179. }
  180. }
  181. $model->clean($response["revision"]);
  182. return $response;
  183. }
  184. /**
  185. * @param TValue $model
  186. * @return void
  187. */
  188. public function create(KintoneModel &$model)
  189. {
  190. $sendData = [
  191. "app" => $this->appId,
  192. "record" => $model->getApiLayout(),
  193. ];
  194. $response = Http::withHeaders([
  195. "X-Cybozu-API-Token" => $this->apiToken,
  196. ])->post($this->getRecordUrl(), $sendData);
  197. if ($response->failed()) {
  198. $e = $response->toException();
  199. if ($e instanceof Exception) {
  200. Log::error($e->getMessage());
  201. Log::error($response->body());
  202. Log::error($sendData);
  203. throw $e;
  204. }
  205. }
  206. $model->clean($response["revision"]);
  207. $model->setRecordId($response["id"]);
  208. return $response;
  209. }
  210. public function createCursor(KintoneRecordQuery|null $query = null)
  211. {
  212. $data = [
  213. "app" => $this->appId,
  214. 'fields' => $this->fields,
  215. ];
  216. if ($query !== null) {
  217. $data["query"] = $query->toQuery();
  218. }
  219. $response = Http::withHeaders([
  220. "X-Cybozu-API-Token" => $this->apiToken,
  221. ])->post($this->getCursorUrl(), $data);
  222. if ($response->failed()) {
  223. $e = $response->toException();
  224. if ($e instanceof Exception) {
  225. Log::error($e->getMessage(), ['data' => $data]);
  226. Log::error($response->body());
  227. throw $e;
  228. }
  229. }
  230. $cursor = $response["id"];
  231. $totalCount = intval($response["totalCount"]);
  232. $this->cursor = $cursor;
  233. if (0 < $totalCount) {
  234. $this->hasNext = true;
  235. $this->cursorDataCount = $totalCount;
  236. } else {
  237. $this->deleteCursor();
  238. }
  239. return $response;
  240. }
  241. /**
  242. * @return Collection<int,TValue>
  243. */
  244. public function next()
  245. {
  246. if (!$this->hasNext) {
  247. return collect();
  248. }
  249. $response = Http::withHeaders([
  250. "X-Cybozu-API-Token" => $this->apiToken,
  251. ])->get($this->getCursorUrl(), [
  252. "id" => $this->cursor,
  253. ]);
  254. if ($response->failed()) {
  255. $e = $response->toException();
  256. if ($e instanceof Exception) {
  257. Log::error($e->getMessage());
  258. throw $e;
  259. }
  260. }
  261. $ret = collect();
  262. $hasNext = $response["next"];
  263. if (!$hasNext) {
  264. $this->cursor = null;
  265. $this->hasNext = false;
  266. $this->cursorDataCount = 0;
  267. }
  268. foreach ($response["records"] as $data) {
  269. /**
  270. * @var KintoneModel $model
  271. */
  272. $model = new $this->appName();
  273. $model->setDataFromRecordResponse($data);
  274. $ret->push($model);
  275. }
  276. return $ret;
  277. }
  278. /**
  279. * @return Collection<int,TValue>
  280. */
  281. public function all(KintoneRecordQuery|null $query = null)
  282. {
  283. if ($this->cursor !== null) {
  284. $this->deleteCursor();
  285. }
  286. $this->createCursor($query);
  287. $list = collect();
  288. while (true) {
  289. $ret = $this->next();
  290. foreach ($ret as $ele) {
  291. $list->push($ele);
  292. }
  293. if ($this->cursor === null) {
  294. break;
  295. }
  296. }
  297. return $list;
  298. }
  299. public function deleteCursor()
  300. {
  301. if ($this->cursor === null) {
  302. return;
  303. }
  304. $response = Http::withHeaders([
  305. "X-Cybozu-API-Token" => $this->apiToken,
  306. ])->delete($this->getCursorUrl(), [
  307. "id" => $this->cursor,
  308. ]);
  309. if ($response->failed()) {
  310. $e = $response->toException();
  311. if ($e instanceof Exception) {
  312. Log::error($e->getMessage());
  313. throw $e;
  314. }
  315. }
  316. $this->cursor = null;
  317. $this->hasNext = false;
  318. $this->cursorDataCount = 0;
  319. }
  320. public function fileGet(string $fileKey)
  321. {
  322. $response = Http::withHeaders([
  323. "X-Cybozu-API-Token" => $this->apiToken,
  324. ])->get($this->getFileUrl(), [
  325. "fileKey" => $fileKey,
  326. ]);
  327. if ($response->failed()) {
  328. $e = $response->toException();
  329. if ($e instanceof Exception) {
  330. Log::error($e->getMessage());
  331. Log::error($response->body());
  332. throw $e;
  333. }
  334. }
  335. return $response;
  336. }
  337. /**
  338. * ファイルアップロード
  339. *
  340. * @param UploadedFile|TmpFile $file
  341. * @return string fileKey
  342. */
  343. public function filePut(UploadedFile|TmpFile $file): string
  344. {
  345. $content = "";
  346. $sendFileName = "";
  347. if ($file instanceof UploadedFile) {
  348. $content = file_get_contents($file);
  349. $sendFileName = sprintf("file.%s", $file->extension());
  350. } else if ($file instanceof TmpFile) {
  351. $content = $file->get();
  352. $sendFileName = $file->getAppFileName();
  353. }
  354. $response = Http::withHeaders([
  355. "X-Cybozu-API-Token" => $this->apiToken,
  356. ])
  357. ->attach("file", $content, $sendFileName)
  358. ->post($this->getFileUrl());
  359. if ($response->failed()) {
  360. $e = $response->toException();
  361. if ($e instanceof Exception) {
  362. Log::error($e->getMessage());
  363. Log::error($response->body());
  364. throw $e;
  365. }
  366. }
  367. return $response['fileKey'];
  368. }
  369. public function getAppFormFields(): array
  370. {
  371. $response = Http::withHeaders([
  372. "X-Cybozu-API-Token" => $this->apiToken,
  373. ])->get($this->getAppFormFieldsUrl(), [
  374. "app" => $this->appId,
  375. ]);
  376. if ($response->failed()) {
  377. $e = $response->toException();
  378. if ($e instanceof Exception) {
  379. Log::error($e->getMessage());
  380. throw $e;
  381. }
  382. }
  383. return $response['properties'];
  384. }
  385. private function getRecordUrl()
  386. {
  387. return $this->getUrl(self::URL_RECORD);
  388. }
  389. private function getRecordsUrl()
  390. {
  391. return $this->getUrl(self::URL_RECORDS);
  392. }
  393. private function getCursorUrl()
  394. {
  395. return $this->getUrl(self::URL_CURSOL);
  396. }
  397. private function getFileUrl()
  398. {
  399. return $this->getUrl(self::URL_FILE);
  400. }
  401. private function getAppFormFieldsUrl()
  402. {
  403. return $this->getUrl(self::URL_APP_FORM_FIELDS);
  404. }
  405. private function getUrl(string $path)
  406. {
  407. return $this->host . $path;
  408. }
  409. }