No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

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