Initial commit, part due

This commit is contained in:
Geoff Doty 2018-12-09 03:04:11 -05:00
commit d750252304
9 changed files with 694 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/vendor/
/build/
composer.lock

66
README.md Normal file
View File

@ -0,0 +1,66 @@
# Mite
> The tiniest framework that *mite* work
*A word of warning, this is an experiment that I'm resurrecting, so it's quite dated atm*
---
```
/___\ ___ _
)O.o( |\/| o | |_ BE small
\_^_/ | | | | |_ be STRONG
" "
```
---
## Overview
**Mite** is a single-file framework that was born out of a necessity of its time, circa 2011, to enable rapid prototyping for PHP. When ideas came too fast to capture in most conventional frameworks for PHP, **Mite** provided a one-include-and-go solution.
This was also the first time I had the idea to break away from `MVC` structure and just build something based around routing. Turned out this idea was not mine alone, it started with `sinatra.rb`, and continued with `express.js`.
There are **better options** out there now
- [FlightPHP](http://flightphp.com/) appears to have achieved my goal
### Features
### Requirements
- PHP 5.3+
## Usage
Create an `index.php` file and put the following code in it
```php
class Prototype extends Mite {
function index()
{
// *.tpl.* not required
$this->view('index.tpl.php');
}
}
$app = new Prototype();
$app->route('/', 'index');
$app->run();
```
The file name as `index.php` is important for routing
> SEE: `examples/`
## TODO
- More Examples
- Document API
- Modernize (ditch 5.2), but remain small
- Mite DB construct take PDO object, not build one
- Support REST functionality

24
composer.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "n2geoff/mite",
"description": "single-file php framework for rapid prototyping",
"type": "framework",
"license": "MIT",
"authors": [
{
"name": "Geoff Doty",
"email": "n2geoff@gmail.com"
}
],
"scripts": {
"test": "vendor/bin/phpunit"
},
"autoload": {
"classmap": [
"src/"
]
},
"require": {},
"require-dev": {
"phpunit/phpunit": "^7.5"
}
}

13
examples/53oop.php Normal file
View File

@ -0,0 +1,13 @@
<?php require('../vendor/autoload.php');
/*
* Example: 5.3 OOP Anonymous Functions
*/
$app = new Mite(array('debug' => TRUE, 'index' => '/mite/examples/53oop.php'));
$app->route('/', function() use($app) {
$app->view('views/index.tpl.php');
});
$app->run();

16
examples/extend.php Normal file
View File

@ -0,0 +1,16 @@
<?php require('../vendor/autoload.php');
/*
* Example: Using MITE as a base class
*/
class test extends Mite {
function index()
{
$this->view('views/index.tpl.php');
}
}
$app = new test(array('debug' => TRUE, 'index' => '/mite/examples/extend.php'));
$app->route('/', 'index');
$app->run();

View File

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Mite Work</title>
<?php echo Mite::style(); ?>
<style type="">
.main {margin: 0 auto; width: 600px; background-color: #ececec;padding: 10px;}
</style>
<?php echo Mite::style(); ?>
</head>
<body>
<div class="main">
<div class="dp100">
<div style="font-family: monospace; font-size: 1.4em;">
<hr/>
<h1>MITE</h1>
<hr/>
./___\.........___ _.................................
.)O.o(...|\/|.o.|.|_.....BE small.................... <br/>
.\_^_/...|..|.|.|.|_.....be STRONG................... <br/>
.."."................................................
<hr/>
<center>A Rapid Prototyping Micro Framework</center>
<hr/>
</div>
</div>
<div class="dp100">
<a href="#">Home</a> | <a href="#">Documentation</a>
<hr/>
</div>
<div class="dp100">
<b>MITE</b> is a tiny php framework library designed
to enble developers to rapidly prototype out <b>micro ideas</b>.
<br/><br/>
What are <b>Micro Ideas</b>?
<br/><br/>
<b>Micro Ideas</b> are things like the developer logging tool
<a href="#">logr</a>, or the simple issue tracker <a href="#">taskd</a>
<br/><br/>
And, just for fun;)
</div>
<div class="dp100">
<h3>Features</h3>
<ul>
<li>Tiny (under 15k)</li>
<li>Portable (single file)</li>
<li>Dynamic Named Routes</li>
<li>Debugging Profiler</li>
<li>Includes a tiny CSS Library</li>
</ul>
</div>
<div class="dp100">
<h3>Requirements</h3>
<ul>
<li>PHP 5.2.x or beyond</li>
</ul>
</div>
<div class="dp100">
<h3>Code!</h3>
<div style="padding: 10px; background-color: #FFFFFF; border: 1px dashed dimgray;">
include 'mite.php';
</div>
</div>
<div class="dp100"><h3><a href="#">Download!</a></h3></div>
<div class="dp100">
<hr/>
<center>&copy;2011 Mite. All rights reserved.</center>
<hr/>
</div>
</div>
</body>
</html>
s

15
phpunit.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
forceCoversAnnotation="true"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
verbose="true">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

472
src/mite.php Normal file
View File

@ -0,0 +1,472 @@
<?php
/**
* Mite - A Rapid Prototyping Micro Framework
*
******************************************************************
* /___\ ___ _
* )O.o( |\/| o | |_ BE small
* \_^_/ | | | | |_ be STRONG
* " "
******************************************************************
* @author Geoff Doty <n2geoff@gmail.com>
* @version .76
*
* @copyright 2011 Geoff Doty
* @license
*/
class Mite {
static private $routes = array(); //route-to-class mapping
static private $index = '/index.php'; //
static private $request = NULL; //url in address bar
// for classes
public $callback = NULL; //class/function
static private $init = NULL; //ah, bad name
static public $errors = array(); //bites
// unused
static private $method = NULL; //for RESTish
static private $options = array(); //Registry
// initilizes framework
public function __construct($config = array())
{
$this->init($config);
}
public function init($config = array())
{
// always set init (can be used as singleton later?)
self::$init = microtime(TRUE);
// sensible defaults
$defaults = array
(
'index' => '/index.php',
'debug' => 'FALSE',
'paths' => array(
'views' => __DIR__,
'models' => __DIR__
)
);
// merge in configuration overriding defaults
self::$options = array_merge($defaults, $config);
// TODO: ack!!! forgot to update/retrieve settings from options
self::$index = self::options('index');
}
/**
* Builds route array
*
* Maps urls to classes or functions
*/
public function route($url, $callback)
{
// for dynamic urls
$url = preg_replace(array("@\:name@", "@\:id@"), array('([a-zA-Z\_\-]+)','([0-9]+)'), $url);
self::$routes[$url] = $callback;
}
// TODO: delete/add profile (echo set options)
public function _options()
{
print_r(self::$options);
}
//
public function run()
{
$request = self::uri();
foreach(self::$routes as $url => $callback)
{
preg_match("@^{$url}$@", $request, $matches);
if(isset($matches[0]) && count($matches[0] > 0))
{
if(is_callable($callback))
{
return call_user_func($callback);
}
else
{
// $class = get_class($this); //5 +
// $class = get_called_class(); //5.3
// if(is_callable("{$class}::{$callback}"))
if(is_callable(array($this, $callback)))
{
// return call_user_func("{$class}::{$callback}"); //called staticly 5.2.3+
// return call_user_func(array($class, $callback)); //called?
return $this->$callback();
}
else
{
echo 'PROBLEM';
}
// header("HTTP/1.1 404 Not Found");
exit('Function Not Found');
}
}
}
// header("HTTP/1.1 404 Not Found");
exit('Page Not Found');
}
//TODO: index.php required in url should make it either or? hmm htaccess issue
public function uri($raw = FALSE)
{
// cleans and strips query string
// $uri = str_replace($this->controller, '', preg_replace('/\?.+/', '', $_SERVER['REQUEST_URI']));
$uri = str_replace(self::$index, '', preg_replace('/\?.+/', '', $_SERVER['REQUEST_URI']));
return ($uri == '' OR $uri == '//') ? '/' : $uri;
}
function options($key = NULL, $value = NULL)
{
if($key === NULL)
{
return self::$options;
}
else
{
if($value === NULL)
{
return $option = isset(self::$options[$key]) ? self::$options[$key] : NULL ;
}
else
{
return $option = isset(self::$options[$key][$value]) ? self::$options[$key][$value] : NULL ;
}
}
}
/*****************************************************************
* State Management
*****************************************************************/
/**
* Cookie Managment
*
* set/get cookies by call signature
*/
public function cookie($name, $value = NULL, $expire = 0, $path = '', $domain = '', $secure = FALSE, $httponly=false)
{
if($value === NULL)
{ // read cookie
return $cookie = isset($_COOKIE[$name]) ? $_COOKIE[$name] : NULL ;
}
else
{ // create cookie
setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
}
}
/**
* Flash Notice
*
* Creates cookie expires on next request
*/
public function flash($name, $value)
{
self::cookie($name, $value, $expire = time()+1);
}
public function session($key, $value = NULL)
{
if($value === NULL)
{ // read session
return $session = isset($_SESSION[$name]) ? $_SESSION[$name] : NULL ;
}
else
{ // create session
$_SESSION[$key] = $value;
}
}
/*****************************************************************
* Helper Methods
*****************************************************************/
/**
* Return Uri Segment
*/
public function uri_segment($number)
{
$segments = explode('/', trim(self::$request, '/'));
return $segment = isset($segments[($number - 1)]) ? $segments[($number - 1)] : NULL;
}
public function input($key, $clean = TRUE)
{ // merge global input arrays
$input = array_merge($_GET, $_POST);
if($clean === TRUE)
{
return isset($input[$key]) ? self::xss($input[$key]) : NULL;
}
else
{
return isset($input[$key]) ? $input[$key] : NULL;
}
}
public function xss($value)
{
$value = stripslashes($value);
return htmlentities($value);
}
public function style($file = NULL)
{
if($file === NULL)
{
$style = "<style type='text/css'>\n";
$style .= "\t html,body,div,p{margin:0;padding:0;border:0;}";
// $style .= ".main{margin: 0 auto; width: 600px;}";
$style .= ".profile{background-color: #e3e3e3;font-weight: bold; border-bottom: 1px solid #404040;}";
$style .= ".dp20,.dp25,.dp33,.dp50,.dp100{float:left;display:inline;*margin-left:-0.04em;}";
$style .= ".dp20{width:20%;}.dp25{width:25%;}.dp33{width:33.33%;}.dp50{width:50%;}.dp100{width:100%;}";
$style .= ".clear{clear:both;}";
$style .= "\n</style>\n";
}
return $style;
}
public function view($template, $data = NULL)
{
if($data != NULL)
{
if(is_array($data))
{
extract($data);
}
}
$dir = self::options('paths','views');
include("{$dir}/{$template}");
}
/*****************************************************************
* Error Handling and Logging
*****************************************************************/
public function errors() {}
protected function has_error()
{
if(count(self::$errors) > 0)
{
return TRUE;
}
return FALSE;
}
public function log($message) {}
/*****************************************************************
* Profiling
*****************************************************************/
function explain()
{
$output = "\n<!-- Debugging Information -->\n";
$output .= Mite::style();
$output .= "<div class='main'>\n";
$output .= "<fieldset>\n\t<legend>Debugging Information</legend>\n";
// Routing
$output .= "<div class='dp100 profile'>\n";
$output .= "\t<div class='dp33'><b>PAGE REQUEST</b></div>\n";
$output .= "\t<div class='dp33'>" . self::uri() . "</div>\n";
$output .= "\t<div class='dp33'>&nbsp;</div>\n";
$output .= "</div>\n";
$output .= "<div class='dp100'>\n";
foreach(self::$routes as $route => $callback)
{
$output .= "\t<div class='dp33'>&nbsp;</div>\n"; // future request method
$output .= "\t<div class='dp33'>{$route}</div>\n";
$output .= "\t<div class='dp33'>";
$output .= is_object($callback) ? get_class($callback) : $callback;
$output .= "</div>\n";
}
$output .= "</div>\n";
// Request varibles
$output .= "<div class='dp100 profile'>GET DATA</div>\n";
foreach($_GET as $key => $value)
{
$output .= "\t<div class='dp50'>{$key}</div>\n";
$output .= "\t<div class='dp50'>{$value}</div>\n";
}
$output .= "<div class='dp100 profile'>POST DATA</div>\n";
foreach($_POST as $key => $value)
{
$output .= "\t<div class='dp50'>{$key}</div>\n";
$output .= "\t<div class='dp50'>{$value}</div>\n";
}
$output .= "<div class='dp100 profile'>COOKIE DATA</div>\n";
foreach($_COOKIE as $key => $value)
{
$output .= "\t<div class='dp50'>{$key}</div>\n";
$output .= "\t<div class='dp50'>{$value}</div>\n";
}
$output .= "<div class='dp100 profile'>SESSION DATA</div>\n";
if(isset($_SESSION))
{
foreach((array) $_SESSION as $key => $value)
{
$output .= "\t<div class='dp50'>{$key}</div>\n";
$output .= "\t<div class='dp50'>{$value}</div>\n";
}
}
// continued in __destruct()
return $output;
}
public function __destruct()
{
if(self::options('debug') === TRUE)
{
$output = self::explain();
// Memory
$output .= "<div class='dp100 profile'>MEMORY USAGE</div>\n";
$output .= "\t<div class='dp33'>&nbsp;</div>\n";
$output .= "\t<div class='dp33'>" . round(memory_get_usage()/1024,2) . " KBs</div>\n";
$output .= "\t<div class='dp33'>" . round(memory_get_peak_usage()/1024,2) . " KBs</div>\n";
// $output .= "</div>\n";
// Profiling
$output .= "<div class='dp100 profile'>PROFILING USAGE</div>\n";
$output .= "\t<div class='dp50'>&nbsp;</div>\n";
$output .= "\t<div class='dp50'>" . round(microtime(true) - self::$init, 4) . " Seconds</div>\n";
// $output .= "</div>\n";
$output .= "</fieldset>\n</div>\n";
$output .= "\n<!-- End of Debugging Information -->\n";
echo $output;
}
}
}
// Basic CRUD class, may remove, or make more query builder style
abstract class Mite_Model {
protected $_table; // table name
protected $_key; // primary key
private $db;
public function __construct($dsn, $username = NULL, $password = NULL)
{
$this->db = $this->connect($dsn, $username, $password);
}
public function connect($dsn, $username = NULL, $password = NULL)
{
// $dsn = "{$driver}:dbname={$database};host={$host}";
try
{
return new PDO($dsn, $username, $password);
}
catch (PDOException $e)
{
throw new Exception($e->getMessage());
}
}
// basic crud
public function insert($data)
{
$sql = "INSERT INTO {$this->_table} ";
$columns = array();
$values = array();
foreach($data as $column => $value)
{
$columns[] = $column;
$values[] = $value;
}
$sql .= '(' . implode(',', $columns) . ') ';
$sql .= 'VALUES(' . implode(',', $values) . ')';
return $this->db->exec($sql);
}
public function update($data)
{
$sql = "UPDATE {$this->_table} SET ";
$updates = array();
foreach($data as $column => $value)
{
$updates[] = "{$column}='{$value}'";
}
$sql .= implode(',', $updates);
$sql .= "WHERE {$this->_key}='{$value[$this->_key]}'";
return $this->db->exec($sql);
}
public function delete($data)
{
$sql = "DELETE FROM {$this->_table} ";
$deletes = array();
foreach($data as $column => $value)
{
$deletes[] = "{$column}='{$value}'";
}
$sql .= implode(',', $deletes);
$sql .= "WHERE {$column}='{$value}'";
return $this->db->exec($sql);
}
public function find($value)
{
$sql = "SELECT * FROM {$this->_table} WHERE {$this->_key} = '{$value}'";
return $this->db->query($sql);
}
// raw sql
public function query($sql)
{
return $this->db->query($sql);
}
public function last_id()
{
try
{
return $this->db->lastInsertId();
}
catch(PDOException $e)
{
return NULL;
}
}
}

12
tests/MiteTest.php Normal file
View File

@ -0,0 +1,12 @@
<?php
use PHPUnit\Framework\TestCase;
class MiteTest extends TestCase {
public function testMiteExist() {
$mite = new Mite();
$this->assertInstanceOf('Mite', $mite);
}
}