diff --git a/.env.example b/.env.example index b45d4b4..2794909 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ APP_KEY= APP_DEBUG=true APP_URL=http://localhost -LOG_CHANNEL=stack +LOG_CHANNEL=web LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug diff --git a/app/Features/InstanceAble.php b/app/Features/InstanceAble.php new file mode 100644 index 0000000..d567d65 --- /dev/null +++ b/app/Features/InstanceAble.php @@ -0,0 +1,14 @@ +make(self::class); + } +} diff --git a/app/Http/Controllers/Web/BaseParam.php b/app/Http/Controllers/Web/BaseParam.php new file mode 100644 index 0000000..d4827b1 --- /dev/null +++ b/app/Http/Controllers/Web/BaseParam.php @@ -0,0 +1,238 @@ +rules(), $name, null); + if (!$rule) { + throw new Exception('存在しないパラメータ ' . $name); + } + + $this->param[$name] = $value; + } + + public function __get($name) + { + return data_get($this->param, Str::snake($name), null); + } + + public function toArray(bool $toCamelCase = false): array + { + if ($toCamelCase === false) { + return $this->param; + } + + $ret = []; + foreach ($this->param as $key => $val) { + $camelKey = Str::camel($key); + $ret[$camelKey] = $val; + } + return $ret; + } + + public function setData(array $data) + { + + $dots = Arr::dot($data); + + $ruleRegExs = RuleAnalyzer::convertToRegEx($this->rules()); + + // logger($ruleRegExs); + + foreach ($dots as $name => $value) { + + if ($value === null) { + data_set($this->param, $name, null); + continue; + } + + $analyzer = new RuleAnalyzer($name, $ruleRegExs); + + if ($analyzer->isMathed()) { + $content = $this->getSettableData($analyzer->getType(), $value); + data_set($this->param, $name, $content); + } + } + + // logger($this->param); + } + + private function getSettableData($rule, $value) + { + if (is_string($rule)) { + if ($rule === self::STR) { + return strval($value); + } + if ($rule === self::NUMERIC) { + return intval($value); + } + if ($rule === self::BOOLEAN_) { + return (bool) $value; + } + if ($rule === self::DATE) { + if (is_string($value)) { + return DateUtil::parse($value); + } else { + return null; + } + } + if ($rule === self::IMAGE || $rule === self::FILE) { + return $value; + } + if ($rule === self::ARRAY) { + return $value; + } + } elseif ($rule instanceof Enum) { + // リフレクションを使ってEnumの型を取得する + $ref = new ReflectionClass((get_class($rule))); + $type = $ref->getProperty('type'); + $type->setAccessible(true); + $enum = $type->getValue($rule); + try { + return $enum::tryFrom($value); + } catch (Exception $e) { + logs()->error('Enum パース失敗', ['rule' => $rule, 'value' => $value, 'exception' => $e->getMessage()]); + throw $e; + } + } + + + throw new Exception(sprintf("不正な変換 ",)); + } + + /** + * 排他チェック + * + * @param Carbon|null $timestamp + * @return boolean + */ + public function checkTimestamp(Carbon|null $timestamp): bool + { + if ($timestamp === null) return true; + + $param = $this->__get(self::PARAM_NAME_TIMESTAMP); + if ($param === null || !$param instanceof Carbon) { + logger("無効なタイムスタンプ確認"); + logger($param); + return false; + } + + return $param->eq($timestamp); + } + + private function isNullable(array|bool $condition, bool $nullable): bool + { + if (is_array($condition)) { + return $nullable; + } else { + return $condition; + } + } + + protected function str(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + self::STR + ], is_array($condition) ? $condition : []); + } + protected function numeric(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + self::NUMERIC + ], is_array($condition) ? $condition : []); + } + protected function boolean(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + self::BOOLEAN_ + ], is_array($condition) ? $condition : []); + } + protected function array(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + self::ARRAY + ], is_array($condition) ? $condition : []); + } + protected function date(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + self::DATE + ], is_array($condition) ? $condition : []); + } + protected function enum(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + ], is_array($condition) ? $condition : []); + } + protected function image(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + self::IMAGE + ], is_array($condition) ? $condition : []); + } + protected function images(string $name, $nullable = false): array + { + $need = $nullable ? self::NULLABLE : self::REQUIRED; + return [ + $name => [$need, self::ARRAY], + sprintf("%s.*", $name) => [$need, self::IMAGE] + ]; + } + protected function file(array|bool $condition = [], $nullable = false): array + { + return array_merge([ + $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, + self::FILE + ], is_array($condition) ? $condition : []); + } + + protected function sortableRules() + { + return [ + BaseRepository::CONDITION_SORT_TARGET => $this->str(true), + BaseRepository::CONDITION_SORT_ORDER => $this->str(true), + BaseRepository::CONDITION_LIMIT => $this->numeric(true), + ]; + } + + protected function timestamp(bool $nullable = false) + { + return [self::PARAM_NAME_TIMESTAMP => $this->date($nullable)]; + } +} diff --git a/app/Http/Controllers/Web/IParam.php b/app/Http/Controllers/Web/IParam.php new file mode 100644 index 0000000..8cc7197 --- /dev/null +++ b/app/Http/Controllers/Web/IParam.php @@ -0,0 +1,10 @@ + $ruleList) { + $pattern = '/^' . Str::replace('*', '\d+', Str::replace('.', '\.', $name)) . '$/'; + $ret[$pattern] = $ruleList; + } + return $ret; + } + + private string $path; + private string $pattern; + + private int|null $arrayIndex = null; + + private bool $mathed = false; + + private array $rules; + + public function __construct(string $path, array &$rules) + { + $this->path = $path; + + // パターンマッチング + foreach ($rules as $pattern => $ruleList) { + if (preg_match($pattern, $path, $matcheds)) { + $this->pattern = $pattern; + $this->rules = $ruleList; + $this->mathed = true; + break; + } + } + + if (!$this->mathed) { + return; + } + + // 配列インデックスの取得 + $this->arrayIndex = $this->getArrayIndexFromPath($path); + } + + public function isMathed() + { + return $this->mathed; + } + + public function getRules() + { + return $this->rules; + } + + public function getType() + { + return $this->rules[1]; + } + + public function isArrayMember() + { + return $this->arrayIndex !== null; + } + + public function getArrayIndex() + { + return $this->arrayIndex; + } + + private function getArrayIndexFromPath(string $path) + { + + preg_match('/^.+\.(\d+)\.[0-9A-Za-z_]+$/', $path, $matches); + if (count($matches) === 0) { + return null; + } + + return intval($matches[1]); + } + + public function getArrayName() + { + $list = explode('.*.', $this->pattern); + array_pop($list); + return implode('.*.'); + } +} diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php new file mode 100644 index 0000000..92e2e1f --- /dev/null +++ b/app/Repositories/BaseRepository.php @@ -0,0 +1,67 @@ +orderBy($target); + } + if ($order === self::ORDER_DESC) { + $query->orderByDesc($target); + } + } + + protected static function limit(Builder $query, array $condition, int $default = self::MAX_LIMIT) + { + $limit = data_get($condition, self::CONDITION_LIMIT, $default) ?? $default; + + $limit = min($limit, self::MAX_LIMIT); + + $query->limit($limit); + } + + protected static function where(Builder $query, array $condition, string $conditionKey, string|null $columnName = null): bool + { + + $ret = data_get($condition, $conditionKey); + if ($ret !== null) { + $query->where($columnName ?? $conditionKey, $ret); + return true; + } else { + return false; + } + } + protected static function whereIn(Builder $query, array $condition, string $conditionKey, string|null $columnName = null): bool + { + + $ret = data_get($condition, $conditionKey); + if ($ret !== null && is_array($ret)) { + $query->whereIn($columnName ?? $conditionKey, $ret); + return true; + } else { + return false; + } + } +} diff --git a/app/Repositories/BaseRepositoryData.php b/app/Repositories/BaseRepositoryData.php new file mode 100644 index 0000000..bd76391 --- /dev/null +++ b/app/Repositories/BaseRepositoryData.php @@ -0,0 +1,29 @@ + $val) { + $this->$key = $val; + } + } + + /** + * @param Collection $list + * @return Collection + */ + static public function makeList(Collection $list) + { + $ret = collect(); + foreach ($list as $data) { + $ret->push(new static($data)); + } + return $ret; + } +} diff --git a/app/Util/DBUtil.php b/app/Util/DBUtil.php new file mode 100644 index 0000000..22f676e --- /dev/null +++ b/app/Util/DBUtil.php @@ -0,0 +1,64 @@ +isBeginning) { + $this->rollBack(); + } + } + + public function beginTransaction(): void + { + if ($this->isBeginning) { + throw new LogicException("2重トランザクション開始検知"); + } + + DB::beginTransaction(); + $this->isBeginning = true; + } + + public function commit(): void + { + if (!$this->isBeginning) { + throw new LogicException("無効なコミット検知"); + } + DB::commit(); + $this->isBeginning = false; + } + + public function rollBack(): void + { + if (!$this->isBeginning) { + throw new LogicException("無効なロールバック検知"); + } + + DB::rollBack(); + $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); + foreach ($trace as $line) { + logger(sprintf( + "Rollback from File:%s Line:%d", + data_get($line, "file", ""), + data_get($line, "line", 0) + )); + } + logs()->warning("ロールバック検知"); + $this->isBeginning = false; + } + + public function isBeginning(): bool + { + return $this->isBeginning; + } +} diff --git a/app/Util/DateUtil.php b/app/Util/DateUtil.php new file mode 100644 index 0000000..92e0b15 --- /dev/null +++ b/app/Util/DateUtil.php @@ -0,0 +1,41 @@ +environment('local')) { + return new Carbon(); + } + + $nowStr = self::getConfig(); + + if ($nowStr !== null && $nowStr !== '') { + $date = new Carbon($nowStr); + if ($date->isValid()) { + return new Carbon(); + return $date; + } + } + return new Carbon(); + } + + public static function parse(string $source): Carbon|null + { + $date = Carbon::parse($source); + if ($date->isValid()) { + return $date->timezone(config('app.timezone')); + } + return null; + } + + + private static function getConfig() + { + return config('date.now', null); + } +} diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php new file mode 100644 index 0000000..db9dd60 --- /dev/null +++ b/resources/views/index.blade.php @@ -0,0 +1,952 @@ + + + + + + + + Laravel + + + + + + + + + + + + + + \ No newline at end of file