Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

533 řádky
14KB

  1. <?php
  2. namespace App\Kintone\Models;
  3. use App\Exceptions\ConfigException;
  4. use App\Files\TmpFile;
  5. use App\Kintone\File;
  6. use App\Kintone\KintoneAccess;
  7. use App\Kintone\KintoneAccessStore;
  8. use App\Kintone\KintoneRecordQuery;
  9. use App\Kintone\Models\SubTable\SubTableData;
  10. use App\Util\DateUtil;
  11. use Illuminate\Http\UploadedFile;
  12. use Illuminate\Support\Arr;
  13. use Illuminate\Support\Carbon;
  14. use Illuminate\Support\Collection;
  15. use Illuminate\Support\Str;
  16. use LogicException;
  17. use stdClass;
  18. abstract class KintoneModel
  19. {
  20. const CONFIG_KEY = "";
  21. const BASE_MODEL = null;
  22. static public function configKey(): string
  23. {
  24. if (!static::CONFIG_KEY) {
  25. throw new LogicException(sprintf("Kintone 設定キー 未設定:%s", static::class));
  26. }
  27. return static::CONFIG_KEY;
  28. }
  29. static public function setConfig(): array
  30. {
  31. $configStr = env(static::configKey());
  32. if (!$configStr) {
  33. throw new ConfigException(static::configKey(), $configStr);
  34. }
  35. $params = explode(",", $configStr);
  36. if (count($params) !== 2) {
  37. throw new ConfigException(static::configKey(), $configStr);
  38. }
  39. [$apiToken, $appId] = $params;
  40. return [static::class => [
  41. 'apiToken' => $apiToken,
  42. 'appId' => (int)$appId,
  43. ]];
  44. }
  45. /**
  46. * @return KintoneAccess<static>
  47. */
  48. static public function getAccess(): KintoneAccess
  49. {
  50. $target = static::BASE_MODEL ?? static::class;
  51. $store = KintoneAccessStore::instance();
  52. if ($store->has($target)) {
  53. return $store->get($target);
  54. }
  55. $access = new KintoneAccess($target, static::class);
  56. $access->setFields(array_keys(static::FIELDS));
  57. foreach (static::RELATIONS as $relation) {
  58. $access->addAppToken($relation);
  59. }
  60. $store->set($target, $access);
  61. return $access;
  62. }
  63. /**
  64. * @return KintoneRecordQuery<static>
  65. */
  66. static public function getQuery(): KintoneRecordQuery
  67. {
  68. $target = static::BASE_MODEL ?? static::class;
  69. return new KintoneRecordQuery($target);
  70. }
  71. /**
  72. * @return static
  73. */
  74. public static function find(string $recordId)
  75. {
  76. return static::getAccess()->find($recordId);
  77. }
  78. /**
  79. * @return static
  80. */
  81. public static function first(?KintoneRecordQuery $query = null)
  82. {
  83. return static::getAccess()->first($query);
  84. }
  85. public static function getDropDownOptions(string $fieldCode): array
  86. {
  87. $ret = [];
  88. $properties = static::getAccess()->getAppFormFields();
  89. $options = Arr::get($properties, sprintf("%s.options", $fieldCode));
  90. $ret = Arr::pluck(Arr::sort($options, function ($option) {
  91. return data_get($option, "index");
  92. }), "label");
  93. return $ret;
  94. }
  95. protected ?string $recordId = null;
  96. protected ?int $revision = null;
  97. protected ?Carbon $createdAt = null;
  98. protected ?Carbon $updatedAt = null;
  99. protected ?stdClass $dataOrigin = null;
  100. protected stdClass $data;
  101. const FIELD_RECORD_NO = "レコード番号";
  102. protected const FIELDS = [];
  103. protected const SUB_TABLES = [];
  104. protected const FIELD_NAMES = [];
  105. protected const RELATIONS = [];
  106. private array $changed = [];
  107. public function __construct()
  108. {
  109. $this->data = new stdClass();
  110. }
  111. public function __get($name)
  112. {
  113. $field = "FIELD_" . Str::of($name)->snake()->upper()->toString();
  114. $constantPath = sprintf("%s::%s", static::class, $field);
  115. if (defined($constantPath)) {
  116. return $this->get(constant($constantPath));
  117. } else {
  118. throw new LogicException(sprintf("未定義のメンバアクセス %s %s", static::class, $field));
  119. }
  120. }
  121. public function __set($name, $value)
  122. {
  123. $field = "FIELD_" . Str::of($name)->snake()->upper()->toString();
  124. $constantPath = sprintf("%s::%s", static::class, $field);
  125. if (defined($constantPath)) {
  126. return $this->set(constant($constantPath), $value);
  127. } else {
  128. throw new LogicException(sprintf("未定義のメンバアクセス %s %s", static::class, $field));
  129. }
  130. }
  131. public function set(string $fieldCode, $value)
  132. {
  133. $type = $this->getFieldType($fieldCode);
  134. if ($type) {
  135. $this->setData($fieldCode, $value);
  136. data_set($this->changed, $fieldCode, true);
  137. }
  138. return $this;
  139. }
  140. private function setData(string $path, $value)
  141. {
  142. data_set($this->data, $path, $value);
  143. return $this;
  144. }
  145. public function setDataFromRecordResponse(array $data): bool
  146. {
  147. $ret = true;
  148. foreach ($data as $fieldCode => $ele) {
  149. $type = data_get($ele, "type");
  150. $value = data_get($ele, "value");
  151. if ($type === "__ID__") {
  152. $this->recordId = $value;
  153. continue;
  154. }
  155. if ($type === "__REVISION__") {
  156. $this->revision = $value;
  157. continue;
  158. }
  159. if ($type === "CREATED_TIME") {
  160. $this->createdAt = DateUtil::parse($value);
  161. continue;
  162. }
  163. if ($type === "UPDATED_TIME") {
  164. $this->updatedAt = DateUtil::parse($value);
  165. continue;
  166. }
  167. data_set($this->data, $fieldCode, $this->readData($ele, $fieldCode));
  168. }
  169. if ($this->recordId === null) {
  170. throw new LogicException(sprintf("レコード番号取得失敗 :%s", static::class));
  171. }
  172. $ret = $this->setDataCustom($data);
  173. if ($ret) {
  174. $this->clean();
  175. }
  176. return $ret;
  177. }
  178. private function readData(array $data, string $fieldCode = "")
  179. {
  180. $type = data_get($data, "type");
  181. $value = data_get($data, "value");
  182. if ($type === null || $value === null) {
  183. return null;
  184. }
  185. $type = FieldType::tryFrom($type);
  186. if ($type !== null) {
  187. if (in_array($type, [FieldType::DATETIME, FieldType::DATE])) {
  188. if ($value) {
  189. return DateUtil::parse($value);
  190. } else {
  191. return null;
  192. }
  193. }
  194. if ($type === FieldType::NUMBER) {
  195. return intval($value);
  196. }
  197. if ($type === FieldType::FILE) {
  198. $ret = [];
  199. foreach ($value as $f) {
  200. $ret[] = new File($f);
  201. }
  202. return $ret;
  203. }
  204. if ($type === FieldType::SUBTABLE && $this->getFieldType($fieldCode)) {
  205. $ret = collect();
  206. foreach ($value as $v) {
  207. $rowRet =
  208. $rowArray = data_get($v, 'value');
  209. foreach ($rowArray as $subFieldCode => $ele) {
  210. $rowRet[$subFieldCode] = $this->readData($ele);
  211. }
  212. $ret->push($this->getSubTableData($fieldCode, $rowRet));
  213. }
  214. return $ret;
  215. }
  216. return $value;
  217. }
  218. return null;
  219. }
  220. private function getSubTableData(string $fieldCode, array $data): SubTableData
  221. {
  222. $className = data_get(static::SUB_TABLES, $fieldCode, "");
  223. if (class_exists($className)) {
  224. return new $className($data);
  225. }
  226. throw new LogicException("キントーン設定不備");
  227. }
  228. /**
  229. * 変更前データを現在データで上書きする
  230. *
  231. * @return void
  232. */
  233. public function clean(?int $revision = null)
  234. {
  235. $this->dataOrigin = clone $this->data;
  236. if ($revision !== null) {
  237. $this->revision = $revision;
  238. }
  239. $this->changed = [];
  240. }
  241. public function getApiLayout(): array
  242. {
  243. $ret = [];
  244. foreach (static::FIELDS as $fieldCode => $type) {
  245. // 変更があった項目のみレイアウトへ出力する
  246. if (!Arr::has($this->changed, $fieldCode)) {
  247. continue;
  248. }
  249. $path = sprintf("%s.value", $fieldCode);
  250. data_set($ret, $path, $this->getApiLayoutValue($fieldCode));
  251. }
  252. return array_merge($ret, $this->getApiLayoutCustom());
  253. }
  254. private function getApiLayoutValue(string $fieldCode)
  255. {
  256. $ret = [];
  257. $data = $this->get($fieldCode);
  258. $type = $this->getFieldType($fieldCode);
  259. if ($type === FieldType::DATETIME) {
  260. $data = $this->getDate($fieldCode);
  261. if ($data) {
  262. return $data->toIso8601ZuluString();
  263. } else {
  264. return "";
  265. }
  266. }
  267. if ($type === FieldType::DATE) {
  268. $data = $this->getDate($fieldCode);
  269. if ($data) {
  270. return $data->toDateString();
  271. } else {
  272. return "";
  273. }
  274. }
  275. if ($data instanceof Collection) {
  276. $ret = [];
  277. foreach ($data as $ele) {
  278. if ($ele instanceof SubTableData) {
  279. $obj = [];
  280. $obj['value'] = $ele->toArray();
  281. $ret[] = $obj;
  282. }
  283. }
  284. return $ret;
  285. }
  286. return data_get($this->data, $fieldCode);
  287. }
  288. private function getFieldType(string $fieldCode): ?FieldType
  289. {
  290. return data_get(static::FIELDS, $fieldCode);
  291. }
  292. public function get(string $key)
  293. {
  294. return data_get($this->data, $key);
  295. }
  296. public function getStr(string $key): ?string
  297. {
  298. return $this->get($key);
  299. }
  300. public function getNumber(string $key): ?int
  301. {
  302. return $this->get($key);
  303. }
  304. public function getDate(string $key): ?Carbon
  305. {
  306. return $this->get($key);
  307. }
  308. public function getTable(string $key): ?array
  309. {
  310. return $this->get($key);
  311. }
  312. public function setRecordId(string $id): static
  313. {
  314. $this->recordId = $id;
  315. return $this;
  316. }
  317. public function getRecordId(): ?string
  318. {
  319. return $this->recordId;
  320. }
  321. public function getRevision(): ?int
  322. {
  323. return $this->revision;
  324. }
  325. public function getUpdatedAt(): ?Carbon
  326. {
  327. return $this->updatedAt;
  328. }
  329. public function getCreatedAt(): ?Carbon
  330. {
  331. return $this->createdAt;
  332. }
  333. /**
  334. * @param string $fieldCode
  335. * @param Collection<int, UploadedFile|TmpFile> $files
  336. * @param bool $override
  337. * @return static
  338. */
  339. public function setFiles(string $fieldCode, Collection $files, bool $override = true): static
  340. {
  341. if ($override) {
  342. $this->set($fieldCode, []);
  343. }
  344. foreach ($files as $file) {
  345. $this->addFile($fieldCode, $file);
  346. }
  347. return $this;
  348. }
  349. public function addFile(string $fieldCode, UploadedFile|TmpFile $file)
  350. {
  351. if ($file instanceof UploadedFile) {
  352. $name = sprintf("image_%d.%s", Str::uuid(), $file->extension());
  353. $contentType = $file->getClientMimeType();
  354. } else {
  355. $name = $file->getAppFileName();
  356. $contentType = $file->getMimeType();
  357. }
  358. $access = $this->getAccess();
  359. $field = $this->get($fieldCode);
  360. array_push($field, [
  361. 'fileKey' => $access->filePut($file),
  362. 'name' => $name,
  363. 'contentType' => $contentType,
  364. ]);
  365. $this->set($fieldCode, $field);
  366. return $this;
  367. }
  368. public function save()
  369. {
  370. $access = static::getAccess();
  371. if ($this->recordId === null) {
  372. $access->create($this);
  373. } else {
  374. $access->update($this);
  375. }
  376. }
  377. public function toArray($column = ['*']): array
  378. {
  379. if ($this->recordId === null) {
  380. throw new LogicException("保存前モデルのシリアライズ検知");
  381. }
  382. $ret = [
  383. 'record_no' => $this->recordId,
  384. 'revision' => $this->revision,
  385. ];
  386. $columnAll = data_get($column, 0) === '*';
  387. /**
  388. * @var string $fieldCode
  389. */
  390. foreach ($this->data as $fieldCode => $value) {
  391. if (!$columnAll && !in_array($fieldCode, $column)) {
  392. continue;
  393. }
  394. $type = data_get(static::FIELDS, $fieldCode);
  395. $columnName = data_get(static::FIELD_NAMES, $fieldCode, null);
  396. if ($columnName === null) {
  397. continue;
  398. }
  399. if ($type === null) {
  400. $ret[$columnName] = $value;
  401. continue;
  402. }
  403. if ($type === FieldType::DATETIME) {
  404. if ($value instanceof Carbon) {
  405. $ret[$columnName] = $value->format('Y/m/d H:i');
  406. } else {
  407. $ret[$columnName] = $value;
  408. }
  409. continue;
  410. }
  411. if ($type === FieldType::DATE) {
  412. if ($value instanceof Carbon) {
  413. $ret[$columnName] = $value->format('Y/m/d');
  414. } else {
  415. $ret[$columnName] = $value;
  416. }
  417. continue;
  418. }
  419. $ret[$columnName] = $value;
  420. }
  421. $ret = array_merge($ret, $this->toArrayCustom());
  422. return $ret;
  423. }
  424. protected function toArrayCustom(): array
  425. {
  426. return [];
  427. }
  428. /**
  429. * オーバーライドを期待
  430. *
  431. * @param array $data
  432. * @return boolean
  433. */
  434. protected function setDataCustom(array $data): bool
  435. {
  436. return true;
  437. }
  438. /**
  439. * オーバーライドを期待
  440. *
  441. * @return array
  442. */
  443. protected function getApiLayoutCustom(): array
  444. {
  445. return [];
  446. }
  447. }