├── 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 | Buy Me A Coffee 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 | 15 | 16 | -------------------------------------------------------------------------------- /application/views/_templates/header.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | PHP MVC skeleton 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | Everything in this box is loaded from application/views/_templates/header.php ! 32 |
33 | The green line is added via JavaScript (to show how to integrate JavaScript). 34 |
35 |

The header (used on all pages)

36 | 37 |

Demo image, to show usage of public/img folder

38 |
39 | Demo image 40 |
41 | 42 |

Demo Navigation

43 | 53 | 54 |

Demo JavaScript

55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /application/views/error/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

This is the Error-page. Will be shown when a page (= controller / method) does not exist.

5 |
6 | -------------------------------------------------------------------------------- /application/views/home/example_one.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

You are in the View: application/views/home/example_one.php (everything in the box comes from this file)

5 |

In a real application this could be a normal page.

6 |
7 | -------------------------------------------------------------------------------- /application/views/home/example_two.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

You are in the View: application/views/home/example_two.php (everything in the box comes from this file)

5 |

In a real application this could be a normal page.

6 |
7 | -------------------------------------------------------------------------------- /application/views/home/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

You are in the View: application/views/home/index.php (everything in the box comes from this file)

5 |

In a real application this could be the homepage.

6 |
7 | -------------------------------------------------------------------------------- /application/views/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

You are in the View: application/views/song/edit.php (everything in this box comes from that file)

5 | 6 |
7 |

Edit a song

8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /application/views/songs/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

You are in the View: application/views/song/index.php (everything in this box comes from that file)

5 | 6 |
7 |

Add a song

8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |

Amount of songs (data from second model)

21 |
22 | 23 |
24 |

Amount of songs (via AJAX)

25 |
26 | 27 |
28 |
29 |

List of songs (data from first model)

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 52 | 53 | 54 | 55 | 56 | 57 |
IdArtistTrackLinkDELETEEDIT
id)) echo htmlspecialchars($song->id, ENT_QUOTES, 'UTF-8'); ?>artist)) echo htmlspecialchars($song->artist, ENT_QUOTES, 'UTF-8'); ?>track)) echo htmlspecialchars($song->track, ENT_QUOTES, 'UTF-8'); ?> 48 | link)) { ?> 49 | link, ENT_QUOTES, 'UTF-8'); ?> 50 | 51 | deleteedit
58 |
59 |
60 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "panique/tiny", 3 | "type": "project", 4 | "description": "A tiny PHP boilerplate", 5 | "keywords": ["skeleton", "boilerplate", "naked", "barebone", "application"], 6 | "homepage": "https://github.com/panique/tiny", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Panique", 11 | "role": "Developer" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/panique/tiny/issues", 16 | "source": "https://github.com/panique/tiny" 17 | }, 18 | "require": { 19 | "php": ">=5.3.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 |