├── app ├── logs │ └── .gitignore ├── store │ └── .gitignore ├── conf │ └── .env ├── model │ └── Timestamp.php ├── controller │ └── index.php └── view │ ├── timestamp.php │ └── index.php ├── public ├── .htaccess └── index.php ├── fox ├── lets.php ├── autoload.php ├── constants.php ├── view.php ├── exception.php ├── bootstrap.php ├── config.php ├── router.php ├── database.php └── functions.php ├── LICENSE └── README.md /app/logs/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /app/store/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /app/conf/.env: -------------------------------------------------------------------------------- 1 | DB_NAME=your-awesome-database 2 | DB_USER=with-your-amazing-user 3 | DB_PASS=and-strongest-password 4 | DB_HOST=localhost 5 | DB_PORT=3306 -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | AddDefaultCharset UTF-8 2 | RewriteEngine On 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteCond %{REQUEST_FILENAME} !-d 5 | RewriteRule ^(.*)$ ./index.php?/$1 [L] -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | define('APP_PUBLIC_PATH', __DIR__); 9 | define('FOX_PATH', implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'fox'])); 10 | 11 | require(FOX_PATH.DIRECTORY_SEPARATOR.'bootstrap.php'); 12 | 13 | \Fox\Lets::go(); 14 | -------------------------------------------------------------------------------- /fox/lets.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | namespace Fox; 9 | 10 | class Lets 11 | { 12 | /** 13 | * Fox, let's go! 14 | * 15 | * @return void 16 | */ 17 | public static function go() 18 | { 19 | \Fox\Config::loadEnv(); 20 | \Fox\Router::run(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /fox/autoload.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | $composer_autoload = getValidPath(FOX_PATH, '..', 'vendor', 'autoload.php'); 13 | if (file_exists($composer_autoload)) { 14 | require($composer_autoload); 15 | } 16 | 17 | spl_autoload_register(function ($class) { 18 | if (strpos($class, __NAMESPACE__.'\\Model\\') === 0) { 19 | $name = substr($class, strlen(__NAMESPACE__.'\\Model\\')); 20 | require(getValidPath(MODEL_PATH, $name.'.php')); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /app/model/Timestamp.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | namespace Fox\Model; 9 | 10 | class Timestamp 11 | { 12 | /** 13 | * Timestamp 14 | * 15 | * @var int 16 | */ 17 | private $timestamp; 18 | 19 | /** 20 | * Constructor log 21 | * 22 | * @return void 23 | */ 24 | public function __construct() 25 | { 26 | $this->timestamp = time(); 27 | } 28 | 29 | /** 30 | * Get timestamp 31 | * 32 | * @return int 33 | */ 34 | public function get() 35 | { 36 | return $this->timestamp; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /fox/constants.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | define('APP_PATH', getValidPath(__DIR__, '..', 'app')); 13 | define('CONFIG_PATH', getValidPath(APP_PATH, 'conf')); 14 | define('MODEL_PATH', getValidPath(APP_PATH, 'model')); 15 | define('VIEW_PATH', getValidPath(APP_PATH, 'view')); 16 | define('CONTROLLER_PATH', getValidPath(APP_PATH, 'controller')); 17 | define('LOG_PATH', getValidPath(APP_PATH, 'logs')); 18 | define('STORE_PATH', getValidPath(APP_PATH, 'store')); 19 | -------------------------------------------------------------------------------- /fox/view.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | class View 13 | { 14 | /** 15 | * Call view file to display on browser 16 | * 17 | * @param string $view 18 | * @param array $args 19 | * @return void 20 | */ 21 | public static function call($view, Array $args = []) 22 | { 23 | if ($args) { 24 | extract($args); 25 | } 26 | $view = str_replace('\\', '/', $view); 27 | require(getValidPath(VIEW_PATH, $view.'.php')); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /fox/exception.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | class Exception extends \Exception 13 | { 14 | /** 15 | * Construtor 16 | * 17 | * @param string $msg 18 | * @return void 19 | */ 20 | public function __construct($msg) 21 | { 22 | file_put_contents( 23 | getValidPath(LOG_PATH, 'error.log'), 24 | date('Y-m-d H:i:s').' - '.trim($msg).PHP_EOL, 25 | FILE_APPEND 26 | ); 27 | parent::__construct($msg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/controller/index.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | namespace Fox\Controller; 9 | 10 | class Index 11 | { 12 | /** 13 | * Construtor 14 | * 15 | * @return void 16 | */ 17 | public function __construct() 18 | { 19 | 20 | } 21 | 22 | /** 23 | * Controller index 24 | * 25 | * @return void 26 | */ 27 | public function index() 28 | { 29 | \Fox\View::call('index'); 30 | } 31 | 32 | /** 33 | * Timstamp call 34 | * 35 | * @return void 36 | */ 37 | public function seeMore() 38 | { 39 | $Timestamp = new \Fox\Model\Timestamp(); 40 | 41 | \Fox\View::call( 42 | 'timestamp', 43 | ['Timestamp' => $Timestamp] 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /fox/bootstrap.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | error_reporting(E_ALL); 13 | ini_set('display_errors', true); 14 | 15 | if (!function_exists('getValidPath')) { 16 | 17 | /** 18 | * Returns a valid path to file or directory 19 | * 20 | * @author Gabriel Prates 21 | * @param string $steps the steps to the path 22 | * @return string 23 | */ 24 | function getValidPath(...$steps): string 25 | { 26 | $path = implode(DIRECTORY_SEPARATOR, $steps); 27 | $path = realpath($path)? realpath($path) : $path; 28 | 29 | return $path; 30 | } 31 | 32 | } 33 | 34 | array_map( 35 | function ($file) { 36 | require($file); 37 | }, 38 | preg_grep( 39 | '/'.basename(__FILE__).'$/', 40 | glob(getValidPath(FOX_PATH, '*.php')), 41 | PREG_GREP_INVERT 42 | ) 43 | ); 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Joubert RedRat and Fox contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fox/config.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | class Config 13 | { 14 | /** 15 | * environment variables filename 16 | */ 17 | const ENVFILE = '.env'; 18 | 19 | /** 20 | * Load environment variables 21 | * 22 | * @return void 23 | */ 24 | public static function loadEnv() 25 | { 26 | if (self::envExist()) { 27 | $envfile = file(getValidPath(CONFIG_PATH, self::ENVFILE)); 28 | 29 | foreach ($envfile as $line) { 30 | putenv(trim($line)); 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * Check if environment variables file exists 37 | * 38 | * @return void 39 | */ 40 | public static function envExist() 41 | { 42 | return file_exists(getValidPath(CONFIG_PATH, self::ENVFILE)); 43 | } 44 | 45 | /** 46 | * Request environment variable's value 47 | * 48 | * @param string $arg 49 | * @return mixed 50 | */ 51 | public static function getValue($var) 52 | { 53 | return getenv($var); 54 | } 55 | 56 | /** 57 | * Write new environment variables file 58 | * 59 | * @param array $values 60 | * @return void 61 | */ 62 | public static function writeEnv(Array $values) 63 | { 64 | if (!self::envExist()) { 65 | file_put_contents(getValidPath(CONFIG_PATH, self::ENVFILE, implode(PHP_EOL, $values))); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Fox 2 | 3 | Fox is a MVC micro-framework written in PHP for building fast, simple and small applications. It emerged from a dark season, when I had issues with my internet connection and needed to do a simple test. Even away from contemporary world, from composer and from modern and cool libraries, I could still solve my problem :) 4 | 5 | #### Install 6 | 7 | * `git clone https://github.com/joubertredrat/fox.git` 8 | * In your apache installation, set `public` folder as your document root and done, it's working! 9 | 10 | In a near future, I will provide `Fox` through `composer create-project` 11 | 12 | #### Features 13 | * Extremely slim: `fox` directory's size is only 124 KB. 14 | * Written in pure PHP: you don't need any external library to get it running. 15 | * Dynamic router to controller, (see [How does it work?](#how-does-it-work). 16 | * Composer friendly: you can add any library you want without trouble. 17 | * Object-Oriented Controller and Model (and View, maybe sometime later). 18 | 19 | #### How does it work? 20 | 21 | Fox has a Dynamic router, based on uri requests into public/index.php, as shown below: 22 | ``` 23 | /users/list-admin/br = request 24 | ____________________________ 25 | | users == Controller\Users | 26 | request => index.php => router | list-admin == listAdmin() | => Controller\Users->listAdmin(arg) => View 27 | | br == arg <= br | 28 | |____________________________| 29 | ``` 30 | 31 | #### Todo 32 | * [ ] Provide this through `composer create-project`. 33 | * [ ] Decouple routing from Apache's mod-rewrite, so it could run with Nginx or PHP built-in webserver, for example. 34 | * [ ] Refactor View to OOP aproach. 35 | -------------------------------------------------------------------------------- /fox/router.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | class Router 13 | { 14 | /** 15 | * Process uri from url 16 | * 17 | * @return string 18 | */ 19 | public static function getUri() 20 | { 21 | if (!isset($_SERVER['QUERY_STRING']) || $_SERVER['QUERY_STRING'] == "") { 22 | return 'index'; 23 | } 24 | 25 | $uri = substr($_SERVER['QUERY_STRING'], 1); 26 | 27 | if (substr($uri, strlen($uri) - 1) == '/') { 28 | $uri = substr($uri, 0, strlen($uri) - 1); 29 | } 30 | 31 | return $uri; 32 | } 33 | 34 | /** 35 | * Get uri and call corresponding controller 36 | * 37 | * @return void 38 | */ 39 | public static function run() 40 | { 41 | $route = self::getUri(); 42 | 43 | $uri = explode('/', $route); 44 | 45 | switch (count($uri)) { 46 | case 1: 47 | $class = $uri[0]; 48 | $method = 'index'; 49 | $args = null; 50 | break; 51 | case 2: 52 | $class = $uri[0]; 53 | $method = $uri[1]; 54 | $args = null; 55 | break; 56 | case 3: 57 | case 4: 58 | case 5: 59 | $class = $uri[0]; 60 | $method = $uri[1]; 61 | unset($uri[0]); 62 | unset($uri[1]); 63 | $args = array_values($uri); 64 | break; 65 | default: 66 | exit('Unknown route'); 67 | break; 68 | } 69 | 70 | if(!file_exists(getValidPath(CONTROLLER_PATH, $class.'.php'))) 71 | { 72 | trigger_error("Controller does not exist"); 73 | } 74 | 75 | require(getValidPath(CONTROLLER_PATH, $class.'.php')); 76 | self::formatClassCall($class); 77 | self::formatMethodCall($method); 78 | 79 | $Controller = new $class(); 80 | if ($args) { 81 | switch (count($args)) { 82 | case 1: 83 | $Controller->$method($args[0]); 84 | break; 85 | case 2: 86 | $Controller->$method($args[0], $args[1]); 87 | break; 88 | case 3: 89 | $Controller->$method($args[0], $args[1], $args[2]); 90 | break; 91 | } 92 | } else { 93 | if(!method_exists($Controller, $method)) 94 | { 95 | trigger_error("Page not found"); 96 | } 97 | $Controller->$method(); 98 | } 99 | } 100 | 101 | /** 102 | * Format controller class call 103 | * 104 | * @param string &$class 105 | */ 106 | private static function formatClassCall(&$class) 107 | { 108 | $class = "\Fox\\Controller\\".ucfirst($class); 109 | } 110 | 111 | /** 112 | * Format controller class method call 113 | * 114 | * @param string &$method 115 | */ 116 | private static function formatMethodCall(&$method) 117 | { 118 | $array = explode('-', $method); 119 | if (count($array) == 1) { 120 | return $method; 121 | } 122 | 123 | $first = array_shift($array); 124 | 125 | $array = array_map('ucfirst', $array); 126 | $method = $first.implode('', $array); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /fox/database.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | use \PDO; 13 | 14 | class Database 15 | { 16 | /** 17 | * Connection with database 18 | * 19 | * @var PDO 20 | */ 21 | private static $dbh; 22 | 23 | /** 24 | * Construtor does nothing 25 | * 26 | * @return void 27 | */ 28 | private function __construct() 29 | { 30 | 31 | } 32 | 33 | /** 34 | * Request PDO instance 35 | * 36 | * @return PDO 37 | */ 38 | public static function getInstance() 39 | { 40 | if (!self::isInstantiated()) { 41 | self::setInstance( 42 | self::buildInstance( 43 | \Fox\Config::getValue('DB_NAME'), 44 | \Fox\Config::getValue('DB_USER'), 45 | \Fox\Config::getValue('DB_PASS'), 46 | \Fox\Config::getValue('DB_HOST'), 47 | \Fox\Config::getValue('DB_PORT') 48 | ) 49 | ); 50 | } 51 | return self::$dbh; 52 | } 53 | 54 | /** 55 | * Request PDO instance outside singleton mode 56 | * 57 | * @param string $db_name 58 | * @param string $db_user 59 | * @param string $db_pass 60 | * @param string $db_host 61 | * @param int $db_port 62 | * @return PDO 63 | */ 64 | public static function getNewPdo($db_name, $db_user, $db_pass, $db_host = 'localhost', $db_port = 3306) 65 | { 66 | return self::buildInstance($db_name, $db_user, $db_pass, $db_host, $db_port); 67 | } 68 | 69 | /** 70 | * Test connection 71 | * 72 | * @param string $db_name 73 | * @param string $db_user 74 | * @param string $db_pass 75 | * @param string $db_host 76 | * @param int $db_port 77 | * @return bool 78 | */ 79 | public static function testConnection($db_name, $db_user, $db_pass, $db_host = 'localhost', $db_port = 3306) 80 | { 81 | $dbh = self::getNewPdo($db_name, $db_user, $db_pass, $db_host, $db_port); 82 | return $dbh instanceof \PDO; 83 | } 84 | 85 | /** 86 | * Define PDO on singleton 87 | * 88 | * @param PDO $dbh 89 | * @return void 90 | */ 91 | private static function setInstance(PDO $dbh) 92 | { 93 | self::$dbh = $dbh; 94 | } 95 | 96 | /** 97 | * Verify if exists on PDO instance on singleton. 98 | * 99 | * @return bool 100 | */ 101 | public static function isInstantiated() 102 | { 103 | return self::$dbh instanceof PDO; 104 | } 105 | 106 | /** 107 | * Create new PDO 108 | * 109 | * @param string $db_name 110 | * @param string $db_user 111 | * @param string $db_pass 112 | * @param string $db_host 113 | * @param int $db_port 114 | * @return PDO 115 | */ 116 | private static function buildInstance($db_name, $db_user, $db_pass, $db_host = 'localhost', $db_port = 3306) 117 | { 118 | try { 119 | $dbh = new PDO( 120 | 'mysql:host='.$db_host.';port='.$db_port.';dbname='.$db_name.';charset=utf8', 121 | $db_user, 122 | $db_pass 123 | ); 124 | $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 125 | $dbh->exec("SET CHARACTER SET utf8"); 126 | $dbh->exec("SET NAMES utf8"); 127 | 128 | return $dbh; 129 | } catch (\PDOException $e) { 130 | return false; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /fox/functions.php: -------------------------------------------------------------------------------- 1 | 6 | * @license: MIT 7 | * @see https://github.com/joubertredrat/fox/ 8 | */ 9 | 10 | namespace Fox; 11 | 12 | class Functions 13 | { 14 | /** 15 | * Validate value types 16 | * 17 | * @param mixed $value 18 | * @param string $type 19 | * @return bool 20 | */ 21 | public static function validateType($value, $type) 22 | { 23 | switch ($type) { 24 | case 'string': 25 | $return = is_string($value); 26 | break; 27 | case 'integer': 28 | $return = filter_var($value, FILTER_VALIDATE_INT) !== false; 29 | break; 30 | case 'float': 31 | $return = filter_var($value, FILTER_VALIDATE_FLOAT) !== false; 32 | break; 33 | case 'boolean': 34 | $return = is_bool($value); 35 | break; 36 | case 'datetime': 37 | if (self::validateType(substr($value, 0, 4), 'datetime_year')) { 38 | \DateTime::createFromFormat('Y-m-d H:i:s', $value); 39 | $validate = DateTime::getLastErrors(); 40 | $return = ($validate['warning_count'] == 0 && $validate['error_count'] == 0); 41 | } else { 42 | $return = false; 43 | } 44 | break; 45 | case 'datetime_year': 46 | if ($value == '0000') { 47 | $return = true; 48 | } else { 49 | $return = filter_var( 50 | (int) $value, 51 | FILTER_VALIDATE_INT, 52 | ['options' => ['min_range' => 1000, 'max_range' => 9999]] 53 | ) !== false; 54 | } 55 | break; 56 | case 'datetime_date': 57 | if ($value === '0000-00-00') { 58 | $return = true; 59 | } else { 60 | $return = self::validateType($value.' '.date('H:i:s'), 'datetime'); 61 | } 62 | break; 63 | case 'datetime_time': 64 | if ($value === '00:00:00') { 65 | $return = true; 66 | } else { 67 | $return = self::validateType(date('Y-m-d').' '.$value, 'datetime'); 68 | } 69 | break; 70 | case 'datetime_timestamp': 71 | $return = filter_var( 72 | (int) $value, 73 | FILTER_VALIDATE_INT, 74 | ['options' => ['min_range' => -2147483647, 'max_range' => 2147483647]] 75 | ) !== false; 76 | break; 77 | default: 78 | $return = false; 79 | break; 80 | } 81 | return $return; 82 | } 83 | 84 | /** 85 | * Request app url 86 | * 87 | * @param string $uri 88 | * @return string 89 | */ 90 | public static function getAppUrl($uri = null) 91 | { 92 | $url = []; 93 | $url[] = $_SERVER['REQUEST_SCHEME']; 94 | if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { 95 | $url[] = 's'; 96 | } 97 | $url[] = '://'.$_SERVER['HTTP_HOST']; 98 | if ($_SERVER['SERVER_PORT'] != '80') { 99 | $url[] = ':'.$_SERVER['SERVER_PORT'].'/'; 100 | } 101 | $url[] = str_replace($_SERVER['QUERY_STRING'] ? $_SERVER['QUERY_STRING'] : '', '', $_SERVER['REQUEST_URI']); 102 | // $url[] = '/'; 103 | if ($uri) { 104 | $url[] = $uri; 105 | } 106 | 107 | return implode('', $url); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/view/timestamp.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fox Framework 7 | 272 | 273 | 274 | 275 | 276 | 277 | 320 | 321 | 322 |
  278 |
279 | 280 | 281 | 282 | 283 | 284 | 285 | 295 | 296 | 297 | 298 |
286 | 287 | 288 | 292 | 293 |
289 |

Hi again,

290 |

Was requested timestamp from controller, I think that is get(); ?>.

291 |
294 |
299 | 300 | 301 | 315 | 316 | 317 | 318 |
319 |
 
323 | 324 | -------------------------------------------------------------------------------- /app/view/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fox Framework 7 | 272 | 273 | 274 | 275 | 276 | 277 | 335 | 336 | 337 |
  278 |
279 | 280 | 281 | 282 | 283 | 284 | 285 | 310 | 311 | 312 | 313 |
286 | 287 | 288 | 307 | 308 |
289 |

Hi,

290 |

It's working. Wan't to see more?

291 | 292 | 293 | 294 | 303 | 304 | 305 |
295 | 296 | 297 | 298 | 299 | 300 | 301 |
Then click here
302 |
306 |
309 |
314 | 315 | 316 | 330 | 331 | 332 | 333 |
334 |
 
338 | 339 | --------------------------------------------------------------------------------