Remove all static model methods. (#52)
Replace all static model methods. Support dependency injection for all models. Clean up things that used the static model methods. Decouple ConfigModel and CssModel. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/52 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
		
							parent
							
								
									dc4f60ce2e
								
							
						
					
					
						commit
						832b7b95fa
					
				| @ -72,8 +72,8 @@ global $app; | ||||
| 
 | ||||
| $app = [ | ||||
|     'db' => $db, | ||||
|     'config' => (new ConfigModel($db))->loadFromDatabase(), | ||||
|     'user' => (new UserModel($db))->loadFromDatabase(), | ||||
|     'config' => (new ConfigModel($db))->get(), | ||||
|     'user' => (new UserModel($db))->get(), | ||||
| ]; | ||||
| 
 | ||||
| // Start a session and generate a CSRF Token
 | ||||
|  | ||||
| @ -13,6 +13,16 @@ class Controller { | ||||
|             throw new RuntimeException("Template not found: $childTemplatePath"); | ||||
|         } | ||||
| 
 | ||||
|         // Add custom CSS filename if needed
 | ||||
|         global $app; | ||||
|         if ($app['config']->cssId) { | ||||
|             $cssModel = new CssModel($app['db']); | ||||
|             $cssFile = $cssModel->getById($app['config']->cssId); | ||||
|             $vars['customCssFilename'] = $cssFile['filename'] ?? null; | ||||
|         } else { | ||||
|             $vars['customCssFilename'] = null; | ||||
|         } | ||||
| 
 | ||||
|         // always check for flash messages and add them if they exist
 | ||||
|         if (Session::hasFlashMessages()){ | ||||
|             $flashMessages = Session::getFlashMessages(); | ||||
|  | ||||
| @ -3,8 +3,8 @@ | ||||
| class CssController extends Controller { | ||||
|     public function index() { | ||||
|         global $app; | ||||
|          | ||||
|         $customCss = CssModel::load(); | ||||
|         $cssModel = new CssModel($app['db']); | ||||
|         $customCss = $cssModel->getAll(); | ||||
| 
 | ||||
|         $vars = [ | ||||
|             'user' => $app['user'], | ||||
| @ -16,7 +16,8 @@ class CssController extends Controller { | ||||
|     } | ||||
| 
 | ||||
|     public function serveCustomCss(string $baseFilename){ | ||||
|         $cssModel = new CssModel(); | ||||
|         global $app; | ||||
|         $cssModel = new CssModel($app['db']); | ||||
|         $filename = "$baseFilename.css"; | ||||
| 
 | ||||
|         $cssRow = $cssModel->getByFilename($filename); | ||||
| @ -77,7 +78,7 @@ class CssController extends Controller { | ||||
| 
 | ||||
|         // Get the data for the selected CSS file
 | ||||
|         $cssId = $_POST['selectCssFile']; | ||||
|         $cssModel = new CssModel(); | ||||
|         $cssModel = new CssModel($app['db']); | ||||
|         $cssRow = $cssModel->getById($cssId); | ||||
| 
 | ||||
|         // exit if the requested file isn't in the database
 | ||||
| @ -182,7 +183,8 @@ class CssController extends Controller { | ||||
|             } | ||||
| 
 | ||||
|             // Add upload to database
 | ||||
|             $cssModel = new CssModel(); | ||||
|             global $app; | ||||
|             $cssModel = new CssModel($app['db']); | ||||
|             $cssModel->save($safeFilename, $description); | ||||
| 
 | ||||
|             // Set success flash message
 | ||||
|  | ||||
| @ -3,8 +3,9 @@ | ||||
|         // Shows the custom emoji management page
 | ||||
|         public function index(){ | ||||
|             global $app; | ||||
|              | ||||
|             $emojiList = EmojiModel::loadAll(); | ||||
| 
 | ||||
|             $emojiModel = new EmojiModel($app['db']); | ||||
|             $emojiList = $emojiModel->getAll(); | ||||
| 
 | ||||
|             $vars = [ | ||||
|                 'config' => $app['config'], | ||||
| @ -34,14 +35,14 @@ | ||||
|             exit; | ||||
|         } | ||||
| 
 | ||||
|         public function handleAdd(string $emoji, ?string $description=null): void { | ||||
|         private function isValidEmoji(string $emoji){ | ||||
|             // Validate 1 visible character in the emoji
 | ||||
|             if (extension_loaded('mbstring')) { | ||||
|                 // TODO - log a warning if mbstring isn't loaded
 | ||||
|                 $charCount = mb_strlen($emoji, 'UTF-8'); | ||||
|                 if ($charCount !== 1) { | ||||
|                     // TODO - handle error
 | ||||
|                     return; | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| @ -50,25 +51,40 @@ | ||||
| 
 | ||||
|             if (!preg_match($emojiPattern, $emoji)) { | ||||
|                 // TODO - handle error
 | ||||
|                 return; | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             // emojis should have more bytes than characters
 | ||||
|             $byteCount = strlen($emoji); | ||||
|             if ($byteCount <= 1) { | ||||
|                 // TODO - handle error
 | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         public function handleAdd(string $emoji, ?string $description=null): void { | ||||
|             global $app; | ||||
| 
 | ||||
|             if (!$this->isValidEmoji($emoji)){ | ||||
|                 // TODO - handle
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // It looks like an emoji. Let's add it.
 | ||||
|             EmojiModel::add($emoji, $description); | ||||
|             $emojiModel = new EmojiModel($app['db']); | ||||
|             $emojiList = $emojiModel->add($emoji, $description); | ||||
|         } | ||||
| 
 | ||||
|         public function handleDelete(): void { | ||||
|             global $app; | ||||
| 
 | ||||
|             $ids = $_POST['delete_emoji_ids']; | ||||
| 
 | ||||
|             if (!empty($ids)) { | ||||
|                 EmojiModel::delete($ids); | ||||
|                 $emojiModel = new EmojiModel($app['db']); | ||||
|                 $emojiModel->delete($ids); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @ -2,7 +2,7 @@ | ||||
|     class MoodController extends Controller { | ||||
|         public function index(){ | ||||
|             global $app; | ||||
|              | ||||
| 
 | ||||
|             $view = new MoodView(); | ||||
| 
 | ||||
|             $moodPicker = $view->render_mood_picker(self::getEmojisWithLabels(), $app['user']->mood); | ||||
| @ -17,7 +17,7 @@ | ||||
| 
 | ||||
|         public function handlePost(){ | ||||
|             global $app; | ||||
|              | ||||
| 
 | ||||
|             if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||
|                 switch ($_POST['action']){ | ||||
|                 case 'set': | ||||
| @ -39,7 +39,10 @@ | ||||
|         } | ||||
| 
 | ||||
|         private static function getEmojisWithLabels(): array { | ||||
|             $customEmoji = EmojiModel::loadAll(); | ||||
|             global $app; | ||||
| 
 | ||||
|             $emojiModel = new EmojiModel($app['db']); | ||||
|             $customEmoji = $emojiModel->getAll(); | ||||
| 
 | ||||
|             if (!empty($customEmoji)){ | ||||
|                 $custom = []; | ||||
|  | ||||
| @ -13,15 +13,8 @@ class ConfigModel { | ||||
| 
 | ||||
|     public function __construct(private PDO $db) {} | ||||
| 
 | ||||
|     // load config from sqlite database (backward compatibility)
 | ||||
|     public static function load(): self { | ||||
|         global $db; | ||||
|         $instance = new self($db); | ||||
|         return $instance->loadFromDatabase(); | ||||
|     } | ||||
|      | ||||
|     // Instance method that uses injected database
 | ||||
|     public function loadFromDatabase(): self { | ||||
|     public function get(): self { | ||||
|         $init = require APP_ROOT . '/config/init.php'; | ||||
|         $c = new self($this->db); | ||||
|         $c->baseUrl = ($c->baseUrl === '') ? $init['base_url'] : $c->baseUrl; | ||||
| @ -53,18 +46,6 @@ class ConfigModel { | ||||
|         return $c; | ||||
|     } | ||||
| 
 | ||||
|     public function customCssFilename() { | ||||
|         if (empty($this->cssId)) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         // Fetch filename from css table using cssId
 | ||||
|         $cssModel = new CssModel(); | ||||
|         $cssRecord = $cssModel->getById($this->cssId); | ||||
| 
 | ||||
|         return $cssRecord ? $cssRecord['filename'] : null; | ||||
|     } | ||||
| 
 | ||||
|     public function save(): self { | ||||
|         $settingsCount = (int) $this->db->query("SELECT COUNT(*) FROM settings")->fetchColumn(); | ||||
| 
 | ||||
| @ -104,6 +85,6 @@ class ConfigModel { | ||||
|                         $this->logLevel | ||||
|                     ]); | ||||
| 
 | ||||
|         return $this->loadFromDatabase(); | ||||
|         return $this->get(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,44 +1,41 @@ | ||||
| <?php | ||||
| class CssModel { | ||||
|     public static function load(): Array { | ||||
|         global $db; | ||||
|         $stmt = $db->prepare("SELECT id, filename, description FROM css ORDER BY filename"); | ||||
|     public function __construct(private PDO $db) {} | ||||
| 
 | ||||
|     public function getAll(): Array { | ||||
|         $stmt = $this->db->prepare("SELECT id, filename, description FROM css ORDER BY filename"); | ||||
|         $stmt->execute(); | ||||
| 
 | ||||
|         return $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function getById(int $id): Array{ | ||||
|        global $db; | ||||
|        $stmt = $db->prepare("SELECT id, filename, description FROM css WHERE id=?"); | ||||
|        $stmt = $this->db->prepare("SELECT id, filename, description FROM css WHERE id=?"); | ||||
|        $stmt->execute([$id]); | ||||
|        return $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|     } | ||||
| 
 | ||||
|     public function getByFilename(string $filename): Array{ | ||||
|        global $db; | ||||
|        $stmt = $db->prepare("SELECT id, filename, description FROM css WHERE filename=?"); | ||||
|        $stmt = $this->db->prepare("SELECT id, filename, description FROM css WHERE filename=?"); | ||||
|        $stmt->execute([$filename]); | ||||
|        return $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|     } | ||||
| 
 | ||||
|     public function delete(int $id): bool{ | ||||
|         global $db; | ||||
|         $stmt = $db->prepare("DELETE FROM css WHERE id=?"); | ||||
|         $stmt = $this->db->prepare("DELETE FROM css WHERE id=?"); | ||||
|         return $stmt->execute([$id]); | ||||
|     } | ||||
| 
 | ||||
|     public function save(string $filename, ?string $description = null): void { | ||||
|         global $db; | ||||
| 
 | ||||
|         $stmt = $db->prepare("SELECT COUNT(id) FROM css WHERE filename = ?"); | ||||
|         $stmt = $this->db->prepare("SELECT COUNT(id) FROM css WHERE filename = ?"); | ||||
|         $stmt->execute([$filename]); | ||||
|         $fileExists = $stmt->fetchColumn(); | ||||
| 
 | ||||
|         if ($fileExists) { | ||||
|             $stmt = $db->prepare("UPDATE css SET description = ? WHERE filename = ?"); | ||||
|             $stmt = $this->db->prepare("UPDATE css SET description = ? WHERE filename = ?"); | ||||
|         } else { | ||||
|             $stmt = $db->prepare("INSERT INTO css (filename, description) VALUES (?, ?)"); | ||||
|             $stmt = $this->db->prepare("INSERT INTO css (filename, description) VALUES (?, ?)"); | ||||
|         } | ||||
| 
 | ||||
|         $stmt->execute([$filename, $description]); | ||||
|  | ||||
| @ -1,29 +1,25 @@ | ||||
| <?php | ||||
| // welp this model is overkill
 | ||||
| class EmojiModel{ | ||||
|     public function __construct(private PDO $db) {} | ||||
| 
 | ||||
|     // This isn't memory-efficient,
 | ||||
|     // but I think it'll be fine on this app's scales.
 | ||||
|     public static function loadAll(): array { | ||||
|         global $db; | ||||
| 
 | ||||
|         $stmt = $db->query("SELECT id, emoji, description FROM emoji"); | ||||
|     public function getAll(): array { | ||||
|         $stmt = $this->db->query("SELECT id, emoji, description FROM emoji"); | ||||
|         return $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|     } | ||||
| 
 | ||||
|     // I'm not going to support editing emoji.
 | ||||
|     // It'll just be a delete/readd
 | ||||
|     public static function add(string $emoji, ?string $description): void{ | ||||
|         global $db; | ||||
| 
 | ||||
|         $stmt = $db->prepare("INSERT INTO emoji (emoji, description) VALUES (?, ?)"); | ||||
|     public function add(string $emoji, ?string $description): void{ | ||||
|         $stmt = $this->db->prepare("INSERT INTO emoji (emoji, description) VALUES (?, ?)"); | ||||
|         $stmt->execute([$emoji, $description]); | ||||
|     } | ||||
| 
 | ||||
|     public static function delete(array $idsToDelete): void{ | ||||
|         global $db; | ||||
| 
 | ||||
|     public function delete(array $idsToDelete): void{ | ||||
|         $placeholders = rtrim(str_repeat('?,', count($idsToDelete)), ','); | ||||
|         $stmt = $db->prepare("DELETE FROM emoji WHERE id IN ($placeholders)"); | ||||
|         $stmt = $this->db->prepare("DELETE FROM emoji WHERE id IN ($placeholders)"); | ||||
|         $stmt->execute($idsToDelete); | ||||
|     } | ||||
| } | ||||
| @ -8,15 +8,7 @@ class UserModel { | ||||
| 
 | ||||
|     public function __construct(private PDO $db) {} | ||||
| 
 | ||||
|     // load user settings from sqlite database (backward compatibility)
 | ||||
|     public static function load(): self { | ||||
|         global $db; | ||||
|         $instance = new self($db); | ||||
|         return $instance->loadFromDatabase(); | ||||
|     } | ||||
|      | ||||
|     // Instance method that uses injected database
 | ||||
|     public function loadFromDatabase(): self { | ||||
|     public function get(): self { | ||||
|         // There's only ever one user. I'm just leaning into that.
 | ||||
|         $stmt = $this->db->query("SELECT username, display_name, website, mood FROM user WHERE id=1"); | ||||
|         $row = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
| @ -43,7 +35,7 @@ class UserModel { | ||||
| 
 | ||||
|       $stmt->execute([$this->username, $this->displayName, $this->website, $this->mood]); | ||||
| 
 | ||||
|       return $this->loadFromDatabase(); | ||||
|       return $this->get(); | ||||
|    } | ||||
| 
 | ||||
|    // Making this a separate function to avoid
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var UserModel $user */ ?>
 | ||||
| <?php /** @var string $childTemplateFile */ ?>
 | ||||
| <?php /** @var string $customCssFilename */ ?>
 | ||||
| <?php /** @var srting $flashSection */ ?>
 | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| @ -13,7 +14,7 @@ | ||||
|               href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'css/default.css')) ?>"> | ||||
| <?php if (!empty($config->cssId)): ?>
 | ||||
|         <link rel="stylesheet" | ||||
|               href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'css/custom/' . $config->customCssFilename())) ?>"> | ||||
|               href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'css/custom/' . $customCssFilename)) ?>"> | ||||
| <?php endif; ?>
 | ||||
|         <link rel="alternate" | ||||
|               type="application/rss+xml" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user