├── .gitignore ├── JokeForm ├── Controller.php └── View.php ├── JokeList ├── Controller.php └── View.php ├── Model ├── JokeForm.php └── JokeList.php ├── README.md ├── config.sample.php ├── public ├── .directory └── index.php └── sample-database.sql /.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | -------------------------------------------------------------------------------- /JokeForm/Controller.php: -------------------------------------------------------------------------------- 1 | load($_GET['id']); 7 | } 8 | else { 9 | return $jokeForm; 10 | } 11 | } 12 | 13 | public function submit(\JokeSite\JokeForm $jokeForm): \JokeSite\JokeForm { 14 | return $jokeForm->save($_POST['joke']); 15 | } 16 | } -------------------------------------------------------------------------------- /JokeForm/View.php: -------------------------------------------------------------------------------- 1 | getErrors(); 6 | 7 | if ($model->isSubmitted() && empty($errors)) { 8 | //On success, redirect to list page 9 | header('location: index.php'); 10 | die; 11 | } 12 | 13 | $joke = $model->getJoke(); 14 | 15 | $output = ''; 16 | 17 | if (!empty($errors)) { 18 | $output .= '

The record could not be saved:

'; 19 | $output .= ''; 24 | } 25 | 26 | $output .= '
27 | 28 | 29 | 30 |
'; 31 | 32 | 33 | return $output; 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JokeList/Controller.php: -------------------------------------------------------------------------------- 1 | sort($_GET['sort']); 7 | } 8 | 9 | if (!empty($_GET['search'])) { 10 | $jokeList = $jokeList->search($_GET['search']); 11 | } 12 | 13 | return $jokeList; 14 | } 15 | 16 | public function delete(\JokeSite\JokeList $jokeList): \JokeSite\JokeList { 17 | return $jokeList->delete($_POST['id']); 18 | } 19 | } -------------------------------------------------------------------------------- /JokeList/View.php: -------------------------------------------------------------------------------- 1 | Add new joke

7 |
8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |

Sort: Newest first | Oldest first 16 |

'; 32 | return $output; 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Model/JokeForm.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 17 | $this->record = $record; 18 | $this->submitted = $submitted; 19 | $this->errors = $errors; 20 | } 21 | 22 | /* 23 | * @description load a record from the database 24 | * @param $id - ID of the record to load from the database 25 | */ 26 | public function load(int $id): JokeForm { 27 | $stmt = $this->pdo->prepare('SELECT * FROM joke WHERE id = :id'); 28 | $stmt->execute(['id' => $id]); 29 | $record = $stmt->fetch(); 30 | return new JokeForm($this->pdo, $this->submitted, $record); 31 | } 32 | 33 | /* 34 | * @description return the record currently being represented 35 | * this may have come from the DB or $_POST 36 | */ 37 | public function getJoke(): array { 38 | return $this->record; 39 | } 40 | 41 | /* 42 | * @description has the form been submitted or not? 43 | */ 44 | public function isSubmitted(): bool { 45 | return $this->submitted; 46 | } 47 | 48 | /* 49 | * @description return a list of validation errors in the current $record 50 | */ 51 | public function getErrors(): array { 52 | return $this->errors; 53 | } 54 | 55 | 56 | /* 57 | * @description attempt to save $record to the database, insert or update 58 | * depending on whether $record['id'] is set 59 | */ 60 | public function save(array $record): JokeForm { 61 | $errors = $this->validate($record); 62 | 63 | if (!empty($errors)) { 64 | // Return a new instance with $record set to the form submission 65 | // When the view displays the joke, it will display the invalid 66 | // form submission back in the box 67 | return new JokeForm($this->pdo, true, $record, $errors); 68 | } 69 | 70 | if (!empty($record['id'])) { 71 | return $this->update($record); 72 | } 73 | else { 74 | return $this->insert($record); 75 | } 76 | } 77 | 78 | /* 79 | * @description validates $record 80 | */ 81 | private function validate(array $record): array { 82 | $errors = []; 83 | 84 | if (empty($record['text'])) { 85 | $errors[] = 'Text cannot be blank'; 86 | } 87 | 88 | return $errors; 89 | } 90 | 91 | /* 92 | * @description save the record using an UPDATE query 93 | */ 94 | private function update(array $record): JokeForm { 95 | $stmt = $this->pdo->prepare('UPDATE joke SET text = :text WHERE id = :id'); 96 | $stmt->execute($record); 97 | 98 | return new JokeForm($this->pdo, true, $record); 99 | } 100 | 101 | /* 102 | * @description save the record using an INSERT query 103 | */ 104 | private function insert(array $record): JokeForm { 105 | $stmt = $this->pdo->prepare('INSERT INTO joke (text) VALUES(:text)'); 106 | 107 | $stmt->execute(['text' => $record['text']]); 108 | 109 | $record['id'] = $this->pdo->lastInsertId(); 110 | 111 | return new JokeForm($this->pdo, true, $record); 112 | } 113 | } -------------------------------------------------------------------------------- /Model/JokeList.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 13 | $this->sort = $sort; 14 | $this->keyword = $keyword; 15 | } 16 | 17 | public function sort($dir): self { 18 | return new self($this->pdo, $dir, $this->keyword); 19 | } 20 | 21 | public function search($keyword): self { 22 | return new self($this->pdo, $this->sort, $keyword); 23 | } 24 | 25 | public function getKeyword(): string { 26 | return $this->keyword; 27 | } 28 | 29 | public function getSort(): string { 30 | return $this->sort; 31 | } 32 | 33 | public function delete($id): self { 34 | $stmt = $this->pdo->prepare('DELETE FROM joke WHERE id = :id'); 35 | $stmt->execute(['id' => $id]); 36 | 37 | return $this; 38 | } 39 | 40 | public function getJokes(): array { 41 | $parameters = []; 42 | 43 | if ($this->sort == 'newest') { 44 | $order = ' ORDER BY id DESC'; 45 | } 46 | else if ($this->sort == 'oldest') { 47 | $order = ' ORDER BY id ASC'; 48 | } 49 | else { 50 | $order = ''; 51 | } 52 | 53 | 54 | if ($this->keyword) { 55 | $where = ' WHERE text LIKE :text'; 56 | $parameters['text'] = '%' . $this->keyword . '%'; 57 | } 58 | else { 59 | $where = ''; 60 | } 61 | 62 | 63 | $stmt = $this->pdo->prepare('SELECT * FROM joke ' . $where . $order); 64 | $stmt->execute($parameters); 65 | 66 | return $stmt->fetchAll(); 67 | } 68 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImmutableMVC 2 | A bare-bones example of an Immutable MVC powered website 3 | 4 | The reasoing and thought process behind this code is available here: https://r.je/immutable-mvc-crud-application.html 5 | -------------------------------------------------------------------------------- /config.sample.php: -------------------------------------------------------------------------------- 1 | PDO::ERRMODE_EXCEPTION]); 12 | 13 | 14 | $route = $_GET['route'] ?? ''; 15 | 16 | if ($route == '') { 17 | $model = new \JokeSite\JokeList($pdo); 18 | $view = new \JokeList\View(); 19 | } 20 | else if ($route == 'edit') { 21 | $model = new \JokeSite\JokeForm($pdo); 22 | $controller = new \JokeForm\Controller(); 23 | 24 | $model = $controller->edit($model); 25 | 26 | if ($_SERVER['REQUEST_METHOD'] == 'POST') { 27 | $model = $controller->submit($model); 28 | } 29 | 30 | $view = new \JokeForm\View(); 31 | } 32 | else if ($route == 'delete') { 33 | $model = new \JokeSite\JokeList($pdo); 34 | $controller = new \JokeList\Controller(); 35 | 36 | $model = $controller->delete($model); 37 | 38 | $view = new \JokeList\View(); 39 | } 40 | else if ($route == 'filterList') { 41 | $model = new \JokeSite\JokeList($pdo); 42 | $view = new \JokeList\View(); 43 | $controller = new \JokeList\Controller(); 44 | 45 | $model = $controller->filterList($model); 46 | } 47 | else { 48 | http_response_code(404); 49 | echo 'Page not found (Invalid route)'; 50 | } 51 | 52 | 53 | 54 | 55 | echo $view->output($model); -------------------------------------------------------------------------------- /sample-database.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Current Database: `ijdb` 3 | -- 4 | 5 | CREATE DATABASE /*!32312 IF NOT EXISTS*/ `ijdb` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */; 6 | 7 | USE `ijdb`; 8 | 9 | -- 10 | -- Table structure for table `joke` 11 | -- 12 | 13 | DROP TABLE IF EXISTS `joke`; 14 | /*!40101 SET @saved_cs_client = @@character_set_client */; 15 | /*!40101 SET character_set_client = utf8 */; 16 | CREATE TABLE `joke` ( 17 | `id` int(11) NOT NULL AUTO_INCREMENT, 18 | `text` varchar(450) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 19 | PRIMARY KEY (`id`) 20 | ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 21 | /*!40101 SET character_set_client = @saved_cs_client */; 22 | 23 | -- 24 | -- Dumping data for table `joke` 25 | -- 26 | 27 | LOCK TABLES `joke` WRITE; 28 | /*!40000 ALTER TABLE `joke` DISABLE KEYS */; 29 | INSERT INTO `joke` VALUES (1,'!false - it\'s funny because it\'s true'), 30 | (2,'Why was the empty array locked outside? It didn\'t have any keys'); 31 | /*!40000 ALTER TABLE `joke` ENABLE KEYS */; 32 | UNLOCK TABLES; --------------------------------------------------------------------------------