API: Database access
1Introduction
To make our API work, we'll need three database tables, one to store notes, one to store tags and one to link the two. These three tables are in addition to those used to store users and authentication keys.
2Tables
Here are the queries for creating the two tables:
-- Table containing notes
CREATE TABLE Note (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_update DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
title TINYTEXT NOT NULL,
content TEXT NOT NULL,
user_id INT UNSIGNED NOT NULL,
PRIMARY KEY (id),
INDEX date_creation (date_creation),
INDEX date_update (date_update),
FULLTEXT title (title),
FOREIGN KEY (user_id) REFERENCES User (id) ON DELETE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- Table containing tags
CREATE TABLE Tag (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
tag TINYTEXT CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
PRIMARY KEY (id),
UNIQUE INDEX tag (tag(255))
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- Link table between notes and tags
CREATE TABLE Note_Tag (
note_id INT UNSIGNED NOT NULL,
tag_id INT UNSIGNED NOT NULL,
FOREIGN KEY (note_id) REFERENCES Note (id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES Tag (id) ON DELETE CASCADE,
UNIQUE INDEX note_id_tag_id (note_id, tag_id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
3DAO
The controllers will need to access the data in the database. This will be done via a "DAO" (Data Access Object), which will be an object accessed via the dependency injection component.
Here's the code recorded in the lib/NoteDao.php file:
<?php
/**
* DAO for managing notes and tags.
* Loadable object with dependency injection component.
*/
class NoteDao implements \Temma\Base\Loadable {
/** Database access. */
private \Temma\Datasource\Sql $_db;
/**
* Constructor.
* @param \Temma\Base\Loader $loader Dependency injection components.
*/
public function __construct(\Temma\Base\Loader $loader) {
$this->_db = $loader->dataSources->db;
}
/**
* Returns the list of tags for a user.
* @param int $userId User identifier.
* @return array Associative array with tags as keys and the number of associated notes as values.
*/
public function getTags(int $userId) : array {
$sql = "SELECT tag,
COUNT(note_id) AS nbrNotes
FROM Note
INNER JOIN Note_Tag ON (Note.id = Note_Tag.note_id)
INNER JOIN Tag ON (Note_Tag.tag_id = Tag.id)
WHERE Note.user_id = " . $this->_db->quote($userId) . "
GROUP BY Tag.id
ORDER BY tag";
$tags = $this->_db->queryAll($sql, 'tag', 'nbrNotes');
return ($tags);
}
/**
* Returns a note from its identifier.
* @param int $noteId Note identifier.
* @return array Associative array.
*/
public function getNote(int $noteId) : array {
// note retrieval
$sql = "SELECT id,
date_creation AS `creation`,
date_update AS `update`,
title,
content,
user_id AS userId
FROM Note
WHERE id = " . $this->_db->quote($noteId);
$note = $this->_db->queryOne($sql);
// tag retrieval
$sql = "SELECT tag
FROM Note_Tag
INNER JOIN Tag ON (Note_Tag.tag_id = Tag.id)
WHERE Note_Tag.note_id = " . $this->_db->quote($noteId);
$note['tags'] = $this->_db->queryAll($sql, null, 'tag');
return ($note);
}
/**
* Returns a list of a user's notes, most recently modified first.
* @param int $userId User identifier.
* @return array List of associative arrays.
*/
public function getNotes(int $userId) : array {
// notes retrieval
$sql = "SELECT id,
date_creation AS `creation`,
date_update AS `update`,
title
FROM Note
WHERE user_id = " . $this->_db->quote($userId) . "
ORDER BY date_update DESC";
$notes = $this->_db->queryAll($sql, 'id');
// tag retrieval
$notes = $this->_fetchTags($notes);
return ($notes);
}
/**
* Returns a list of notes based on search criteria.
* @param int $userId User identifier.
* @param ?string $tag (optionnel) Tag to search for.
* @param ?string $title (optionnel) String to search for in title.
* @return array List of associative arrays.
*/
public function searchNotes(int $userId, ?string $tag=null, ?string $title) : array {
// search for notes
$sql = "SELECT id,
date_creation AS `creation`,
date_update AS `update`,
title
FROM ";
if ($tag)
$sql .= "Tag INNER JOIN Note ON (Tag.note_id = Note.id) ";
else
$sql .= "Note ";
$sql .= "WHERE user_id = " . $this->_db->quote($userId) . " ";
if ($tag)
$sql .= "AND tag = " . $this->_db->quote($tag);
if ($title)
$sql .= "AND title LIKE " . $this->_db->quote("%$title%");
$notes = $this->_db->queryAll($sql, 'id');
// tag retrieval
$notes = $this->_fetchTags($notes);
return ($notes);
}
/**
* Add a new note.
* @param int $userId User identifier.
* @param string $title Note title.
* @param string $content HTML content of the note.
* @param ?array $tags List of tags in the note.
* @return int New note identifier.
*/
public function create(int $userId, string $title, string $content, ?array $tags) : int {
// creation of the note
$sql = "INSERT INTO Note
SET title = " . $this->_db->quote($title) . ",
content = " . $this->_db->quote($content) . ",
user_id = " . $this->_db->quote($userId);
$this->_db->exec($sql);
$noteId = $this->_db->lastInsertId();
if (!$tags)
return ($noteId);
// add tags
$this->_addTagsToNote($noteId, $tags);
return ($noteId);
}
/**
* Modifies an existing note.
* @param int $noteId Note identifier.
* @param ?string $title (optionnel) New note title.
* @param ?string $content (optionnel) New HTML content for the note.
* @param ?array $tags (optionnel) New list of tags for the note.
*/
public function update(int $noteId, ?string $title=null, ?string $content=null, ?array $tags=null) : void {
if ($title || $content) {
$set = [];
if ($title)
$set[] = "title = " . $this->_db->quote($title);
if ($content)
$set[] = "content = " . $this->_db->quote($content);
$sql = "UPDATE Note
SET " . implode(', ', $set) . "
WHERE id = " . $this->_db->quote($noteId);
$this->_db->exec($sql);
}
if (!$tags)
return;
// removal of old tags
$sql = "DELETE FROM Note_Tag
WHERE note_id = " . $this->_db->quote($noteId);
$this->_db->exec($sql);
// add tags
$this->_addTagsToNote($noteId, $tags);
}
/**
* Deletes a note.
* @param int $noteId Note identifier.
*/
public function remove(int $noteId) : void {
$sql = "DELETE FROM Note
WHERE id = " . $this->_db->quote($noteId);
$this->_db->exec($sql);
}
/**
* Private method that enriches notes with their tags.
* @param array $notes List of notes.
* @return array The enriched list.
*/
private function _fetchTags(array $notes) : array {
$sql = "SELECT tag,
note_id AS noteId
FROM Note_Tag
INNER JOIN Tag ON (Note_Tag.tag_id = Tag.id)
WHERE Note_Tag.note_id IN " . implode(', ', array_keys($notes));
$tags = $this->_db->queryAll($sql);
foreach ($tags as $tag) {
$notes[$tag['noteId']]['tags'] ??= [];
$notes[$tag['noteId']]['tags'][] = $tag['tag'];
}
return ($notes);
}
/**
* Private method that adds tags to a note.
* @param int $noteId Note identifier.
* @param array $tags List of tags.
*/
private function _addTagsToNote(int $noteId, array $tags) : void {
// character escaping
$tags = array_map(function($t) {
return $this->_db->quote($t);
}, $tags);
// insert tags that don't yet exist
$sql = "INSERT INTO Tag (tag)
VALUES (" . implode('), (', $tags) . ")
ON DUPLICATE KEY UPDATE id = id";
$this->_db->exec($sql);
// retrieve tag identifiers
$sql = "SELECT id
FROM Tag
WHERE tag IN (" . implode(', ', $tags) . ")";
$tagIds = $this->_db->queryAll($sql);
// liens entre la note et les tags
$sql = "INSERT INTO Note_Tag (note_id, tag_id)
VALUES ('$noteId', " . implode("), ('$noteId', ", $tagIds) . ")
ON DUPLICATE KEY UPDATE note_id = note_id";
$this->_db->exec($sql);
}
}
- Line 7: Creation of the NoteDao object, which implements the \Temma\Base\Loadable interface to be used through the dependency injection component.
- Line 9: Database connection object.
- Lines 15 to 17: Constructor, used to retrieve the database connection object.
- Lines 23 to 34: Method that returns the list of tags corresponding to a user's notes.
- Lines 40 to 58: Method that returns all note data.
- Lines 64 to 77: Method that returns the list of a user's notes.
- Lines 85 to 105: Method that searches a list of notes based on criteria.
- Lines 114 to 127: Method used to add a new note to the database.
- Lines 135 to 155: Method used to update an existing note.
- Lines 160 to 164: Method used to delete a note.
- Lines 171 to 183: Private method used to enrich a list of notes with associated tags.
- Lines 189 to 209: Private method used to create tags and associate them with a note.
In a controller, this DAO is called via the dependency injection component. For example:
$note = $this->_loader->NoteDao->getNote($noteId);