├── .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 .= '';
20 | foreach ($errors as $error) {
21 | $output .= '- ' . $error . '
';
22 | }
23 | $output .= '
';
24 | }
25 |
26 | $output .= '';
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 |
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;
--------------------------------------------------------------------------------