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.

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