Skip to content

Instantly share code, notes, and snippets.

@mt-xing

mt-xing/blog.php Secret

Created Jan 1, 2021
Embed
What would you like to do?
Blog v3
<?php
/**
* A single blog post
*/
class Post {
public function __construct(
public int $id,
public DateTimeImmutable $date,
public string $title,
public string $text
) {
}
/**
* Truncates the post body to the specified number of words
*
* @param int $words Default 100
*
* @return string
*/
public function truncate(int $words = 100): string {
$cleanText = trim(preg_replace('/\s\s+/', ' ', strip_tags($this->text)));
preg_match("/(?:\w+(?:\W+|$)){0,$words}/", $cleanText, $matches);
return rtrim($matches[0]) . '&hellip;';
}
/**
* Return the post date as a computer-readable string
* @return string
*/
public function computerDate(): string {
return $this->date->format('Y-m-d');
}
/**
* Return the post date as a fancy English string
* @return string
*/
public function longDate(): string {
return $this->date->format('F j<\s\u\p>S</\s\u\p>, Y');
}
/**
* Return the fancy English date wrapped in a semantic HTML time tag
* @return string
*/
public function htmlDate(): string {
return '<time datetime="' . $this->computerDate() . '">' . $this->longDate() . '</time>';
}
}
/**
* The blog, with all its entries.
*/
class Blog {
/** @var Blog|null The singleton instance, or null if uninstantiated */
private static ?Blog $instance = null;
private mysqli $connection;
/**
* Construct blog and open database connection
*/
private function __construct() {
$hostname = 'localhost';
$database = '<TABLE NAME HERE>';
$port = 3306;
$username = '<USERNAME HERE>';
$password = '<PASSWORD HERE>';
if (!self::isProd()) {
$username = '<DEV USERNAME HERE>';
$password = '<DEV PASSWORD HERE>';
}
$this->connection =
mysqli_connect($hostname, $username, $password, $database, $port) or die('Error connecting to database :(');
}
/**
* Returns true iff we are in production
*
* @return bool
* @noinspection PhpPureAttributeCanBeAddedInspection
*/
private static function isProd(): bool {
return strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN';
}
/**
* Turn a MySQL result into a blog post object
*
* @param array $res
*
* @return Post
*/
private static function mapResultToPost(array $res): Post {
try {
return new Post(
$res['id'],
new DateTimeImmutable($res['date_posted'], new DateTimeZone('UTC')),
$res['title'],
$res['post']
);
} catch (Exception $e) {
return new Post(
$res['id'],
(new DateTimeImmutable())->setTimestamp(0),
$res['title'],
$res['post']
);
}
}
/**
* Get the singleton instance
*
* @return Blog
*/
public static function get(): Blog {
if (self::$instance === null) {
self::$instance = new Blog();
}
return self::$instance;
}
/**
* Get an array of blog posts, sorted by date, descending
*
* @param ?int $latestID The latest blog post id to include, EXCLUSIVE
* @param int $numPosts The maximum number of posts to return
*
* @return Post[] An array of blog posts
*/
public function all(?int $latestID = null, int $numPosts = 5): array {
$query = 'SELECT * FROM blog_posts WHERE id < IFNULL(?, ~0) ORDER BY date_posted DESC LIMIT ?';
$statement = $this->connection->prepare($query);
$statement->bind_param('ii', $latestID, $numPosts);
$statement->execute();
$posts = $statement->get_result();
$statement->fetch();
$statement->close();
return array_map(array($this, 'mapResultToPost'), $posts->fetch_all(MYSQLI_ASSOC));
}
/**
* Get a single blog post by id
*
* @param int $id ID of the blog post to get
*
* @return ?Post The post if successful, or null if not.
*/
public function post(int $id): ?Post {
$query = 'SELECT * FROM blog_posts WHERE id = ?';
$statement = $this->connection->prepare($query);
$statement->bind_param('i', $id);
$statement->execute();
$post = $statement->get_result();
$statement->fetch();
$statement->close();
if($post->num_rows !== 1) {
return null;
}
return self::mapResultToPost($post->fetch_assoc());
}
/**
* Get all posts from a month, descending by date
*
* @param string $month A string with the month (eg: 2021-01)
*
* @return Post[] An array of posts
*/
public function month(string $month): array {
try {
$start = (new DateTimeImmutable($month))->getTimestamp();
$end = (new DateTimeImmutable($month . ' 1 month - 1 second'))->getTimestamp();
$query = 'SELECT * FROM blog_posts WHERE date_posted BETWEEN FROM_UNIXTIME(?) AND FROM_UNIXTIME(?) ORDER BY date_posted DESC';
$statement = $this->connection->prepare($query);
$statement->bind_param('ii', $start, $end);
$statement->execute();
$posts = $statement->get_result();
$statement->fetch();
$statement->close();
return array_map(array($this, 'mapResultToPost'), $posts->fetch_all(MYSQLI_ASSOC));
} catch (Exception $e) {
return [];
}
}
/**
* Get an array of all months, in year-month format
*
* @return string[]
*/
public function listMonths(): array {
$query = 'SELECT DISTINCT (DATE_FORMAT(date_posted, \'%Y-%m\')) FROM blog_posts ORDER BY date_posted DESC';
$res = $this->connection->query($query);
return array_map(function ($row) {
return $row[0];
}, $res->fetch_all());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment