Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

444 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, static::class);
  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. }
  151. public function setDataFromRecordResponse(array $data): bool
  152. {
  153. $ret = true;
  154. foreach ($data as $fieldCode => $ele) {
  155. $type = data_get($ele, "type");
  156. $value = data_get($ele, "value");
  157. if ($type === "__ID__") {
  158. $this->recordId = $value;
  159. continue;
  160. }
  161. if ($type === "__REVISION__") {
  162. $this->revision = $value;
  163. continue;
  164. }
  165. if ($type === "CREATED_TIME") {
  166. $this->createdAt = DateUtil::parse($value);
  167. continue;
  168. }
  169. if ($type === "UPDATED_TIME") {
  170. $this->updatedAt = DateUtil::parse($value);
  171. continue;
  172. }
  173. $type = FieldType::tryFrom($type);
  174. if ($type === null) continue;
  175. if (in_array($type, [FieldType::DATETIME, FieldType::DATE])) {
  176. if ($value) {
  177. data_set($this->data, $fieldCode, DateUtil::parse($value));
  178. } else {
  179. data_set($this->data, $fieldCode, null);
  180. }
  181. continue;
  182. }
  183. if ($type === FieldType::FILE) {
  184. $ret = [];
  185. foreach ($value as $f) {
  186. $ret[] = new File($f);
  187. }
  188. data_set($this->data, $fieldCode, $ret);
  189. continue;
  190. }
  191. if ($type === FieldType::SUBTABLE) {
  192. continue;
  193. }
  194. // 以外はそのまま格納
  195. data_set($this->data, $fieldCode, $value);
  196. }
  197. if ($this->recordId === null) {
  198. throw new LogicException(sprintf("レコード番号取得失敗 :%s", static::class));
  199. }
  200. $ret = $this->setDataCustom($data);
  201. if ($ret) {
  202. $this->clean();
  203. }
  204. return $ret;
  205. }
  206. /**
  207. * 変更前データを現在データで上書きする
  208. *
  209. * @return void
  210. */
  211. public function clean(?int $revision = null)
  212. {
  213. $this->dataOrigin = clone $this->data;
  214. if ($revision !== null) {
  215. $this->revision = $revision;
  216. }
  217. $this->changed = [];
  218. }
  219. public function getApiLayout(): array
  220. {
  221. $ret = [];
  222. foreach (static::FIELDS as $fieldCode => $type) {
  223. // 変更があった項目のみレイアウトへ出力する
  224. if (!Arr::has($this->changed, $fieldCode)) {
  225. continue;
  226. }
  227. $path = sprintf("%s.value", $fieldCode);
  228. if ($type === FieldType::DATETIME) {
  229. $data = $this->getDate($fieldCode);
  230. if ($data) {
  231. data_set($ret, $path, $data->toIso8601ZuluString());
  232. } else {
  233. data_set($ret, $path, "");
  234. }
  235. continue;
  236. }
  237. if ($type === FieldType::DATE) {
  238. $data = $this->getDate($fieldCode);
  239. if ($data) {
  240. data_set($ret, $path, $data->toDateString());
  241. } else {
  242. data_set($ret, $path, "");
  243. }
  244. continue;
  245. }
  246. data_set($ret, $path, data_get($this->data, $fieldCode));
  247. }
  248. return array_merge($ret, $this->getApiLayoutCustom());
  249. }
  250. public function get(string $key)
  251. {
  252. return data_get($this->data, $key);
  253. }
  254. public function getStr(string $key): ?string
  255. {
  256. return $this->get($key);
  257. }
  258. public function getNumber(string $key): ?int
  259. {
  260. return $this->get($key);
  261. }
  262. public function getDate(string $key): ?Carbon
  263. {
  264. return $this->get($key);
  265. }
  266. public function getTable(string $key): ?array
  267. {
  268. return $this->get($key);
  269. }
  270. public function setRecordId(string $id): static
  271. {
  272. $this->recordId = $id;
  273. return $this;
  274. }
  275. public function getRecordId(): ?string
  276. {
  277. return $this->recordId;
  278. }
  279. public function getRevision(): ?int
  280. {
  281. return $this->revision;
  282. }
  283. public function getUpdatedAt(): ?Carbon
  284. {
  285. return $this->updatedAt;
  286. }
  287. public function getCreatedAt(): ?Carbon
  288. {
  289. return $this->createdAt;
  290. }
  291. public function save()
  292. {
  293. $access = static::getAccess();
  294. if ($this->recordId === null) {
  295. $access->create($this);
  296. } else {
  297. $access->update($this);
  298. }
  299. }
  300. public function toArray($column = ['*']): array
  301. {
  302. if ($this->recordId === null) {
  303. throw new LogicException("保存前モデルのシリアライズ検知");
  304. }
  305. $ret = [
  306. 'record_no' => $this->recordId,
  307. 'revision' => $this->revision,
  308. ];
  309. $columnAll = data_get($column, 0) === '*';
  310. /**
  311. * @var string $fieldCode
  312. */
  313. foreach ($this->data as $fieldCode => $value) {
  314. if (!$columnAll && !in_array($fieldCode, $column)) {
  315. continue;
  316. }
  317. $type = data_get(static::FIELDS, $fieldCode);
  318. $columnName = data_get(static::FIELD_NAMES, $fieldCode, null);
  319. if ($columnName === null) {
  320. continue;
  321. }
  322. if ($type === null) {
  323. $ret[$columnName] = $value;
  324. continue;
  325. }
  326. if ($type === FieldType::DATETIME) {
  327. if ($value instanceof Carbon) {
  328. $ret[$columnName] = $value->format('Y/m/d H:i');
  329. } else {
  330. $ret[$columnName] = $value;
  331. }
  332. continue;
  333. }
  334. if ($type === FieldType::DATE) {
  335. if ($value instanceof Carbon) {
  336. $ret[$columnName] = $value->format('Y/m/d');
  337. } else {
  338. $ret[$columnName] = $value;
  339. }
  340. continue;
  341. }
  342. $ret[$columnName] = $value;
  343. }
  344. $ret = array_merge($ret, $this->toArrayCustom());
  345. return $ret;
  346. }
  347. protected function toArrayCustom(): array
  348. {
  349. return [];
  350. }
  351. /**
  352. * オーバーライドを期待
  353. *
  354. * @param array $data
  355. * @return boolean
  356. */
  357. protected function setDataCustom(array $data): bool
  358. {
  359. return true;
  360. }
  361. /**
  362. * オーバーライドを期待
  363. *
  364. * @return array
  365. */
  366. protected function getApiLayoutCustom(): array
  367. {
  368. return [];
  369. }
  370. }