├── README.md
├── Vagrantfile
├── _install
├── mini3.png
├── mysql
│ ├── 01-create-database.sql
│ ├── 02-create-table-song.sql
│ └── 03-insert-demo-data-into-table-song.sql
└── nginx
│ └── default
├── application
├── Controller
│ ├── ErrorController.php
│ ├── HomeController.php
│ └── SongsController.php
├── Core
│ ├── Application.php
│ └── Model.php
├── Model
│ └── Song.php
├── config
│ └── config.php
└── view
│ ├── _templates
│ ├── footer.php
│ └── header.php
│ ├── error
│ └── index.php
│ ├── home
│ ├── example_one.php
│ ├── example_two.php
│ └── index.php
│ └── songs
│ ├── edit.php
│ └── index.php
├── bootstrap.sh
├── composer.json
└── public
├── .htaccess
├── css
└── style.css
├── img
└── demo-image.png
├── index.php
└── js
└── application.js
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # MINI3
4 |
5 | MINI3 is an extremely simple and easy to understand skeleton PHP application, reduced to the max.
6 | MINI3 is NOT a professional framework and does not come with all the stuff real frameworks have.
7 | If you just want to show some pages, do a few database calls and a little-bit of AJAX here and there, without
8 | reading in massive documentations of highly complex professional frameworks, then MINI3 might be very useful for you.
9 | MINI3 is easy to install, runs nearly everywhere and doesn't make things more complicated than necessary.
10 |
11 |
12 | [MINI](https://github.com/panique/mini) (original version) and [MINI2](https://github.com/panique/mini2) (used Slim router) were built by me (panique), MINI3 is an excellent and improved version
13 | of the original MINI, made by [JaoNoctus](https://github.com/JaoNoctus). Big thanks, man! :)
14 |
15 | ## Features
16 |
17 | - extremely simple, easy to understand
18 | - simple but clean structure
19 | - makes "beautiful" clean URLs
20 | - demo CRUD actions: Create, Read, Update and Delete database entries easily
21 | - demo AJAX call
22 | - tries to follow PSR coding guidelines
23 | - uses PDO for any database requests, comes with an additional PDO debug tool to emulate your SQL statements
24 | - commented code
25 | - uses only native PHP code, so people don't have to learn a framework
26 | - uses PSR-4 autoloader
27 |
28 | ## Requirements (but it's auto-installed)
29 |
30 | - PHP 8
31 | - MySQL
32 | - basic knowledge of Composer for sure
33 | - for auto-installation: VirtualBox, Vagrant
34 |
35 | ## Forks
36 |
37 | There are some nice upgraded versions of this mini framework, check it out at https://github.com/ribafs/php-router
38 |
39 | ## Installation (in Vagrant, 100% automatic)
40 |
41 | To keep things super-simple, we are using Vagrant here, a simple technology to run virtual machines for development.
42 | It's outdated, but does the job, and is much easier to understand than Docker. Just install VirtualBox, Vagrant, then
43 | copy this repo's code to a folder, go to that folder and type:
44 |
45 | ```bash
46 | vagrant up
47 | ```
48 |
49 | This will create a virtual machine with the configs given in `Vagrantfile`: It will create an Ubuntu 2022.04 Jammy64
50 | VM with 1024MB RAM, sync the current folder to `/var/www/html` inside the VM, make the VM available on the IP
51 | `192.168.56.77` and start the bash script `bootstrap.sh`, which is just a set of commands that will install all
52 | necessary software.
53 |
54 | If the auto-installer is finished, go to http://192.168.56.77 in your browser and click around a bit ;)
55 |
56 | # OLD INSTALLATION TUTORIALS FROM 2016
57 |
58 | Below you'll find installation tutorial for the old version of MINI3 from 2016.
59 |
60 | ## Installation (in Vagrant, 100% automatic)
61 |
62 | If you are using Vagrant for your development, then you can install MINI3 with one click (or one command on the
63 | command line) [[Vagrant doc](https://docs.vagrantup.com/v2/getting-started/provisioning.html)]. MINI3 comes with a demo
64 | Vagrant-file (defines your Vagrant box) and a demo bootstrap.sh which automatically installs Apache, PHP, MySQL,
65 | PHPMyAdmin, git and Composer, sets a chosen password in MySQL and PHPMyadmin and even inside the application code,
66 | downloads the Composer-dependencies, activates mod_rewrite and edits the Apache settings, downloads the code from GitHub
67 | and runs the demo SQL statements (for demo data). This is 100% automatic, you'll end up after +/- 5 minutes with a fully
68 | running installation of MINI3 inside an Ubuntu 14.04 LTS Vagrant box.
69 |
70 | To do so, put `Vagrantfile` and `bootstrap.sh` from `_vagrant` inside a folder (and nothing else).
71 | Do `vagrant box add ubuntu/trusty64` to add Ubuntu 14.04 LTS ("Trusty Thar") 64bit to Vagrant (unless you already have
72 | it), then do `vagrant up` to run the box. When installation is finished you can directly use the fully installed demo
73 | app on `192.168.33.66`. As this just a quick demo environment the MySQL root password and the PHPMyAdmin root password
74 | are set to `12345678`, the project is installed in `/var/www/html/myproject`. You can change this for sure inside
75 | `bootstrap.sh`.
76 |
77 | ## Auto-Installation on Ubuntu 14.04 LTS (in 30 seconds)
78 |
79 | You can install MINI3 including Apache, MySQL, PHP and PHPMyAdmin, mod_rewrite, Composer, all necessary settings and
80 | even the passwords inside the configs file by simply downloading one file and executing it, the entire installation
81 | will run 100% automatically. If you are stuck somehow, also have a look into this tutorial for the original MINI1,
82 | it's basically the same installation process:
83 | [Install MINI in 30 seconds inside Ubuntu 14.04 LTS](http://www.dev-metal.com/install-mini-30-seconds-inside-ubuntu-14-04-lts/)
84 |
85 | ## Manual Installation
86 |
87 | 1. Edit the database credentials in `application/config/config.php`
88 | 2. Execute the .sql statements in the `_install/`-folder (with PHPMyAdmin for example).
89 | 3. Make sure you have mod_rewrite activated on your server / in your environment. Some guidelines:
90 | [Ubuntu 14.04 LTS](http://www.dev-metal.com/enable-mod_rewrite-ubuntu-14-04-lts/),
91 | [Ubuntu 12.04 LTS](http://www.dev-metal.com/enable-mod_rewrite-ubuntu-12-04-lts/),
92 | [EasyPHP on Windows](http://stackoverflow.com/questions/8158770/easyphp-and-htaccess),
93 | [AMPPS on Windows/Mac OS](http://www.softaculous.com/board/index.php?tid=3634&title=AMPPS_rewrite_enable/disable_option%3F_please%3F),
94 | [XAMPP for Windows](http://www.leonardaustin.com/blog/technical/enable-mod_rewrite-in-xampp/),
95 | [MAMP on Mac OS](http://stackoverflow.com/questions/7670561/how-to-get-htaccess-to-work-on-mamp)
96 | 4. Install composer and run `composer install` in the project's folder to create the PSR-4 autoloading stuff from Composer automatically.
97 | If you have no idea what this means: Remember the "good" old times when we were using "include file.php" all over our projects to include and use something ?
98 | PSR-0/4 is the modern, clean and automatic version of that. Please have a google research if that's important for you.
99 |
100 | Feel free to commit your guideline for Ubuntu 16.04 LTS or other linuxes to the list!
101 |
102 | MINI3 runs without any further configuration. You can also put it inside a sub-folder, it will work without any
103 | further configuration.
104 | Maybe useful: A simple tutorial on [How to install LAMPP (Linux, Apache, MySQL, PHP, PHPMyAdmin) on Ubuntu 14.04 LTS](http://www.dev-metal.com/installsetup-basic-lamp-stack-linux-apache-mysql-php-ubuntu-14-04-lts/)
105 | and [the same for Ubuntu 12.04 LTS](http://www.dev-metal.com/setup-basic-lamp-stack-linux-apache-mysql-php-ubuntu-12-04/).
106 |
107 | ## Server configs for
108 |
109 | ### nginx
110 |
111 | ```nginx
112 | server {
113 | server_name default_server _; # Listen to any servername
114 | listen [::]:80;
115 | listen 80;
116 |
117 | root /var/www/html/myproject/public;
118 |
119 | location / {
120 | index index.php;
121 | try_files /$uri /$uri/ /index.php?url=$uri;
122 | }
123 |
124 | location ~ \.(php)$ {
125 | fastcgi_pass unix:/var/run/php5-fpm.sock;
126 | fastcgi_index index.php;
127 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
128 | include fastcgi_params;
129 | }
130 | }
131 | ```
132 |
133 | A deeper discussion on nginx setups can be found [here](https://github.com/panique/mini/issues/55).
134 |
135 | ## Security
136 |
137 | The script makes use of mod_rewrite and blocks all access to everything outside the /public folder.
138 | Your .git folder/files, operating system temp files, the application-folder and everything else is not accessible
139 | (when set up correctly). For database requests PDO is used, so no need to think about SQL injection (unless you
140 | are using extremely outdated MySQL versions).
141 |
142 | ## How to include stuff / use PSR-4
143 |
144 | As this project uses proper PSR-4 namespaces, make sure you load/use your stuff correctly:
145 | Instead of including classes with old-school code like `include xxx.php`, simply do something like `use Mini\Model\Song;` on top of your file (modern IDEs even do that automatically).
146 | This would automatically include the file *Song.php* from the folder *Mini/Model* (it's case-sensitive!).
147 |
148 | But wait, there's no `Mini/Model/Song.php` in the project, but a `application/Model/Song.php`, right ?
149 | To keep things cleaner, the composer.json sets a *namespace* (see code below), which is basically a name or an alias, for a certain folder / area of your application,
150 | in this case the folder `application` is now reachable via `Mini` when including stuff.
151 |
152 | ```
153 | {
154 | "psr-4":
155 | {
156 | "Mini\\" : "application/"
157 | }
158 | }
159 | ```
160 |
161 | This might look stupid at first, but comes in handy later. To sum it up:
162 |
163 | To load the file `application/Model/Song.php`, write a `use Mini\Model\Song;` on top of your controller file.
164 | Have a look into the SongController to get an idea how everything works!
165 |
166 | FYI: As decribed in the install tutorial, you'll need do perform a "composer install" when setting up your application for the first time, which will
167 | create a set of files (= the autoloader) inside /vendor folder. This is the normal way Composer handle this stuff. If you delete your vendor folder
168 | the autoloading will not work anymore. If you change something in the composer.json, always make sure to run composer install/update again!
169 |
170 | ## Goodies
171 |
172 | MINI3 comes with a little customized [PDO debugger tool](https://github.com/panique/pdo-debug) (find the code in
173 | application/libs/helper.php), trying to emulate your PDO-SQL statements. It's extremely easy to use:
174 |
175 | ```php
176 | $sql = "SELECT id, artist, track, link FROM song WHERE id = :song_id LIMIT 1";
177 | $query = $this->db->prepare($sql);
178 | $parameters = array(':song_id' => $song_id);
179 |
180 | echo Helper::debugPDO($sql, $parameters);
181 |
182 | $query->execute($parameters);
183 | ```
184 |
185 | ## License
186 |
187 | This project is licensed under the MIT License.
188 | This means you can use and modify it for free in private or commercial projects.
189 |
190 | ## Quick-Start
191 |
192 | #### The structure in general
193 |
194 | The application's URL-path translates directly to the controllers (=files) and their methods inside
195 | application/controllers.
196 |
197 | `example.com/home/exampleOne` will do what the *exampleOne()* method in application/Controller/HomeController.php says.
198 |
199 | `example.com/home` will do what the *index()* method in application/Controller/HomeController.php says.
200 |
201 | `example.com` will do what the *index()* method in application/Controller/HomeController.php says (default fallback).
202 |
203 | `example.com/songs` will do what the *index()* method in application/Controller/SongsController.php says.
204 |
205 | `example.com/songs/editsong/17` will do what the *editsong()* method in application/Controller/SongsController.php says and
206 | will pass `17` as a parameter to it.
207 |
208 | Self-explaining, right ?
209 |
210 | #### Showing a view
211 |
212 | Let's look at the exampleOne()-method in the home-controller (application/Controller/HomeController.php): This simply shows
213 | the header, footer and the example_one.php page (in views/home/). By intention as simple and native as possible.
214 |
215 | ```php
216 | public function exampleOne()
217 | {
218 | // load view
219 | require APP . 'views/_templates/header.php';
220 | require APP . 'views/home/example_one.php';
221 | require APP . 'views/_templates/footer.php';
222 | }
223 | ```
224 |
225 | #### Working with data
226 |
227 | Let's look into the index()-method in the songs-controller (application/Controller/SongsController.php): Similar to exampleOne,
228 | but here we also request data. Again, everything is extremely reduced and simple: $Song->getAllSongs() simply
229 | calls the getAllSongs()-method in application/Model/Song.php (when $Song = new Song()).
230 |
231 | ```php
232 | namespace Mini\Controller
233 |
234 | use Mini\Model\Song;
235 |
236 | class SongsController
237 | {
238 | public function index()
239 | {
240 | // Instance new Model (Song)
241 | $Song = new Song();
242 | // getting all songs and amount of songs
243 | $songs = $Song->getAllSongs();
244 | $amount_of_songs = $Song->getAmountOfSongs();
245 |
246 | // load view. within the view files we can echo out $songs and $amount_of_songs easily
247 | require APP . 'views/_templates/header.php';
248 | require APP . 'views/songs/index.php';
249 | require APP . 'views/_templates/footer.php';
250 | }
251 | }
252 | ```
253 |
254 | For extreme simplicity, data-handling methods are in application/model/ClassName.php. Have a look how getAllSongs() in model.php looks like: Pure and
255 | super-simple PDO.
256 |
257 | ```php
258 | namespace Mini\Model
259 |
260 | use Mini\Core\Model;
261 |
262 | class Song extends Model
263 | {
264 | public function getAllSongs()
265 | {
266 | $sql = "SELECT id, artist, track, link FROM song";
267 | $query = $this->db->prepare($sql);
268 | $query->execute();
269 |
270 | return $query->fetchAll();
271 | }
272 | }
273 | ```
274 |
275 | The result, here $songs, can then easily be used directly
276 | inside the view files (in this case application/views/songs/index.php, in a simplified example):
277 |
278 | ```php
279 |
280 |
281 |
282 | artist)) echo htmlspecialchars($song->artist, ENT_QUOTES, 'UTF-8'); ?> |
283 | track)) echo htmlspecialchars($song->track, ENT_QUOTES, 'UTF-8'); ?> |
284 |
285 |
286 |
287 | ```
288 |
289 | ## Contribute
290 |
291 | Please commit into the develop branch (which holds the in-development version), not into master branch
292 | (which holds the tested and stable version).
293 |
294 | ## Changelog
295 |
296 | **August 2016**
297 |
298 | - [panique] fix for weird lowercase/uppercase path problem (also big thanks to @snickbit & @ugurozturk for the fix!)
299 | - [panique] forking joanoctus's excellent PSR4 ersion of MINI to MINI3
300 |
301 | **January 2016**
302 |
303 | - [joanoctus] implementing PSR-4 autoloader
304 |
305 | **February 2015**
306 |
307 | - [jeroenseegers] nginx setup configuration
308 |
309 | **December 2014**
310 | - [panique] css fixes
311 | - [panique] renamed controller / view to singular
312 | - [panique] added charset to PDO creation (increased security)
313 |
314 | **November 2014**
315 | - [panique] auto-install script for Vagrant
316 | - [panique] basic documentation
317 | - [panique] PDO-debugger is now a static helper-method, not a global function anymore
318 | - [panique] folder renaming
319 | - [reg4in] JS AJAX calls runs now properly even when using script in sub-folder
320 | - [panique] removed all "models", using one model file now
321 | - [panique] full project renaming, re-branding
322 |
323 | **October 2014**
324 | - [tarcnux/panique] PDO debugging
325 | - [panique] demo ajax call
326 | - [panique] better output escaping
327 | - [panique] renamed /libs to /core
328 | - [tarcnux] basic CRUD (create/read/update/delete) examples have now an U (update)
329 | - [panique] URL is now config-free, application detects URL and sub-folder
330 | - [elysdir] htaccess has some good explanation-comments now
331 | - [bst27] fallback for non-existing controller / method
332 | - [panique] fallback will show error-page now
333 | - [digitaltoast] URL split fix to make php-mvc work flawlessly on nginx
334 | - [AD7six] security improvement: moved index.php to /public, route ALL request to /public
335 |
336 | **September 2014**
337 | - [panique] added link to support forum
338 | - [panique] added link to Facebook page
339 |
340 | **August 2014**
341 | - [panique] several changes in the README, donate-button changes
342 |
343 | **June 2014**
344 | - [digitaltoast] removed X-UA-Compatible meta tag from header (as it's not needed anymore these days)
345 | - [digitaltoast] removed protocol in jQuery URL (modern way to load external files, making it independent to protocol change)
346 | - [digitaltoast] downgraded jQuery from 2.1 to 1.11 to avoid problems when working with IE7/8 (jQuery 2 dropped IE7/8 support)
347 | - [panique] moved jQuery loading to footer (to avoid page render blocking)
348 |
349 | **April 2014**
350 | - [panique] updated jQuery link to 2.1
351 | - [panique] more than 3 parameters (arguments to be concrete) are possible
352 | - [panique] cleaner way of parameter handling
353 | - [panique] smaller cleanings and improvements
354 | - [panique] Apache 2.4 install information
355 |
356 | **January 2014**
357 | - [panique] fixed .htaccess issue when there's a controller named "index" and a base index.php (which collide)
358 |
359 | ## Other stuff
360 |
361 | And by the way, I'm also blogging at [Dev Metal](http://www.dev-metal.com) :)
362 |
363 | ## Support the project
364 |
365 |
366 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5 | VAGRANTFILE_API_VERSION = "2"
6 |
7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8 |
9 | # Ubuntu 2022.04
10 | config.vm.box = "ubuntu/jammy64"
11 |
12 | # Create a private network, which allows host-only access to the machine using a specific IP.
13 | config.vm.network "private_network", ip: "192.168.56.77"
14 |
15 | config.vm.provider "virtualbox" do |vb|
16 | vb.memory = "1024"
17 | end
18 |
19 | # Share an additional folder to the guest VM. The first argument is the path on the host to the actual folder.
20 | # The second argument is the path on the guest to mount the folder.
21 | config.vm.synced_folder "./", "/var/www/html"
22 |
23 | # Define the bootstrap file: A (shell) script that runs after first setup of your box (= provisioning)
24 | config.vm.provision :shell, path: "bootstrap.sh"
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/_install/mini3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/panique/mini3/34a2867e8175319074e9ff122227e34c1e428a8f/_install/mini3.png
--------------------------------------------------------------------------------
/_install/mysql/01-create-database.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE IF NOT EXISTS `mini`;
2 |
--------------------------------------------------------------------------------
/_install/mysql/02-create-table-song.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE `mini`.`song` (
2 | `id` int(11) NOT NULL AUTO_INCREMENT,
3 | `artist` text COLLATE utf8_unicode_ci NOT NULL,
4 | `track` text COLLATE utf8_unicode_ci NOT NULL,
5 | `link` text COLLATE utf8_unicode_ci,
6 | PRIMARY KEY (`id`),
7 | UNIQUE KEY `id` (`id`)
8 | ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
9 |
--------------------------------------------------------------------------------
/_install/mysql/03-insert-demo-data-into-table-song.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO `mini`.`song` (`id`, `artist`, `track`, `link`) VALUES
2 | (1, 'Rin', 'Ljubav/Beichtstuhl', 'https://www.youtube.com/watch?v=MDHJMirQ5PI'),
3 | (2, 'Jeremih feat. Natasha Mosley', 'F*** U All The Time', 'https://www.youtube.com/watch?v=6-Bq7PCKCJ4'),
4 | (3, 'Nao', 'In the Morning', 'https://www.youtube.com/watch?v=uuocmqLRgOM'),
5 | (4, 'Sofi / Tukker', 'Matadora', 'https://www.youtube.com/watch?v=d6GJeap4Oqo'),
6 | (5, 'Yung Hurn', 'Nein', 'https://www.youtube.com/watch?v=22m5eU6uxeQ'),
7 | (6, 'Rin', 'Error', 'https://www.youtube.com/watch?v=VzajBMa-2P8'),
8 | (7, 'Detachments', 'Circles (Martyn Remix)', 'http://www.youtube.com/watch?v=UzS7Gvn7jJ0'),
9 | (8, 'Survive', 'Hourglass', 'https://www.youtube.com/watch?v=JVOe2oGoHLk'),
10 | (9, 'Big Narstie', 'Hello Hi', 'https://www.youtube.com/watch?v=q10WwZJmPew'),
11 | (10, 'Sleaford Mods', 'Tarantula Deadly Cargo', 'https://www.youtube.com/watch?v=E-gvxxhcS8s'),
12 | (11, 'Mykki Blanco & Woodkid', 'Highschool never ends', 'https://www.youtube.com/watch?v=cNGR4ciDmTA'),
13 | (12, 'Secondcity & Tyler Rowe', 'I Enter', 'https://www.youtube.com/watch?v=vAmDJAxNMi0'),
14 | (13, 'Maxxi Soundsystem', 'Regrets we have no use for (Radio1 Rip)', 'https://soundcloud.com/maxxisoundsystem/maxxi-soundsystem-ft-name-one'),
15 | (14, 'Jamie T', 'Don''t you find', 'https://www.youtube.com/watch?v=-tmoaFAT108'),
16 | (15, 'Sierra Kid', 'Ein Fan von Dir', 'https://www.youtube.com/watch?v=dfabdmbpQeQ'),
17 | (16, 'SSIO', 'Nullkommaneun', 'https://www.youtube.com/watch?v=Slei8n08Cqk'),
18 | (17, 'Pupkulies & Rebecca', 'ICI', 'https://www.youtube.com/watch?v=60tebPRj_D0'),
19 | (18, 'Color War', 'Shapeshifting', 'https://vimeo.com/111250184'),
20 | (19, 'RÜFÜS', 'Innerbloom', 'https://www.youtube.com/watch?v=IA1liCmUsAM'),
21 | (20, 'RÜFÜS', 'Tonight', 'https://www.youtube.com/watch?v=GCa_TKn9ghI');
--------------------------------------------------------------------------------
/_install/nginx/default:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80 default_server;
3 | listen [::]:80 default_server;
4 |
5 | root /var/www/html/public;
6 |
7 | index index.html index.htm index.nginx-debian.html;
8 |
9 | server_name _;
10 |
11 | location / {
12 | index index.php;
13 | try_files /$uri /$uri/ /index.php?url=$uri;
14 | }
15 |
16 | location ~ \.php$ {
17 | include snippets/fastcgi-php.conf;
18 | fastcgi_pass unix:/run/php/php8.1-fpm.sock;
19 | }
20 |
21 | location ~ /\.ht {
22 | deny all;
23 | }
24 | }
--------------------------------------------------------------------------------
/application/Controller/ErrorController.php:
--------------------------------------------------------------------------------
1 | getAllSongs();
31 | $amount_of_songs = $Song->getAmountOfSongs();
32 |
33 | // load views. within the views we can echo out $songs and $amount_of_songs easily
34 | require APP . 'view/_templates/header.php';
35 | require APP . 'view/songs/index.php';
36 | require APP . 'view/_templates/footer.php';
37 | }
38 |
39 | /**
40 | * ACTION: addSong
41 | * This method handles what happens when you move to http://yourproject/songs/addsong
42 | * IMPORTANT: This is not a normal page, it's an ACTION. This is where the "add a song" form on songs/index
43 | * directs the user after the form submit. This method handles all the POST data from the form and then redirects
44 | * the user back to songs/index via the last line: header(...)
45 | * This is an example of how to handle a POST request.
46 | */
47 | public function addSong()
48 | {
49 | // if we have POST data to create a new song entry
50 | if (isset($_POST["submit_add_song"])) {
51 | // Instance new Model (Song)
52 | $Song = new Song();
53 | // do addSong() in model/model.php
54 | $Song->addSong($_POST["artist"], $_POST["track"], $_POST["link"]);
55 | }
56 |
57 | // where to go after song has been added
58 | header('location: ' . URL . 'songs/index');
59 | }
60 |
61 | /**
62 | * ACTION: deleteSong
63 | * This method handles what happens when you move to http://yourproject/songs/deletesong
64 | * IMPORTANT: This is not a normal page, it's an ACTION. This is where the "delete a song" button on songs/index
65 | * directs the user after the click. This method handles all the data from the GET request (in the URL!) and then
66 | * redirects the user back to songs/index via the last line: header(...)
67 | * This is an example of how to handle a GET request.
68 | * @param int $song_id Id of the to-delete song
69 | */
70 | public function deleteSong($song_id)
71 | {
72 | // if we have an id of a song that should be deleted
73 | if (isset($song_id)) {
74 | // Instance new Model (Song)
75 | $Song = new Song();
76 | // do deleteSong() in model/model.php
77 | $Song->deleteSong($song_id);
78 | }
79 |
80 | // where to go after song has been deleted
81 | header('location: ' . URL . 'songs/index');
82 | }
83 |
84 | /**
85 | * ACTION: editSong
86 | * This method handles what happens when you move to http://yourproject/songs/editsong
87 | * @param int $song_id Id of the to-edit song
88 | */
89 | public function editSong($song_id)
90 | {
91 | // if we have an id of a song that should be edited
92 | if (isset($song_id)) {
93 | // Instance new Model (Song)
94 | $Song = new Song();
95 | // do getSong() in model/model.php
96 | $song = $Song->getSong($song_id);
97 |
98 | // in a real application we would also check if this db entry exists and therefore show the result or
99 | // redirect the user to an error page or similar
100 |
101 | // load views. within the views we can echo out $song easily
102 | require APP . 'view/_templates/header.php';
103 | require APP . 'view/songs/edit.php';
104 | require APP . 'view/_templates/footer.php';
105 | } else {
106 | // redirect user to songs index page (as we don't have a song_id)
107 | header('location: ' . URL . 'songs/index');
108 | }
109 | }
110 |
111 | /**
112 | * ACTION: updateSong
113 | * This method handles what happens when you move to http://yourproject/songs/updatesong
114 | * IMPORTANT: This is not a normal page, it's an ACTION. This is where the "update a song" form on songs/edit
115 | * directs the user after the form submit. This method handles all the POST data from the form and then redirects
116 | * the user back to songs/index via the last line: header(...)
117 | * This is an example of how to handle a POST request.
118 | */
119 | public function updateSong()
120 | {
121 | // if we have POST data to create a new song entry
122 | if (isset($_POST["submit_update_song"])) {
123 | // Instance new Model (Song)
124 | $Song = new Song();
125 | // do updateSong() from model/model.php
126 | $Song->updateSong($_POST["artist"], $_POST["track"], $_POST["link"], $_POST['song_id']);
127 | }
128 |
129 | // where to go after song has been added
130 | header('location: ' . URL . 'songs/index');
131 | }
132 |
133 | /**
134 | * AJAX-ACTION: ajaxGetStats
135 | * TODO documentation
136 | */
137 | public function ajaxGetStats()
138 | {
139 | // Instance new Model (Song)
140 | $Song = new Song();
141 | $amount_of_songs = $Song->getAmountOfSongs();
142 |
143 | // simply echo out something. A supersimple API would be possible by echoing JSON here
144 | echo $amount_of_songs;
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/application/Core/Application.php:
--------------------------------------------------------------------------------
1 | splitUrl();
24 |
25 | // check for controller: no controller given ? then load start-page
26 | if (!$this->url_controller) {
27 |
28 | $page = new \Mini\Controller\HomeController();
29 | $page->index();
30 |
31 | } elseif (file_exists(APP . 'Controller/' . ucfirst($this->url_controller) . 'Controller.php')) {
32 | // here we did check for controller: does such a controller exist ?
33 |
34 | // if so, then load this file and create this controller
35 | // like \Mini\Controller\CarController
36 | $controller = "\\Mini\\Controller\\" . ucfirst($this->url_controller) . 'Controller';
37 | $this->url_controller = new $controller();
38 |
39 | // check for method: does such a method exist in the controller ?
40 | if (method_exists($this->url_controller, (string) $this->url_action)) {
41 |
42 | if (!empty($this->url_params)) {
43 | // Call the method and pass arguments to it
44 | call_user_func_array(array($this->url_controller, $this->url_action), $this->url_params);
45 | } else {
46 | // If no parameters are given, just call the method without parameters, like $this->home->method();
47 | $this->url_controller->{$this->url_action}();
48 | }
49 |
50 | } else {
51 | if (empty($this->url_action)) {
52 | // no action defined: call the default index() method of a selected controller
53 | $this->url_controller->index();
54 | } else {
55 | header('location: ' . URL . 'error');
56 | }
57 | }
58 | } else {
59 | header('location: ' . URL . 'error');
60 | }
61 | }
62 |
63 | /**
64 | * Get and split the URL
65 | */
66 | private function splitUrl()
67 | {
68 | if (isset($_GET['url'])) {
69 |
70 | // split URL
71 | $url = trim($_GET['url'], '/');
72 | $url = filter_var($url, FILTER_SANITIZE_URL);
73 | $url = explode('/', $url);
74 |
75 | // Put URL parts into according properties
76 | // By the way, the syntax here is just a short form of if/else, called "Ternary Operators"
77 | // @see http://davidwalsh.name/php-shorthand-if-else-ternary-operators
78 | $this->url_controller = isset($url[0]) ? $url[0] : null;
79 | $this->url_action = isset($url[1]) ? $url[1] : null;
80 |
81 | // Remove controller and action from the split URL
82 | unset($url[0], $url[1]);
83 |
84 | // Rebase array keys and store the URL params
85 | $this->url_params = array_values($url);
86 |
87 | // for debugging. uncomment this if you have problems with the URL
88 | //echo 'Controller: ' . $this->url_controller . '
';
89 | //echo 'Action: ' . $this->url_action . '
';
90 | //echo 'Parameters: ' . print_r($this->url_params, true) . '
';
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/application/Core/Model.php:
--------------------------------------------------------------------------------
1 | user_name !
33 | // For example, fetch mode FETCH_ASSOC would return results like this: $result["user_name] !
34 | // @see http://www.php.net/manual/en/pdostatement.fetch.php
35 | $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
36 |
37 | // generate a database connection, using the PDO connector
38 | // @see http://net.tutsplus.com/tutorials/php/why-you-should-be-using-phps-pdo-for-database-access/
39 | $this->db = new PDO(DB_TYPE . ':host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=' . DB_CHARSET, DB_USER, DB_PASS, $options);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/application/Model/Song.php:
--------------------------------------------------------------------------------
1 | db->prepare($sql);
26 | $query->execute();
27 |
28 | // fetchAll() is the PDO method that gets all result rows, here in object-style because we defined this in
29 | // core/controller.php! If you prefer to get an associative array as the result, then do
30 | // $query->fetchAll(PDO::FETCH_ASSOC); or change core/controller.php's PDO options to
31 | // $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ...
32 | return $query->fetchAll();
33 | }
34 |
35 | /**
36 | * Add a song to database
37 | * TODO put this explanation into readme and remove it from here
38 | * Please note that it's not necessary to "clean" our input in any way. With PDO all input is escaped properly
39 | * automatically. We also don't use strip_tags() etc. here so we keep the input 100% original (so it's possible
40 | * to save HTML and JS to the database, which is a valid use case). Data will only be cleaned when putting it out
41 | * in the views (see the views for more info).
42 | * @param string $artist Artist
43 | * @param string $track Track
44 | * @param string $link Link
45 | */
46 | public function addSong($artist, $track, $link)
47 | {
48 | $sql = "INSERT INTO song (artist, track, link) VALUES (:artist, :track, :link)";
49 | $query = $this->db->prepare($sql);
50 | $parameters = array(':artist' => $artist, ':track' => $track, ':link' => $link);
51 |
52 | // useful for debugging: you can see the SQL behind above construction by using:
53 | // echo '[ PDO DEBUG ]: ' . Helper::debugPDO($sql, $parameters); exit();
54 |
55 | $query->execute($parameters);
56 | }
57 |
58 | /**
59 | * Delete a song in the database
60 | * Please note: this is just an example! In a real application you would not simply let everybody
61 | * add/update/delete stuff!
62 | * @param int $song_id Id of song
63 | */
64 | public function deleteSong($song_id)
65 | {
66 | $sql = "DELETE FROM song WHERE id = :song_id";
67 | $query = $this->db->prepare($sql);
68 | $parameters = array(':song_id' => $song_id);
69 |
70 | // useful for debugging: you can see the SQL behind above construction by using:
71 | // echo '[ PDO DEBUG ]: ' . Helper::debugPDO($sql, $parameters); exit();
72 |
73 | $query->execute($parameters);
74 | }
75 |
76 | /**
77 | * Get a song from database
78 | * @param integer $song_id
79 | */
80 | public function getSong($song_id)
81 | {
82 | $sql = "SELECT id, artist, track, link FROM song WHERE id = :song_id LIMIT 1";
83 | $query = $this->db->prepare($sql);
84 | $parameters = array(':song_id' => $song_id);
85 |
86 | // useful for debugging: you can see the SQL behind above construction by using:
87 | // echo '[ PDO DEBUG ]: ' . Helper::debugPDO($sql, $parameters); exit();
88 |
89 | $query->execute($parameters);
90 |
91 | // fetch() is the PDO method that get exactly one result
92 | return $query->fetch();
93 | }
94 |
95 | /**
96 | * Update a song in database
97 | * // TODO put this explaination into readme and remove it from here
98 | * Please note that it's not necessary to "clean" our input in any way. With PDO all input is escaped properly
99 | * automatically. We also don't use strip_tags() etc. here so we keep the input 100% original (so it's possible
100 | * to save HTML and JS to the database, which is a valid use case). Data will only be cleaned when putting it out
101 | * in the views (see the views for more info).
102 | * @param string $artist Artist
103 | * @param string $track Track
104 | * @param string $link Link
105 | * @param int $song_id Id
106 | */
107 | public function updateSong($artist, $track, $link, $song_id)
108 | {
109 | $sql = "UPDATE song SET artist = :artist, track = :track, link = :link WHERE id = :song_id";
110 | $query = $this->db->prepare($sql);
111 | $parameters = array(':artist' => $artist, ':track' => $track, ':link' => $link, ':song_id' => $song_id);
112 |
113 | // useful for debugging: you can see the SQL behind above construction by using:
114 | // echo '[ PDO DEBUG ]: ' . Helper::debugPDO($sql, $parameters); exit();
115 |
116 | $query->execute($parameters);
117 | }
118 |
119 | /**
120 | * Get simple "stats". This is just a simple demo to show
121 | * how to use more than one model in a controller (see application/controller/songs.php for more)
122 | */
123 | public function getAmountOfSongs()
124 | {
125 | $sql = "SELECT COUNT(id) AS amount_of_songs FROM song";
126 | $query = $this->db->prepare($sql);
127 | $query->execute();
128 |
129 | // fetch() is the PDO method that get exactly one result
130 | return $query->fetch()->amount_of_songs;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/application/config/config.php:
--------------------------------------------------------------------------------
1 |
3 | Find MINI3 on GitHub.
4 | If you like the project, feel free to buy me a coffee by clicking the yellow button ;)
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |