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.

410 lines
9.8KB

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