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 lines
12KB

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