Files
f3-simple-blog/app/Models/Post.php
2026-03-30 00:00:03 +02:00

203 lines
5.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
class Post extends DB\SQL\Mapper
{
public const TITLE_MAX_LENGTH = 120;
public const EXCERPT_MAX_LENGTH = 240;
public function __construct()
{
parent::__construct(Base::instance()->get('DB'), 'posts');
}
public static function bootstrap(DB\SQL $db): void
{
if ($db->schema('posts', null, 0)) {
return;
}
$db->exec('CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
excerpt TEXT NOT NULL,
body_markdown TEXT NOT NULL,
body_html TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)');
$db->exec('CREATE INDEX idx_posts_created_at ON posts(created_at DESC)');
}
public static function blank(): array
{
return [
'title' => '',
'excerpt' => '',
'body_markdown' => '',
];
}
public function page(int $page, int $perPage): array
{
$result = $this->paginate(max(0, $page - 1), $perPage, null, ['order' => 'created_at DESC, id DESC']);
$items = array_map(fn(self $row): array => $this->summary($row->cast()), $result['subset'] ?: []);
return [
'items' => $items,
'pagination' => [
'page' => max(1, min($page, $result['count'] ?: 1)),
'pages' => max(1, (int) ($result['count'] ?: 1)),
],
];
}
public function findBySlug(string $slug): ?array
{
$this->load(['slug = ?', $slug]);
if ($this->dry()) {
return null;
}
$row = $this->cast();
$post = $this->summary($row) + ['body_html' => (string) $row['body_html']];
return $post;
}
public function findForForm(int $id): ?array
{
$this->load(['id = ?', $id]);
if ($this->dry()) {
return null;
}
return [
'id' => (int) $this->id,
'title' => (string) $this->title,
'excerpt' => (string) $this->excerpt,
'body_markdown' => (string) $this->body_markdown,
];
}
public function savePost(array $input, ?int $id = null): int
{
$payload = $this->payload($input);
$now = app_now();
if ($id === null) {
$this->reset();
$payload['slug'] = $this->uniqueSlug($payload['title']);
$payload['created_at'] = $now;
} else {
$this->load(['id = ?', $id]);
if ($this->dry()) {
throw new RuntimeException('Article introuvable.');
}
}
$payload['updated_at'] = $now;
$this->copyfrom($payload);
$this->save();
return (int) $this->id;
}
public function deleteById(int $id): void
{
$this->load(['id = ?', $id]);
if ($this->dry()) {
throw new RuntimeException('Article introuvable.');
}
$this->erase();
}
public function usesMedia(string $fileName): bool
{
return $this->count(['body_markdown LIKE ?', '%media:' . $fileName . '%']) > 0;
}
private function payload(array $input): array
{
$title = trim((string) ($input['title'] ?? ''));
$excerpt = trim((string) ($input['excerpt'] ?? ''));
$body = trim((string) ($input['body_markdown'] ?? ''));
if ($title === '') {
throw new RuntimeException('Ajoute un titre.');
}
if (mb_strlen($title) > self::TITLE_MAX_LENGTH) {
throw new RuntimeException('Le titre est trop long.');
}
if ($excerpt === '') {
throw new RuntimeException('Ajoute un extrait.');
}
if (mb_strlen($excerpt) > self::EXCERPT_MAX_LENGTH) {
throw new RuntimeException('Lextrait est trop long.');
}
return [
'title' => $title,
'excerpt' => $excerpt,
'body_markdown' => $body,
'body_html' => MarkdownService::instance()->compile($body, new Media()),
];
}
private function uniqueSlug(string $title): string
{
$base = app_slug($title);
$slug = $base;
$n = 2;
while ($this->count(['slug = ?', $slug]) > 0) {
$slug = $base . '-' . $n;
$n++;
}
return $slug;
}
private function summary(array $row): array
{
$thumbnail = $this->firstImage((string) ($row['body_html'] ?? ''));
return [
'id' => (int) $row['id'],
'title' => (string) $row['title'],
'slug' => (string) $row['slug'],
'excerpt' => (string) $row['excerpt'],
'thumbnail_url' => $thumbnail['url'],
'thumbnail_alt' => $thumbnail['alt'],
'created_at' => (string) $row['created_at'],
'updated_at' => (string) $row['updated_at'],
];
}
private function firstImage(string $html): array
{
if ($html === '') {
return ['url' => '', 'alt' => ''];
}
if (!preg_match('~(<img\s[^>]*src="([^"]+)"[^>]*>)~i', $html, $match)) {
return ['url' => '', 'alt' => ''];
}
$tag = $match[1];
$alt = '';
if (preg_match('~alt="([^"]*)"~i', $tag, $altMatch)) {
$alt = html_entity_decode($altMatch[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
return [
'url' => html_entity_decode($match[2], ENT_QUOTES | ENT_HTML5, 'UTF-8'),
'alt' => $alt,
];
}
}