You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

453 line
11KB

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