選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

477 行
12KB

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