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.

406 lines
9.7KB

  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. $response = Http::withHeaders([
  156. "X-Cybozu-API-Token" => $this->apiToken,
  157. ])->post($this->getRecordUrl(), [
  158. "app" => $this->appId,
  159. "record" => $model->getApiLayout(),
  160. ]);
  161. if ($response->failed()) {
  162. $e = $response->toException();
  163. if ($e instanceof Exception) {
  164. Log::error($e->getMessage());
  165. throw $e;
  166. }
  167. }
  168. $model->clean($response["revision"]);
  169. $model->setRecordId($response["id"]);
  170. return $response;
  171. }
  172. public function createCursor(KintoneRecordQuery|null $query = null)
  173. {
  174. $data = [
  175. "app" => $this->appId,
  176. 'fields' => $this->fields,
  177. ];
  178. if ($query !== null) {
  179. $data["query"] = $query->toQuery();
  180. }
  181. $response = Http::withHeaders([
  182. "X-Cybozu-API-Token" => $this->apiToken,
  183. ])->post($this->getCursorUrl(), $data);
  184. if ($response->failed()) {
  185. $e = $response->toException();
  186. if ($e instanceof Exception) {
  187. Log::error($e->getMessage(), ['data' => $data]);
  188. throw $e;
  189. }
  190. }
  191. $cursor = $response["id"];
  192. $totalCount = $response["totalCount"];
  193. if (0 < $totalCount) {
  194. $this->hasNext = true;
  195. $this->cursor = $cursor;
  196. $this->cursorDataCount = $totalCount;
  197. } else {
  198. $this->cursor = null;
  199. $this->hasNext = false;
  200. $this->cursorDataCount = 0;
  201. }
  202. return $response;
  203. }
  204. /**
  205. * @return Collection<int,TValue>
  206. */
  207. public function next()
  208. {
  209. if (!$this->hasNext) {
  210. return collect();
  211. }
  212. $response = Http::withHeaders([
  213. "X-Cybozu-API-Token" => $this->apiToken,
  214. ])->get($this->getCursorUrl(), [
  215. "id" => $this->cursor,
  216. ]);
  217. if ($response->failed()) {
  218. $e = $response->toException();
  219. if ($e instanceof Exception) {
  220. Log::error($e->getMessage());
  221. throw $e;
  222. }
  223. }
  224. $ret = collect();
  225. $hasNext = $response["next"];
  226. if (!$hasNext) {
  227. $this->cursor = null;
  228. $this->hasNext = false;
  229. $this->cursorDataCount = 0;
  230. }
  231. foreach ($response["records"] as $data) {
  232. /**
  233. * @var KintoneModel $model
  234. */
  235. $model = new $this->appName();
  236. $model->setDataFromRecordResponse($data);
  237. $ret->push($model);
  238. }
  239. return $ret;
  240. }
  241. /**
  242. * @return Collection<int,TValue>
  243. */
  244. public function all(KintoneRecordQuery|null $query = null)
  245. {
  246. if ($this->cursor !== null) {
  247. $this->deleteCursor();
  248. }
  249. $this->createCursor($query);
  250. $list = collect();
  251. while (true) {
  252. $ret = $this->next();
  253. foreach ($ret as $ele) {
  254. $list->push($ele);
  255. }
  256. if ($this->cursor === null) {
  257. break;
  258. }
  259. }
  260. return $list;
  261. }
  262. public function deleteCursor()
  263. {
  264. if ($this->cursor === null) {
  265. return;
  266. }
  267. $response = Http::withHeaders([
  268. "X-Cybozu-API-Token" => $this->apiToken,
  269. ])->delete($this->getCursorUrl(), [
  270. "id" => $this->cursor,
  271. ]);
  272. if ($response->failed()) {
  273. $e = $response->toException();
  274. if ($e instanceof Exception) {
  275. Log::error($e->getMessage());
  276. throw $e;
  277. }
  278. }
  279. $this->cursor = null;
  280. $this->hasNext = false;
  281. $this->cursorDataCount = 0;
  282. }
  283. public function fileGet(string $fileKey)
  284. {
  285. $response = Http::withHeaders([
  286. "X-Cybozu-API-Token" => $this->apiToken,
  287. "Content-Type" => "application/json",
  288. ])->get($this->getCursorUrl(), [
  289. "fileKey" => $fileKey,
  290. ]);
  291. if ($response->failed()) {
  292. $e = $response->toException();
  293. if ($e instanceof Exception) {
  294. Log::error($e->getMessage());
  295. throw $e;
  296. }
  297. }
  298. return $response;
  299. }
  300. public function filePut($file): string
  301. {
  302. $response = Http::withHeaders([
  303. "X-Cybozu-API-Token" => $this->apiToken,
  304. "Content-Type" => "multipart/form-data",
  305. ])->post($this->getCursorUrl(), $file);
  306. if ($response->failed()) {
  307. $e = $response->toException();
  308. if ($e instanceof Exception) {
  309. Log::error($e->getMessage());
  310. throw $e;
  311. }
  312. }
  313. return $response[''];
  314. }
  315. private function getRecordUrl()
  316. {
  317. return $this->getUrl(self::URL_RECORD);
  318. }
  319. private function getRecordsUrl()
  320. {
  321. return $this->getUrl(self::URL_RECORDS);
  322. }
  323. private function getCursorUrl()
  324. {
  325. return $this->getUrl(self::URL_CURSOL);
  326. }
  327. private function getFileUrl()
  328. {
  329. return $this->getUrl(self::URL_FILE);
  330. }
  331. private function getUrl(string $path)
  332. {
  333. return $this->host . $path;
  334. }
  335. }