Cronライブラリ

守矢諏訪子 <suwako@076.moe>、2026-06-11 02:25
コミット 6693091c3053e2f65a6879be9c055ef03565dff0 ・親 cb8de00 ・ファイルを見る

4個のファイル、+479 -0

変更 .gitignore
@@ -1,6 +1,7 @@
11 data/*.pem
22 data/*.txt
33 data/*.json
4+data/cron
45 data/cache
56 data/user/*.json
67 log/*.txt
追加 c.php
@@ -0,0 +1,11 @@
1+<?php
2+
3+require_once __DIR__.'/autoload.php';
4+define('ROOT', realpath(__DIR__));
5+define('CRON_ENABLED', true);
6+use Std\Lib\Cron;
7+use Std\Lib\CronTab;
8+
9+$cron = new Cron();
10+$ct = new CronTab(true, '0', '2', '15', '6', '*', 'apfederate');
11+$cron->add($ct);
@@ -0,0 +1,290 @@
1+<?php
2+/*************************************************************
3+# 076 License
4+
5+Copyright (c) テクニカル諏訪子
6+
7+Permission is hereby granted to any person obtaining a copy of the software
8+Little Beast (the "Software") to use, modify, merge, copy, publish, distribute,
9+sublicense, and/or sell copies of the Software, subject to the following conditions:
10+
11+ 1. **Origin Attribution**:
12+ - You must not misrepresent the origin of the Software; you must not claim
13+ you created the original Software.
14+ - If the Software is used in a product, you must either:
15+ a. Provide clear attribution in the product's documentation, user interface,
16+ or other visible areas, **OR**
17+ b. Pay the original developers a fee they specify in writing.
18+ 2. **Usage Restriction**:
19+ - The Software, or any derivative works, dependencies, or libraries
20+ incorporating it, must not be used for censorship or to suppress freedom of
21+ speech, expression, or creativity. Prohibited uses include, but are not
22+ limited to:
23+ - Censorship of so-called "hate speech", visuals, non-mainstream opinions,
24+ ideas, or objective reality.
25+ - Tools or systems designed to restrict access to information or
26+ artistic works.
27+ 3. **Notice Preservation**:
28+ - This license and the above copyright notice must remain intact in all copies
29+ of the source code.
30+
31+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
32+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
33+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
34+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
35+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
36+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37+ */
38+namespace Std\Lib;
39+
40+use Result;
41+
42+class CronTab {
43+ private int $id;
44+ public bool $isActive;
45+ public string $minute;
46+ public string $hour;
47+ public string $dayOfMonth;
48+ public string $month;
49+ public string $dayOfWeek;
50+ public string $command;
51+
52+ public function __construct(
53+ bool $isActive = true,
54+ string $minute = '*',
55+ string $hour = '*',
56+ string $dayOfMonth = '*',
57+ string $month = '*',
58+ string $dayOfWeek = '*',
59+ string $command = ''
60+ ) {
61+ if (!CRON_ENABLED) return;
62+ $this->isActive = $isActive;
63+ $this->minute = $minute;
64+ $this->hour = $hour;
65+ $this->dayOfMonth = $dayOfMonth;
66+ $this->month = $month;
67+ $this->dayOfWeek = $dayOfWeek;
68+ $this->command = $command;
69+ }
70+
71+ public function getId(): int {
72+ if (!CRON_ENABLED) return 0;
73+ return $this->id;
74+ }
75+
76+ public function setId(int $id): void {
77+ if (!CRON_ENABLED) return;
78+ $this->id = $id;
79+ }
80+
81+ public function toArray(): array {
82+ if (!CRON_ENABLED) return [];
83+ return [
84+ 'id' => $this->id,
85+ 'isActive' => $this->isActive,
86+ 'minute' => $this->minute,
87+ 'hour' => $this->hour,
88+ 'dayOfMonth' => $this->dayOfMonth,
89+ 'month' => $this->month,
90+ 'dayOfWeek' => $this->dayOfWeek,
91+ 'command' => $this->command,
92+ ];
93+ }
94+
95+ public static function fromArray(array $data): self {
96+ if (!CRON_ENABLED) return self;
97+ return new self(
98+ $data['id'] ?? 0,
99+ $data['isActive'] ?? true,
100+ $data['minute'] ?? '*',
101+ $data['hour'] ?? '*',
102+ $data['dayOfMonth'] ?? '*',
103+ $data['month'] ?? '*',
104+ $data['dayOfWeek'] ?? '*',
105+ $data['command'] ?? '',
106+ );
107+ }
108+}
109+
110+/**
111+ * cron
112+ */
113+class Cron {
114+ private string $dataDir;
115+ private string $dataFile;
116+ private int $lastId;
117+
118+ public function __construct() {
119+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
120+ $this->dataDir = ROOT.'/data/cron/';
121+ $this->dataFile = $this->dataDir.'cron.json';
122+
123+ if (!file_exists($this->dataDir)) {
124+ $res = mkdir($this->dataDir, 0755);
125+ assert($res, "Cronディレクトリの作成に失敗");
126+ }
127+ if (!file_exists($this->dataFile)) {
128+ $json = json_encode([], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
129+ $res = file_put_contents($this->dataFile, $json);
130+ assert($res, "Cronファイルの作成に失敗");
131+ }
132+
133+ $this->loadLastId();
134+ }
135+
136+ public function getLastId(): int {
137+ return $this->lastId;
138+ }
139+
140+ public function list(): Result {
141+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
142+ $content = file_get_contents($this->dataFile);
143+ if ($content === false) return Result::Error('Cronファイルが存在しません。');
144+
145+ $data = json_decode($content, true);
146+ if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) return Result::Error('Cronの復号化に失敗');
147+
148+ return Result::Success('', $data);
149+ }
150+
151+ public function show(int $id): Result {
152+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
153+ $listRes = $this->list();
154+ if (!$listRes->isSuccess) return $listRes;
155+
156+ $data = $listRes->data;
157+ foreach ($data as $d) {
158+ if (($d['id'] ?? 0) === $id) return Result::Success('', $d);
159+ }
160+
161+ return Result::Error("ID {$id} のCronエントリが見つかりません。");
162+ }
163+
164+ public function enable(int $id): Result {
165+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
166+ return $this->setActive($id, true);
167+ }
168+
169+ public function disable(int $id): Result {
170+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
171+ return $this->setActive($id, false);
172+ }
173+
174+ public function toggle(int $id): Result {
175+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
176+ $showRes = $this->show($id);
177+ if (!$showRes->isSuccess) return $showRes;
178+ $data = $showRes->data;
179+ $active = !($data['isActive'] ?? true);
180+ return $this->setActive($id, $active);
181+ }
182+
183+ public function add(CronTab $ct): Result {
184+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
185+ $listRes = $this->list();
186+ if (!$listRes->isSuccess) return $listRes;
187+
188+ $data = $listRes->data;
189+ $this->lastId++;
190+ $ct->setId($this->lastId);
191+ $data[] = $ct->toArray();
192+
193+ if (!$this->save($data)) return Result::Error('Cronデータの保存に失敗');
194+ return Result::Success("Cronエントリ #{$this->lastId} を追加しました。", $ct->toArray());
195+ }
196+
197+ public function edit(int $id, CronTab $ct): Result {
198+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
199+ $listRes = $this->list();
200+ if (!$listRes->isSuccess) return $listRes;
201+
202+ $data = $listRes->data;
203+ $found = false;
204+
205+ foreach ($data as &$d) {
206+ if (($d['id'] ?? 0) === $id) {
207+ $ct->setId($id);
208+ $entry = $ct->toArray();
209+ $found = true;
210+ break;
211+ }
212+ }
213+
214+ if (!$found) return Result::Error("ID {$id} のCronエントリが見つかりません。");
215+ if (!$this->save($data)) return Result::Error('Cronデータの保存に失敗しました。');
216+ return Result::Success("ID {$id} のCronエントリを更新しました。");
217+ }
218+
219+ public function delete(int $id): Result {
220+ if (!CRON_ENABLED) return Result::Error('エラー:Cronシステムは無効です。');
221+ $listRes = $this->list();
222+ if (!$listRes->isSuccess) return $listRes;
223+
224+ $data = $listRes->data;
225+ $nd = [];
226+ $found = false;
227+
228+ foreach ($data as $d) {
229+ if (($d['id'] ?? 0) === $id) {
230+ $found = true;
231+ continue;
232+ }
233+ $nd = $d;
234+ }
235+
236+ if (!$found) return Result::Error("ID {$id} のCronエントリが見つかりません。");
237+ if (!$this->save($nd)) return Result::Error('Cronデータの保存に失敗');
238+
239+ if (empty($nd)) {
240+ $this->lastId = 0;
241+ } else {
242+ $ids = array_column($nd, 'id');
243+ $this->lastId = (int)max($ids);
244+ }
245+
246+ return Result::Success("ID {$id} のCronエントリを削除しました。");
247+ }
248+
249+ ///////////////
250+
251+ private function loadLastId(): void {
252+ $content = file_get_contents($this->dataFile);
253+ if ($content === false) return;
254+
255+ $data = json_decode($content, true);
256+ if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) return;
257+
258+ if (!empty($data)) {
259+ $ids = array_column($data, 'id');
260+ $this->lastId = (int)max($ids);
261+ } else {
262+ $this->lastId = 0;
263+ }
264+ }
265+
266+ private function save(array $data): bool {
267+ $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
268+ return file_put_contents($this->dataFile, $json) !== false;
269+ }
270+
271+ private function setActive(int $id, bool $active): Result {
272+ $listRes = $this->list();
273+ if (!$listRes->isSuccess) return $listRes;
274+
275+ $data = $listRes->data;
276+ $found = false;
277+
278+ foreach ($data as $d) {
279+ if (($d['id'] ?? 0) === $id) {
280+ $d['isActive'] = $active;
281+ $found = true;
282+ break;
283+ }
284+ }
285+
286+ if (!$found) return Result::Error("ID {$id} のCronエントリが見つかりません。");
287+ if (!$this->save($data)) return Result::Error('Cronデータの保存に失敗しました。');
288+ return Result::Success("ID {$id} を ".($active ? '有効' : '無効').'にしました。');
289+ }
290+}
@@ -0,0 +1,177 @@
1+<?php
2+/*************************************************************
3+# 076 License
4+
5+Copyright (c) テクニカル諏訪子
6+
7+Permission is hereby granted to any person obtaining a copy of the software
8+Little Beast (the "Software") to use, modify, merge, copy, publish, distribute,
9+sublicense, and/or sell copies of the Software, subject to the following conditions:
10+
11+ 1. **Origin Attribution**:
12+ - You must not misrepresent the origin of the Software; you must not claim
13+ you created the original Software.
14+ - If the Software is used in a product, you must either:
15+ a. Provide clear attribution in the product's documentation, user interface,
16+ or other visible areas, **OR**
17+ b. Pay the original developers a fee they specify in writing.
18+ 2. **Usage Restriction**:
19+ - The Software, or any derivative works, dependencies, or libraries
20+ incorporating it, must not be used for censorship or to suppress freedom of
21+ speech, expression, or creativity. Prohibited uses include, but are not
22+ limited to:
23+ - Censorship of so-called "hate speech", visuals, non-mainstream opinions,
24+ ideas, or objective reality.
25+ - Tools or systems designed to restrict access to information or
26+ artistic works.
27+ 3. **Notice Preservation**:
28+ - This license and the above copyright notice must remain intact in all copies
29+ of the source code.
30+
31+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
32+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
33+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
34+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
35+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
36+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37+ */
38+namespace Std\Test;
39+
40+require_once __DIR__.'/../../../autoload.php';
41+
42+use Std\Lib\Tester;
43+use Std\Lib\Cron;
44+use Std\Lib\CronTab;
45+
46+if (CRON_ENABLED) {
47+ $test = new Tester([
48+ 'colorOutput' => true,
49+ 'verboseOutput' => true
50+ ]);
51+
52+ $test->describe('Cronの基本的な機能性', function($test): void {
53+ $test->it('ジョブを追加出来るはず', function($test): void {
54+ $cron = new Cron();
55+ $test->assertNotNull($cron);
56+ $ct = new CronTab(true, '0', '2', '15', '6', '*', 'apfederate');
57+ $test->assertNotNull($ct);
58+
59+ $actual = $cron->add($ct)->data;
60+
61+ $expect = [
62+ 'id' => 1,
63+ 'isActive' => true,
64+ 'minute' => '0',
65+ 'hour' => '2',
66+ 'dayOfMonth' => '15',
67+ 'month' => '6',
68+ 'dayOfWeek' => '*',
69+ 'command' => 'apfederate',
70+ ];
71+
72+ $test->assertEquals($expect, $actual);
73+ });
74+
75+ $test->it('ジョブを表示出来るはず', function($test): void {
76+ $cron = new Cron();
77+ $test->assertNotNull($cron);
78+
79+ $actual = $cron->show($cron->getLastId())->data;
80+
81+ $expect = [
82+ 'id' => 1,
83+ 'isActive' => true,
84+ 'minute' => '0',
85+ 'hour' => '2',
86+ 'dayOfMonth' => '15',
87+ 'month' => '6',
88+ 'dayOfWeek' => '*',
89+ 'command' => 'apfederate',
90+ ];
91+
92+ $test->assertEquals($expect, $actual);
93+ });
94+
95+ $test->it('ジョブを変更出来るはず', function($test): void {
96+ $cron = new Cron();
97+ $test->assertNotNull($cron);
98+ $ct = new CronTab(true, '0', '7', '15', '6', '*', 'apfederate');
99+ $test->assertNotNull($ct);
100+
101+ $actual = $cron->edit($cron->getLastId(), $ct)->data;
102+
103+ $expect = [
104+ 'id' => 1,
105+ 'isActive' => true,
106+ 'minute' => '0',
107+ 'hour' => '7',
108+ 'dayOfMonth' => '15',
109+ 'month' => '6',
110+ 'dayOfWeek' => '*',
111+ 'command' => 'apfederate',
112+ ];
113+
114+ $test->assertEquals($expect, $actual);
115+ });
116+
117+ $test->it('ジョブを有無出来るはず', function($test): void {
118+ $cron = new Cron();
119+ $test->assertNotNull($cron);
120+
121+ $actual = $cron->toggle($cron->getLastId())->data;
122+
123+ $expect = [
124+ 'id' => 1,
125+ 'isActive' => false,
126+ 'minute' => '0',
127+ 'hour' => '7',
128+ 'dayOfMonth' => '15',
129+ 'month' => '6',
130+ 'dayOfWeek' => '*',
131+ 'command' => 'apfederate',
132+ ];
133+
134+ $test->assertEquals($expect, $actual);
135+ });
136+
137+ $test->it('ジョブを一覧出来るはず', function($test): void {
138+ $cron = new Cron();
139+ $test->assertNotNull($cron);
140+
141+ $actual = $cron->list()->data;
142+
143+ $expect = [
144+ 'id' => 1,
145+ 'isActive' => false,
146+ 'minute' => '0',
147+ 'hour' => '7',
148+ 'dayOfMonth' => '15',
149+ 'month' => '6',
150+ 'dayOfWeek' => '*',
151+ 'command' => 'apfederate',
152+ ];
153+
154+ $test->assertEquals($expect, $actual);
155+ });
156+
157+ $test->it('ジョブを削除出来るはず', function($test): void {
158+ $cron = new Cron();
159+ $test->assertNotNull($cron);
160+
161+ $actual = $cron->delete($cron->getLastId())->data;
162+
163+ $expect = [
164+ 'id' => 1,
165+ 'isActive' => false,
166+ 'minute' => '0',
167+ 'hour' => '7',
168+ 'dayOfMonth' => '15',
169+ 'month' => '6',
170+ 'dayOfWeek' => '*',
171+ 'command' => 'apfederate',
172+ ];
173+
174+ $test->assertEquals($expect, $actual);
175+ });
176+ });
177+}