├── README.md
├── _installation
├── 01-create-database.sql
├── 02-create-table-song.sql
└── 03-insert-demo-data-into-table-song.sql
├── application
├── config
│ └── config.php
├── controllers
│ ├── error.php
│ ├── home.php
│ └── songs.php
├── core
│ ├── application.php
│ └── controller.php
├── libs
│ └── pdo-debug.php
├── model
│ └── model.php
└── views
│ ├── _templates
│ ├── footer.php
│ └── header.php
│ ├── error
│ └── index.php
│ ├── home
│ ├── example_one.php
│ ├── example_two.php
│ └── index.php
│ ├── index.php
│ └── songs
│ ├── edit.php
│ └── index.php
├── composer.json
├── index.php
└── public
├── css
└── style.css
├── img
└── demo-image.png
└── js
└── application.js
/README.md:
--------------------------------------------------------------------------------
1 | # TINY
2 |
3 | TINY is a reduced version on [MINI](https://github.com/panique/mini), an extremely simple and easy to understand demo
4 | PHP application. TINY is NOT a professional framework, it's the opposite: Just a tiny config-free application.
5 | It does not need mod_rewrite and therefore runs everywhere. If you just want to show some pages, do a few database calls
6 | and a little-bit of AJAX here and there, without reading in massive documentations of highly complex professional
7 | frameworks, then TINY might be very useful for you.
8 |
9 | TINY's full folder/file structure is fully accessible from the web, be aware of that (like in Wordpress, Prestashop etc
10 | too by the way). To prevent people from looking into your files/folder there are some workarounds (try to call a view
11 | file directly), but keep in mind that this is just made for quick prototypes and small sites, so be careful when using
12 | TINY in live situations.
13 |
14 | ## Features
15 |
16 | - extremely simple, easy to understand
17 | - runs nearly config-free everywhere
18 | - simple structure
19 | - demo CRUD actions: Create, Read, Update and Delete database entries easily
20 | - demo AJAX call
21 | - tries to follow PSR 1/2 coding guidelines
22 | - uses PDO for any database requests, comes with an additional PDO debug tool to emulate your SQL statements
23 | - commented code
24 | - uses only native PHP code, so people don't have to learn a framework
25 |
26 | ## Requirements
27 |
28 | - PHP 5.3.0+
29 | - MySQL
30 |
31 | ## Installation
32 |
33 | 1. If you run TINY from within a sub-folder, edit the folder's name in application/config/config.php and change
34 | `define('URL_SUB_FOLDER', 'tiny-master');`. If you don't use a sub-folder, then simply comment out this line.
35 | 2. Edit the database credentials in `application/config/config.php`
36 | 3. Execute the .sql statements in the `_installation/`-folder (with PHPMyAdmin for example).
37 |
38 | Here's 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/)
39 | and the same for [Ubuntu 12.04 LTS](http://www.dev-metal.com/setup-basic-lamp-stack-linux-apache-mysql-php-ubuntu-12-04/).
40 |
41 | ## Differences to MINI
42 |
43 | TINY is a modified version of MINI, made to run in every environment. TINY does NOT need mod_rewrite.
44 |
45 | MINI uses mod_rewrite to a.) create better URLs and b.) block public access to any folders and any files outside of /public, so users / attackers will not be able to call anything except index.php and the contents in /public, usually public files like .js, .css, images and so on. This also prevents attackers to have access to stuff like .git folder/files or to any temporary swap files of any files, sometimes generated by text-editors, IDEs, FTP programs etc.
46 |
47 | Applications / frameworks etc. that do not make use of mod_rewrite usually have the problem that every file inside their structure is directly callable, for example every .php file, or the wp-config.php, the configuration file of WordPress, is directly callable in masses of WordPress installations, sometimes even on very large sites. Usually okay, as the attacker will not see any output, but as lots of tools make temp-copies and swap files of a currently edited / uploaded file it might become critical. Bots and clever attackers have an easy game getting these clear-text-files. Non-PHP-files would be downloadable in plain-text, too (just imagine all the .inc files).
48 |
49 | ### What does this mean for TINY ?
50 |
51 | Lot’s of hosting and development environments don’t have mod_rewrite activated and/or it’s impossible for the developer (for whatever reason), then TINY might be an alternative. Without mod_rewrite it’s not possible to block access to every folder/file in a really good way.
52 |
53 | Therefore, TINY is not a good choice for live sites visited by the public. But to be honest, major parts of the internet still work like that, it’s disturbing how many – even big – sites let users / attackers call every .php files inside their structure and let them look into folders.
54 |
55 | However, TINY has some workarounds: To prevent everybody from looking into your view files there’s a line of PHP in the beginning of every view file, asking if we are inside the application or not. If yes, the files is parsed, if not, PHP will stop parsing the file. This is how this looks in application/views/songs/edit.php
56 |
57 | ```php
58 |
59 |
60 |
61 |
62 | ```
63 |
64 | To prevent people from looking into folders there are several index.php in these folders that do basically nothing. WordPress and other (low-security) applications do similar / same stuff:
65 |
66 | ```php
67 |

117 |
118 | ## Quick-Start
119 |
120 | #### The structure in general
121 |
122 | The application's URL-path translates directly to the controllers (=files) and their methods inside
123 | application/controllers.
124 |
125 | `example.com/home/exampleOne` will do what the *exampleOne()* method in application/controllers/home.php says.
126 |
127 | `example.com/home` will do what the *index()* method in application/controllers/home.php says.
128 |
129 | `example.com` will do what the *index()* method in application/controllers/home.php says (default fallback).
130 |
131 | `example.com/songs` will do what the *index()* method in application/controllers/songs.php says.
132 |
133 | `example.com/songs/editsong/17` will do what the *editsong()* method in application/controllers/songs.php says and
134 | will pass `17` as a parameter to it.
135 |
136 | Self-explaining, right ?
137 |
138 | #### Showing a view
139 |
140 | Let's look at the exampleOne()-method in the home-controller (application/controllers/home.php): This simply shows
141 | the header, footer and the example_one.php page (in views/home/). By intention as simple and native as possible.
142 |
143 | ```php
144 | public function exampleOne()
145 | {
146 | // load views
147 | require APP . 'views/_templates/header.php';
148 | require APP . 'views/home/example_one.php';
149 | require APP . 'views/_templates/footer.php';
150 | }
151 | ```
152 |
153 | #### Working with data
154 |
155 | Let's look into the index()-method in the songs-controller (application/controllers/songs.php): Similar to exampleOne,
156 | but here we also request data. Again, everything is extremely reduced and simple: $this->model->getAllSongs() simply
157 | calls the getAllSongs()-method in application/model/model.php.
158 |
159 | ```php
160 | public function index()
161 | {
162 | // getting all songs and amount of songs
163 | $songs = $this->model->getAllSongs();
164 | $amount_of_songs = $this->model->getAmountOfSongs();
165 |
166 | // load views. within the views we can echo out $songs and $amount_of_songs easily
167 | require APP . 'views/_templates/header.php';
168 | require APP . 'views/songs/index.php';
169 | require APP . 'views/_templates/footer.php';
170 | }
171 | ```
172 |
173 | For extreme simplicity, all data-handling methods are in application/model/model.php. This is for sure not really
174 | professional, but the most simple implementation. Have a look how getAllSongs() in model.php looks like: Pure and
175 | super-simple PDO.
176 |
177 | ```php
178 | public function getAllSongs()
179 | {
180 | $sql = "SELECT id, artist, track, link FROM song";
181 | $query = $this->db->prepare($sql);
182 | $query->execute();
183 |
184 | return $query->fetchAll();
185 | }
186 | ```
187 |
188 | The result, here $songs, can then easily be used directly
189 | inside the view files (in this case application/views/songs/index.php, in a simplified example):
190 |
191 | ```php
192 |
193 |
194 |
195 | artist)) echo htmlspecialchars($song->artist, ENT_QUOTES, 'UTF-8'); ?> |
196 | track)) echo htmlspecialchars($song->track, ENT_QUOTES, 'UTF-8'); ?> |
197 |
198 |
199 |
200 | ```
201 |
202 | ## Dear haters, trolls and everything-sucks-people...
203 |
204 | ... TINY is just a even more reduced version of MINI, which is a simple helper-tool I've created for my daily work,
205 | simply because it was much easier to setup and to handle than real frameworks. For several use-cases it's totally okay, does the job and there's absolutely no reason to discuss why it's "shit compared to Laravel", why it does not follow several MVC principles or why there's no personal unpaid support or no russian translation or similar weird stuff. The trolling against Open-Source-projects (and their authors) has really reached insane dimensions.
206 |
207 | I've written this unpaid, voluntarily, in my free-time and uploaded it on GitHub to share. It's totally free, for private and commercial use. If you don't like it, don't use it. If you see issues, then please write a ticket (and if you are really cool: I'm very thankful for any commits!). But don't bash, don't complain, don't hate. Only bad people do so.
208 |
209 | ## Changelog
210 |
211 | **November 2014**
212 | - [panique] forked from MINI, initial release
213 |
--------------------------------------------------------------------------------
/_installation/01-create-database.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE IF NOT EXISTS `tiny`;
2 |
--------------------------------------------------------------------------------
/_installation/02-create-table-song.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE `tiny`.`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 |
--------------------------------------------------------------------------------
/_installation/03-insert-demo-data-into-table-song.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO `tiny`.`song` (`id`, `artist`, `track`, `link`) VALUES
2 | (1, 'Dena', 'Cash, Diamond Ring, Swimming Pools', 'http://www.youtube.com/watch?v=r4CDc9yCAqE'),
3 | (2, 'Jessy Lanza', 'Kathy Lee', 'http://vimeo.com/73455369'),
4 | (3, 'The Orwells', 'In my Bed (live)', 'http://www.youtube.com/watch?v=8tA_2qCGnmE'),
5 | (4, 'L''Orange & Stik Figa', 'Smoke Rings', 'https://www.youtube.com/watch?v=Q5teohMyGEY'),
6 | (5, 'Labyrinth Ear', 'Navy Light', 'http://www.youtube.com/watch?v=a9qKkG7NDu0'),
7 | (6, 'Bon Hiver', 'Wolves (Kill them with Colour Remix)', 'http://www.youtube.com/watch?v=5GXAL5mzmyw'),
8 | (7, 'Detachments', 'Circles (Martyn Remix)', 'http://www.youtube.com/watch?v=UzS7Gvn7jJ0'),
9 | (8, 'Dillon & Dirk von Loetzow', 'Tip Tapping (Live at ZDF Aufnahmezustand)', 'https://www.youtube.com/watch?v=hbrOLsgu000'),
10 | (9, 'Dillon', 'Contact Us (Live at ZDF Aufnahmezustand)', 'https://www.youtube.com/watch?v=E6WqTL2Up3Y'),
11 | (10, 'Tricky', 'Hey Love (Promo Edit)', 'http://www.youtube.com/watch?v=OIsCGdW49OQ'),
12 | (11, 'Compuphonic', 'Sunset feat. Marques Toliver (DJ T. Remix)', 'http://www.youtube.com/watch?v=Ue5ZWSK9r00'),
13 | (12, 'Ludovico Einaudi', 'Divenire (live @ Royal Albert Hall London)', 'http://www.youtube.com/watch?v=X1DRDcGlSsE'),
14 | (13, 'Maxxi Soundsystem', 'Regrets we have no use for (Radio1 Rip)', 'https://soundcloud.com/maxxisoundsystem/maxxi-soundsystem-ft-name-one'),
15 | (14, 'Beirut', 'Nantes (Fredo & Thang Remix)', 'https://www.youtube.com/watch?v=ojV3oMAgGgU'),
16 | (15, 'Buku', 'All Deez', 'http://www.youtube.com/watch?v=R0bN9JRIqig'),
17 | (16, 'Pilocka Krach', 'Wild Pete', 'http://www.youtube.com/watch?v=4wChP_BEJ4s'),
18 | (17, 'Mount Kimbie', 'Here to stray (live at Pitchfork Music Festival Paris)', 'http://www.youtube.com/watch?v=jecgI-zEgIg'),
19 | (18, 'Kool Savas', 'King of Rap (2012) / Ein Wunder', 'http://www.youtube.com/watch?v=mTqc6UTG1eY&hd=1'),
20 | (19, 'Chaim feat. Meital De Razon', 'Love Rehab (Original Mix)', 'http://www.youtube.com/watch?v=MJT1BbNFiGs'),
21 | (20, 'Emika', 'Searching', 'http://www.youtube.com/watch?v=oscuSiHfbwo'),
22 | (21, 'Emika', 'Sing to me', 'http://www.youtube.com/watch?v=k9sDBZm8pgk'),
23 | (22, 'George Fitzgerald', 'Thinking of You', 'http://www.youtube.com/watch?v=-14B8l49iKA'),
24 | (23, 'Disclosure', 'You & Me (Flume Edit)', 'http://www.youtube.com/watch?v=OUkkaqSNduU'),
25 | (24, 'Crystal Castles', 'Doe Deer', 'http://www.youtube.com/watch?v=zop0sWrKJnQ'),
26 | (25, 'Tok Tok vs. Soffy O.', 'Missy Queens Gonna Die', 'http://www.youtube.com/watch?v=EN0Tnw5zy6w'),
27 | (26, 'Fink', 'Maker (Synapson Remix)', 'http://www.youtube.com/watch?v=Dyd-cUkj4Nk'),
28 | (27, 'Flight Facilities (ft. Christine Hoberg)', 'Clair De Lune', 'http://www.youtube.com/watch?v=Jcu1AHaTchM'),
29 | (28, 'Karmon', 'Turning Point (Original Mix)', 'https://www.youtube.com/watch?v=-tB-zyLSPEo'),
30 | (29, 'Shuttle Life', 'The Birds', 'http://www.youtube.com/watch?v=-I3m3cWDEtM'),
31 | (30, 'Santé', 'Homegirl (Rampa Mix)', 'http://www.youtube.com/watch?v=fnhMNOWxLYw');
32 |
--------------------------------------------------------------------------------
/application/config/config.php:
--------------------------------------------------------------------------------
1 | model->getAllSongs();
22 | $amount_of_songs = $this->model->getAmountOfSongs();
23 |
24 | // load views. within the views we can echo out $songs and $amount_of_songs easily
25 | require APP . 'views/_templates/header.php';
26 | require APP . 'views/songs/index.php';
27 | require APP . 'views/_templates/footer.php';
28 | }
29 |
30 | /**
31 | * ACTION: addSong
32 | * This method handles what happens when you move to http://yourproject/songs/addsong
33 | * IMPORTANT: This is not a normal page, it's an ACTION. This is where the "add a song" form on songs/index
34 | * directs the user after the form submit. This method handles all the POST data from the form and then redirects
35 | * the user back to songs/index via the last line: header(...)
36 | * This is an example of how to handle a POST request.
37 | */
38 | public function addSong()
39 | {
40 | // if we have POST data to create a new song entry
41 | if (isset($_POST["submit_add_song"])) {
42 | // do addSong() in model/model.php
43 | $this->model->addSong($_POST["artist"], $_POST["track"], $_POST["link"]);
44 | }
45 |
46 | // where to go after song has been added
47 | header('location: ' . URL_WITH_INDEX_FILE . 'songs/index');
48 | }
49 |
50 | /**
51 | * ACTION: deleteSong
52 | * This method handles what happens when you move to http://yourproject/songs/deletesong
53 | * IMPORTANT: This is not a normal page, it's an ACTION. This is where the "delete a song" button on songs/index
54 | * directs the user after the click. This method handles all the data from the GET request (in the URL!) and then
55 | * redirects the user back to songs/index via the last line: header(...)
56 | * This is an example of how to handle a GET request.
57 | * @param int $song_id Id of the to-delete song
58 | */
59 | public function deleteSong($song_id)
60 | {
61 | // if we have an id of a song that should be deleted
62 | if (isset($song_id)) {
63 | // do deleteSong() in model/model.php
64 | $this->model->deleteSong($song_id);
65 | }
66 |
67 | // where to go after song has been deleted
68 | header('location: ' . URL_WITH_INDEX_FILE . 'songs/index');
69 | }
70 |
71 | /**
72 | * ACTION: editSong
73 | * This method handles what happens when you move to http://yourproject/songs/editsong
74 | * @param int $song_id Id of the to-edit song
75 | */
76 | public function editSong($song_id)
77 | {
78 | // if we have an id of a song that should be edited
79 | if (isset($song_id)) {
80 | // do getSong() in model/model.php
81 | $song = $this->model->getSong($song_id);
82 |
83 | // in a real application we would also check if this db entry exists and therefore show the result or
84 | // redirect the user to an error page or similar
85 |
86 | // load views. within the views we can echo out $song easily
87 | require APP . 'views/_templates/header.php';
88 | require APP . 'views/songs/edit.php';
89 | require APP . 'views/_templates/footer.php';
90 | } else {
91 | // redirect user to songs index page (as we don't have a song_id)
92 | header('location: ' . URL_WITH_INDEX_FILE . 'songs/index');
93 | }
94 | }
95 |
96 | /**
97 | * ACTION: updateSong
98 | * This method handles what happens when you move to http://yourproject/songs/updatesong
99 | * IMPORTANT: This is not a normal page, it's an ACTION. This is where the "update a song" form on songs/edit
100 | * directs the user after the form submit. This method handles all the POST data from the form and then redirects
101 | * the user back to songs/index via the last line: header(...)
102 | * This is an example of how to handle a POST request.
103 | */
104 | public function updateSong()
105 | {
106 | // if we have POST data to create a new song entry
107 | if (isset($_POST["submit_update_song"])) {
108 | // do updateSong() from model/model.php
109 | $this->model->updateSong($_POST["artist"], $_POST["track"], $_POST["link"], $_POST['song_id']);
110 | }
111 |
112 | // where to go after song has been added
113 | header('location: ' . URL_WITH_INDEX_FILE . 'songs/index');
114 | }
115 |
116 | /**
117 | * AJAX-ACTION: ajaxGetStats
118 | * TODO documentation
119 | */
120 | public function ajaxGetStats()
121 | {
122 | $amount_of_songs = $this->model->getAmountOfSongs();
123 |
124 | // simply echo out something. A super-simple API would be possible by echoing JSON here
125 | echo $amount_of_songs;
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/application/core/application.php:
--------------------------------------------------------------------------------
1 | getUrlWithoutModRewrite();
22 |
23 | // check for controller: no controller given ? then load start-page
24 | if (!$this->url_controller) {
25 |
26 | require APP . 'controllers/home.php';
27 | $page = new Home();
28 | $page->index();
29 |
30 | } elseif (file_exists(APP . 'controllers/' . $this->url_controller . '.php')) {
31 | // here we did check for controller: does such a controller exist ?
32 |
33 | // if so, then load this file and create this controller
34 | // example: if controller would be "car", then this line would translate into: $this->car = new car();
35 | require APP . 'controllers/' . $this->url_controller . '.php';
36 | $this->url_controller = new $this->url_controller();
37 |
38 | // check for method: does such a method exist in the controller ?
39 | if (method_exists($this->url_controller, $this->url_action)) {
40 |
41 | if(!empty($this->url_params)) {
42 | // Call the method and pass arguments to it
43 | call_user_func_array(array($this->url_controller, $this->url_action), $this->url_params);
44 | } else {
45 | // If no parameters are given, just call the method without parameters, like $this->home->method();
46 | $this->url_controller->{$this->url_action}();
47 | }
48 |
49 | } else {
50 | if(strlen($this->url_action) == 0) {
51 | // no action defined: call the default index() method of a selected controller
52 | $this->url_controller->index();
53 | }
54 | else {
55 | // defined action not existent: show the error page
56 | require APP . 'controllers/error.php';
57 | $page = new Error();
58 | $page->index();
59 | }
60 | }
61 | } else {
62 | require APP . 'controllers/error.php';
63 | $page = new Error();
64 | $page->index();
65 | }
66 | }
67 |
68 | /**
69 | * Get and split the URL
70 | */
71 | private function getUrlWithoutModRewrite()
72 | {
73 | // TODO the "" is weird
74 | // get URL ($_SERVER['REQUEST_URI'] gets everything after domain and domain ending), something like
75 | // array(6) { [0]=> string(0) "" [1]=> string(9) "index.php" [2]=> string(10) "controller" [3]=> string(6) "action" [4]=> string(6) "param1" [5]=> string(6) "param2" }
76 | // split on "/"
77 | $url = explode('/', $_SERVER['REQUEST_URI']);
78 | // also remove everything that's empty or "index.php", so the result is a cleaned array of URL parts, like
79 | // array(4) { [2]=> string(10) "controller" [3]=> string(6) "action" [4]=> string(6) "param1" [5]=> string(6) "param2" }
80 | $url = array_diff($url, array('', 'index.php'));
81 | // to keep things clean we reset the array keys, so we get something like
82 | // array(4) { [0]=> string(10) "controller" [1]=> string(6) "action" [2]=> string(6) "param1" [3]=> string(6) "param2" }
83 | $url = array_values($url);
84 |
85 | // if first element of our URL is the sub-folder (defined in config/config.php), then remove it from URL
86 | if (defined('URL_SUB_FOLDER') && !empty($url[0]) && $url[0] === URL_SUB_FOLDER) {
87 | // remove first element (that's obviously the sub-folder)
88 | unset($url[0]);
89 | // reset keys again
90 | $url = array_values($url);
91 | }
92 |
93 | // Put URL parts into according properties
94 | // By the way, the syntax here is just a short form of if/else, called "Ternary Operators"
95 | // @see http://davidwalsh.name/php-shorthand-if-else-ternary-operators
96 | $this->url_controller = isset($url[0]) ? $url[0] : null;
97 | $this->url_action = isset($url[1]) ? $url[1] : null;
98 |
99 | // Remove controller and action from the split URL
100 | unset($url[0], $url[1]);
101 |
102 | // Rebase array keys and store the URL params
103 | $this->url_params = array_values($url);
104 |
105 | // for debugging. uncomment this if you have problems with the URL
106 | //echo 'Controller: ' . $this->url_controller . '
';
107 | //echo 'Action: ' . $this->url_action . '
';
108 | //echo 'Parameters: ' . print_r($this->url_params, true) . '
';
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/application/core/controller.php:
--------------------------------------------------------------------------------
1 | openDatabaseConnection();
25 | $this->loadModel();
26 | }
27 |
28 | /**
29 | * Open the database connection with the credentials from application/config/config.php
30 | */
31 | private function openDatabaseConnection()
32 | {
33 | // set the (optional) options of the PDO connection. in this case, we set the fetch mode to
34 | // "objects", which means all results will be objects, like this: $result->user_name !
35 | // For example, fetch mode FETCH_ASSOC would return results like this: $result["user_name] !
36 | // @see http://www.php.net/manual/en/pdostatement.fetch.php
37 | $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
38 |
39 | // generate a database connection, using the PDO connector
40 | // @see http://net.tutsplus.com/tutorials/php/why-you-should-be-using-phps-pdo-for-database-access/
41 | $this->db = new PDO(DB_TYPE . ':host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PASS, $options);
42 | }
43 |
44 | /**
45 | * Loads the "model".
46 | * @return object model
47 | */
48 | public function loadModel()
49 | {
50 | require APP . '/model/model.php';
51 | // create new "model" (and pass the database connection)
52 | $this->model = new Model($this->db);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/application/libs/pdo-debug.php:
--------------------------------------------------------------------------------
1 |
14 | * @param string $raw_sql
15 | * @param array $parameters
16 | * @return string
17 | */
18 | function debugPDO($raw_sql, $parameters) {
19 |
20 | $keys = array();
21 | $values = $parameters;
22 |
23 | foreach ($parameters as $key => $value) {
24 |
25 | // check if named parameters (':param') or anonymous parameters ('?') are used
26 | if (is_string($key)) {
27 | $keys[] = '/' . $key . '/';
28 | } else {
29 | $keys[] = '/[?]/';
30 | }
31 |
32 | // bring parameter into human-readable format
33 | if (is_string($value)) {
34 | $values[$key] = "'" . $value . "'";
35 | } elseif (is_array($value)) {
36 | $values[$key] = implode(',', $value);
37 | } elseif (is_null($value)) {
38 | $values[$key] = 'NULL';
39 | }
40 | }
41 |
42 | /*
43 | echo "
[DEBUG] Keys:
";
44 | print_r($keys);
45 |
46 | echo "\n[DEBUG] Values: ";
47 | print_r($values);
48 | echo "
";
49 | */
50 |
51 | $raw_sql = preg_replace($keys, $values, $raw_sql, 1, $count);
52 |
53 | return $raw_sql;
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/application/model/model.php:
--------------------------------------------------------------------------------
1 | db = $db;
12 | } catch (PDOException $e) {
13 | exit('Database connection could not be established.');
14 | }
15 | }
16 |
17 | /**
18 | * Get all songs from database
19 | */
20 | public function getAllSongs()
21 | {
22 | $sql = "SELECT id, artist, track, link FROM song";
23 | $query = $this->db->prepare($sql);
24 | $query->execute();
25 |
26 | // fetchAll() is the PDO method that gets all result rows, here in object-style because we defined this in
27 | // core/controller.php! If you prefer to get an associative array as the result, then do
28 | // $query->fetchAll(PDO::FETCH_ASSOC); or change core/controller.php's PDO options to
29 | // $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ...
30 | return $query->fetchAll();
31 | }
32 |
33 | /**
34 | * Add a song to database
35 | * TODO put this explanation into readme and remove it from here
36 | * Please note that it's not necessary to "clean" our input in any way. With PDO all input is escaped properly
37 | * automatically. We also don't use strip_tags() etc. here so we keep the input 100% original (so it's possible
38 | * to save HTML and JS to the database, which is a valid use case). Data will only be cleaned when putting it out
39 | * in the views (see the views for more info).
40 | * @param string $artist Artist
41 | * @param string $track Track
42 | * @param string $link Link
43 | */
44 | public function addSong($artist, $track, $link)
45 | {
46 | $sql = "INSERT INTO song (artist, track, link) VALUES (:artist, :track, :link)";
47 | $query = $this->db->prepare($sql);
48 | $parameters = array(':artist' => $artist, ':track' => $track, ':link' => $link);
49 |
50 | // useful for debugging: you can see the SQL behind above construction by using:
51 | // echo '[ PDO DEBUG ]: ' . debugPDO($sql, $parameters); exit();
52 |
53 | $query->execute($parameters);
54 | }
55 |
56 | /**
57 | * Delete a song in the database
58 | * Please note: this is just an example! In a real application you would not simply let everybody
59 | * add/update/delete stuff!
60 | * @param int $song_id Id of song
61 | */
62 | public function deleteSong($song_id)
63 | {
64 | $sql = "DELETE FROM song WHERE id = :song_id";
65 | $query = $this->db->prepare($sql);
66 | $parameters = array(':song_id' => $song_id);
67 |
68 | // useful for debugging: you can see the SQL behind above construction by using:
69 | // echo '[ PDO DEBUG ]: ' . debugPDO($sql, $parameters); exit();
70 |
71 | $query->execute($parameters);
72 | }
73 |
74 | /**
75 | * Get a song from database
76 | */
77 | public function getSong($song_id)
78 | {
79 | $sql = "SELECT id, artist, track, link FROM song WHERE id = :song_id LIMIT 1";
80 | $query = $this->db->prepare($sql);
81 | $parameters = array(':song_id' => $song_id);
82 |
83 | // useful for debugging: you can see the SQL behind above construction by using:
84 | // echo '[ PDO DEBUG ]: ' . debugPDO($sql, $parameters); exit();
85 |
86 | $query->execute($parameters);
87 |
88 | // fetch() is the PDO method that get exactly one result
89 | return $query->fetch();
90 | }
91 |
92 | /**
93 | * Update a song in database
94 | * // TODO put this explaination into readme and remove it from here
95 | * Please note that it's not necessary to "clean" our input in any way. With PDO all input is escaped properly
96 | * automatically. We also don't use strip_tags() etc. here so we keep the input 100% original (so it's possible
97 | * to save HTML and JS to the database, which is a valid use case). Data will only be cleaned when putting it out
98 | * in the views (see the views for more info).
99 | * @param string $artist Artist
100 | * @param string $track Track
101 | * @param string $link Link
102 | * @param int $song_id Id
103 | */
104 | public function updateSong($artist, $track, $link, $song_id)
105 | {
106 | $sql = "UPDATE song SET artist = :artist, track = :track, link = :link WHERE id = :song_id";
107 | $query = $this->db->prepare($sql);
108 | $parameters = array(':artist' => $artist, ':track' => $track, ':link' => $link, ':song_id' => $song_id);
109 |
110 | // useful for debugging: you can see the SQL behind above construction by using:
111 | // echo '[ PDO DEBUG ]: ' . debugPDO($sql, $parameters); exit();
112 |
113 | $query->execute($parameters);
114 | }
115 |
116 | /**
117 | * Get simple "stats". This is just a simple demo to show
118 | * how to use more than one model in a controller (see application/controller/songs.php for more)
119 | */
120 | public function getAmountOfSongs()
121 | {
122 | $sql = "SELECT COUNT(id) AS amount_of_songs FROM song";
123 | $query = $this->db->prepare($sql);
124 | $query->execute();
125 |
126 | // fetch() is the PDO method that get exactly one result
127 | return $query->fetch()->amount_of_songs;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/application/views/_templates/footer.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |