Browse Source

Merge branch 'master' of https://github.com/TechEmpower/FrameworkBenchmarks

Masahiro Nagano 11 years ago
parent
commit
b1eecc49ec
100 changed files with 6928 additions and 139 deletions
  1. 218 66
      README.md
  2. 4 2
      beego/benchmark_config
  3. 55 0
      beego/src/hello/hello.go
  4. 10 5
      config/benchmark_profile
  5. 5 5
      config/postgresql.conf
  6. 37 0
      config/requirements-py3.txt
  7. 36 0
      config/requirements-pypy.txt
  8. 38 0
      config/requirements.txt
  9. 9 4
      cpoll_cppsp/www/db
  10. 9 4
      cpoll_cppsp/www/db_pg_async
  11. 9 4
      cpoll_cppsp/www/db_pg_threadpool
  12. 28 16
      flask/app.py
  13. 7 7
      flask/benchmark_config
  14. 1 0
      flask/requirements.txt
  15. 28 14
      flask/setup.py
  16. 24 9
      flask/setup_py3.py
  17. 0 1
      flask/setup_pypy.py
  18. 1 1
      grails/README.md
  19. 1 1
      grails/hello/application.properties
  20. 18 0
      php-pimf/.editorconfig
  21. 1 0
      php-pimf/.gitattributes
  22. 3 0
      php-pimf/.gitmodules
  23. 20 0
      php-pimf/.htaccess
  24. 53 0
      php-pimf/README.md
  25. 0 0
      php-pimf/__init__.py
  26. 109 0
      php-pimf/app/Vanilla/Controller/Hello.php
  27. 19 0
      php-pimf/app/Vanilla/DataMapper/Fortune.php
  28. 37 0
      php-pimf/app/Vanilla/DataMapper/World.php
  29. 60 0
      php-pimf/app/Vanilla/_database/create.sql
  30. 13 0
      php-pimf/app/Vanilla/_templates/table.phtml
  31. 4 0
      php-pimf/app/Vanilla/_templates/tr.phtml
  32. 4 0
      php-pimf/app/Vanilla/routes.php
  33. 37 0
      php-pimf/app/autoload.app.php
  34. 18 0
      php-pimf/app/bootstrap.app.php
  35. 167 0
      php-pimf/app/config.app.php
  36. 47 0
      php-pimf/benchmark_config
  37. 5 0
      php-pimf/composer.json
  38. 133 0
      php-pimf/deploy/nginx.conf
  39. 127 0
      php-pimf/deploy/nginx_raw.conf
  40. 9 0
      php-pimf/deploy/php-pimf
  41. 0 0
      php-pimf/favicon.ico
  42. 13 0
      php-pimf/index.php
  43. 25 0
      php-pimf/license.txt
  44. 4 0
      php-pimf/pimf
  45. 18 0
      php-pimf/pimf-framework/.editorconfig
  46. 1 0
      php-pimf/pimf-framework/.gitattributes
  47. 31 0
      php-pimf/pimf-framework/.gitignore
  48. 68 0
      php-pimf/pimf-framework/.scrutinizer.yml
  49. 20 0
      php-pimf/pimf-framework/.travis.yml
  50. 52 0
      php-pimf/pimf-framework/README.md
  51. 106 0
      php-pimf/pimf-framework/autoload.core.php
  52. 25 0
      php-pimf/pimf-framework/composer.json
  53. 222 0
      php-pimf/pimf-framework/core/Pimf/Application.php
  54. 115 0
      php-pimf/pimf-framework/core/Pimf/Cache.php
  55. 110 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Apc.php
  56. 240 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Dba.php
  57. 109 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/File.php
  58. 96 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Memcached.php
  59. 85 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Memory.php
  60. 136 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Pdo.php
  61. 98 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Redis.php
  62. 137 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Storage.php
  63. 89 0
      php-pimf/pimf-framework/core/Pimf/Cache/Storages/Wincache.php
  64. 135 0
      php-pimf/pimf-framework/core/Pimf/Cli.php
  65. 81 0
      php-pimf/pimf-framework/core/Pimf/Cli/Color.php
  66. 103 0
      php-pimf/pimf-framework/core/Pimf/Cli/Std.php
  67. 26 0
      php-pimf/pimf-framework/core/Pimf/Contracts/Arrayable.php
  68. 28 0
      php-pimf/pimf-framework/core/Pimf/Contracts/Cleanable.php
  69. 28 0
      php-pimf/pimf-framework/core/Pimf/Contracts/Jsonable.php
  70. 26 0
      php-pimf/pimf-framework/core/Pimf/Contracts/MessageProvider.php
  71. 26 0
      php-pimf/pimf-framework/core/Pimf/Contracts/Renderable.php
  72. 26 0
      php-pimf/pimf-framework/core/Pimf/Contracts/Reunitable.php
  73. 110 0
      php-pimf/pimf-framework/core/Pimf/Controller/Base.php
  74. 139 0
      php-pimf/pimf-framework/core/Pimf/Controller/Core.php
  75. 22 0
      php-pimf/pimf-framework/core/Pimf/Controller/Exception.php
  76. 199 0
      php-pimf/pimf-framework/core/Pimf/Cookie.php
  77. 71 0
      php-pimf/pimf-framework/core/Pimf/DataMapper/Base.php
  78. 82 0
      php-pimf/pimf-framework/core/Pimf/Database.php
  79. 103 0
      php-pimf/pimf-framework/core/Pimf/EntityManager.php
  80. 231 0
      php-pimf/pimf-framework/core/Pimf/Environment.php
  81. 134 0
      php-pimf/pimf-framework/core/Pimf/Error.php
  82. 176 0
      php-pimf/pimf-framework/core/Pimf/Event.php
  83. 234 0
      php-pimf/pimf-framework/core/Pimf/Logger.php
  84. 110 0
      php-pimf/pimf-framework/core/Pimf/Memcached.php
  85. 55 0
      php-pimf/pimf-framework/core/Pimf/Model/AsArray.php
  86. 79 0
      php-pimf/pimf-framework/core/Pimf/Param.php
  87. 54 0
      php-pimf/pimf-framework/core/Pimf/Pdo/Connector.php
  88. 49 0
      php-pimf/pimf-framework/core/Pimf/Pdo/Factory.php
  89. 45 0
      php-pimf/pimf-framework/core/Pimf/Pdo/Mysql.php
  90. 53 0
      php-pimf/pimf-framework/core/Pimf/Pdo/Postgre.php
  91. 36 0
      php-pimf/pimf-framework/core/Pimf/Pdo/Sqlite.php
  92. 46 0
      php-pimf/pimf-framework/core/Pimf/Pdo/Sqlserver.php
  93. 322 0
      php-pimf/pimf-framework/core/Pimf/Redis.php
  94. 107 0
      php-pimf/pimf-framework/core/Pimf/Registry.php
  95. 133 0
      php-pimf/pimf-framework/core/Pimf/Request.php
  96. 106 0
      php-pimf/pimf-framework/core/Pimf/Resolver.php
  97. 20 0
      php-pimf/pimf-framework/core/Pimf/Resolver/Exception.php
  98. 263 0
      php-pimf/pimf-framework/core/Pimf/Response.php
  99. 174 0
      php-pimf/pimf-framework/core/Pimf/Route.php
  100. 92 0
      php-pimf/pimf-framework/core/Pimf/Route/Target.php

+ 218 - 66
README.md

@@ -6,11 +6,163 @@ Read more and see the results of our tests on Amazon EC2 and physical hardware a
 
 
 Join in the conversation at our Google Group: https://groups.google.com/forum/?fromgroups=#!forum/framework-benchmarks
 Join in the conversation at our Google Group: https://groups.google.com/forum/?fromgroups=#!forum/framework-benchmarks
 
 
-## Running the test suite
+### Prerequisites
+
+Before starting setup, all the required hosts must be provisioned, with the respective operating system and required software installed, and with connectivity for remote management (SSH on Linux, RDP and WinRM on Windows).
+
+Refer to [Benchmark Suite Deployment README file](../deployment/README.md) for the provisioning procedures documentation.
+
+### App, load, and database servers
+
+**NOTE:** If testing a pull request or doing development, it is usually adequate to only use one computer. In that case, your server, client, and database IPs will be 127.0.0.1
+
+#### Installing the Framework Benchmark App Server
+
+* Install [Ubuntu 14.04](http://www.ubuntu.com/download/server) with username `tfb`. Ensure that OpenSSH is selected when you install. If not, run the following command
+```bash
+$ sudo apt-get install openssh-server
+```
+* If Ubuntu is already installed, run the following command and follow the prompts.
+```bash
+$ sudo adduser tfb
+```
+* Log in as `tfb`
+* Fully update **NOTE**: If you update the kernel (linux-firmware), it is generally a good idea to reboot aftewards.
+```bash
+$ sudo apt-get update && sudo apt-get upgrade
+```
+* Run the command: `sudo visudo`
+* Change line 20 in from `%sudo   ALL=(ALL:ALL) ALL` to `%sudo   ALL=NOPASSWD: ALL`
+* Run the following **(Don't enter a password, just hit enter when the prompt pops up)**. **NOTE** This is still necessary if the client and database are on the same computer as the server
+```bash
+$ ssh-keygen
+$ ssh-copy-id <database ip>
+$ ssh-copy-id <client ip>
+```
+* Install git and clone the Framework Benchmarks repository
+```bash
+$ sudo apt-get install git
+$ cd ~
+$ git clone https://github.com/TechEmpower/FrameworkBenchmarks.git
+$ cd FrameworkBenchmarks
+```
+* Install the server software. This will take a long time
+```bash
+$ nohup python toolset/run-tests.py -s <server hostname/ip> -c <client hostname/ip> -u tfb --install-software --install server --list-tests &
+```
+* If you want to view the process of installing, do the following. The session can be interrupted easily so no need to worry about keeping a connection.
+```bash
+$ tail -f nohup.out
+```
+* Reboot when the install is done
+* Edit your ~/.bashrc file to change the following
+ * Change `TFB_SERVER_HOST=<ip address>` to the server's IP address
+ * Change `TFB_CLIENT_HOST=<ip address>` to the client's ip address
+ * Change `TFB_DATABASE_HOST=<ip address>` to the database's ip address.
+ * Change `TFB_CLIENT_IDENTITY_FILE=<path>` to the id file you specified when you ran ssh-keygen (probably /home/tfb/.ssh/id_rsa if you don't know what it is)
+ * Run the command `source ~/.bashrc`
+* If you are setting up any other servers, do so before proceeding.
+* Run the following commands
+```bash
+cd ~/FrameworkBenchmarks
+source ~/.bash_profile
+# For your first time through the tests, set the ulimit for open files
+ulimit -n 8192
+# Most software is installed automatically by the script, but running the mongo command below from
+# the install script was causing some errors. For now this needs to be run manually.
+cd installs && curl -sS https://getcomposer.org/installer | php -- --install-dir=bin
+cd ..
+sudo apt-get remove --purge openjdk-6-jre openjdk-6-jre-headless
+# Change database-private-ip to the database ip
+mongo --host database-private-ip < config/create.js
+```
+* Before running the tests, do the following
+```bash
+$ source ~/.bashrc
+```
+
+---
+
+#### Installing the Framework Benchmark Database Server
+
+* Install [Ubuntu 14.04](http://www.ubuntu.com/download/server) with username `tfb`
+* Log in as `tfb`
+* Fully update **NOTE**: If you update the kernel (linux-firmware), it is generally a good idea to reboot aftewards.
+```bash
+$ sudo apt-get update && sudo apt-get upgrade
+```
+* Run the command: `sudo visudo`
+* Change line 20 in from `%sudo   ALL=(ALL:ALL) ALL` to `%sudo   ALL=NOPASSWD: ALL`
+* On the app server, run the following from the FrameworkBenchmark directory (this should only take a small amount of time, several minutes or so):
+```bash
+$ toolset/run-tests.py --install-software --install database --list-tests
+```
+
+---
+
+#### Installing the Framework Benchmark Load Server
+
+* Install [Ubuntu 14.04](http://www.ubuntu.com/download/server) with username `tfb`
+* Log in as `tfb`
+* Fully update **NOTE**: If you update the kernel (linux-firmware), it is generally a good idea to reboot aftewards.
+```bash
+$ sudo apt-get update && sudo apt-get upgrade
+```
+* Run the command: `sudo visudo`
+* Change line 20 in from `%sudo   ALL=(ALL:ALL) ALL` to `%sudo   ALL=NOPASSWD: ALL`
+* On the app server, run the following from the FrameworkBenchmark directory (this should only take a small amount of time, several minutes or so):
+```bash
+$ toolset/run-tests.py --install-software --install client --list-tests
+```
+
+You can validate that the setup worked by running a smoke test like this:
+
+    toolset/run-tests.py --max-threads 1 --name smoketest --test servlet-raw --type all -m verify
+
+This should run the verification step for a single framework.
+
+---
+
+#### Windows server setup
+
+* Connect to the Windows server via Remote Desktop.
+* Copy `installer-bootstrap.ps1` from "toolset/setup/windows" to the server (use CTRL-C and CTRL-V).
+* Copy your Linux client private key too.
+* Right click on the installer script and select `Run with PowerShell`.
+* Press Enter to confirm.
+* It will install git and then launch `installer.ps1` from the repository, which will install everything else.
+* The installation takes about 20 minutes.
+* Then you have a working console: try `python`, `git`, `ssh`, `curl`, `node` etc. and verify that everything works + PowerShell goodies.
+
+The client/database machine is still assumed to be a Linux box. You can install just the client software via
+
+    python toolset\run-tests.py -s server-private-ip -c client-private-ip -i "C:\Users\Administrator\Desktop\client.key" --install-software --install client --list-tests
+
+but this step is not required if you already installed the Linux server and client as described above.
+
+Now you can run tests:
+
+    python toolset\run-tests.py -s server-private-ip -c client-private-ip -i "C:\Users\Administrator\Desktop\client.key" --max-threads 2 --duration 30 --sleep 5 --name win --test aspnet --type all
+
+---
+
+#### SQL Server setup
+
+* Connect to the SQL Server host via Remote Desktop.
+* Run a `Command Prompt` as Administrator.
+* Enter this command:
+
+        powershell -ExecutionPolicy Bypass -Command "iex (New-Object Net.WebClient).DownloadString('https://raw.github.com/TechEmpower/FrameworkBenchmarks/master/toolset/setup/sqlserver/setup-sqlserver-bootstrap.ps1')"
+
+* This will configure SQL Server, the Windows Firewall, and populate the database.
+
+Now, when running `run-tests.py`, just add `-d <ip of SQL Server instance>`. This works for the (Windows Server-based) `aspnet-sqlserver-raw` and `aspnet-sqlserver-entityframework` tests.
+
+---
 
 
-We ran our tests using two dedicated i7 2600k machines as well as two EC2 m1.large instances.
+## Running the test suite
 
 
-On the [Benchmark Tools README file](toolset/README.md) you will find tools and instructions to replicate our tests using EC2, Windows Azure or your own dedicated machines.
+We ran our tests using three dedicated i7 2600k machines, three EC2 m1.large instances, and three servers from Peak Hosting
 
 
 ## Updating Tests
 ## Updating Tests
 
 
@@ -52,52 +204,52 @@ This file should exist at the root of the test directory.
 
 
 Here is the basic structure of benchmark_config, using the Compojure framework as an example.  Compojure has two test *permutations*, which are identified as the "tests" list in the JSON structure below.
 Here is the basic structure of benchmark_config, using the Compojure framework as an example.  Compojure has two test *permutations*, which are identified as the "tests" list in the JSON structure below.
 
 
-	{
-	  "framework": "compojure",
-	  "tests": [{
-	    "default": {
-	      "setup_file": "setup",
-	      "json_url": "/compojure/json",
-	      "db_url": "/compojure/db/1",
-	      "query_url": "/compojure/db/",
-	      "fortune_url": "/compojure/fortune-hiccup",
-	      "plaintext_url": "/compojure/plaintext",
-	      "port": 8080,
-	      "approach": "Realistic",
-	      "classification": "Micro",
-	      "database": "MySQL",
-	      "framework": "compojure",
-	      "language": "Clojure",
-	      "orm": "Micro",
-	      "platform": "Servlet",
-	      "webserver": "Resin",
-	      "os": "Linux",
-	      "database_os": "Linux",
-	      "display_name": "compojure",
-	      "notes": "",
-	      "versus": "servlet"
-	    },
-	    "raw": {
-	      "setup_file": "setup",
-	      "db_url": "/compojure/dbraw/1",
-	      "query_url": "/compojure/dbraw/",
-	      "port": 8080,
-	      "approach": "Realistic",
-	      "classification": "Micro",
-	      "database": "MySQL",
-	      "framework": "compojure",
-	      "language": "Clojure",
-	      "orm": "Raw",
-	      "platform": "Servlet",
-	      "webserver": "Resin",
-	      "os": "Linux",
-	      "database_os": "Linux",
-	      "display_name": "compojure-raw",
-	      "notes": "",
-	      "versus": "servlet"
-	    }
-	  }]
-	}
+    {
+      "framework": "compojure",
+      "tests": [{
+        "default": {
+          "setup_file": "setup",
+          "json_url": "/compojure/json",
+          "db_url": "/compojure/db/1",
+          "query_url": "/compojure/db/",
+          "fortune_url": "/compojure/fortune-hiccup",
+          "plaintext_url": "/compojure/plaintext",
+          "port": 8080,
+          "approach": "Realistic",
+          "classification": "Micro",
+          "database": "MySQL",
+          "framework": "compojure",
+          "language": "Clojure",
+          "orm": "Micro",
+          "platform": "Servlet",
+          "webserver": "Resin",
+          "os": "Linux",
+          "database_os": "Linux",
+          "display_name": "compojure",
+          "notes": "",
+          "versus": "servlet"
+        },
+        "raw": {
+          "setup_file": "setup",
+          "db_url": "/compojure/dbraw/1",
+          "query_url": "/compojure/dbraw/",
+          "port": 8080,
+          "approach": "Realistic",
+          "classification": "Micro",
+          "database": "MySQL",
+          "framework": "compojure",
+          "language": "Clojure",
+          "orm": "Raw",
+          "platform": "Servlet",
+          "webserver": "Resin",
+          "os": "Linux",
+          "database_os": "Linux",
+          "display_name": "compojure-raw",
+          "notes": "",
+          "versus": "servlet"
+        }
+      }]
+    }
 
 
 * framework: Specifies the framework name.
 * framework: Specifies the framework name.
 * tests: An list of tests that can be run for this framework. In many cases, this contains a single element for the "default" test, but additional tests can be specified.  Each test name must be unique when concatenated with the framework name.
 * tests: An list of tests that can be run for this framework. In many cases, this contains a single element for the "default" test, but additional tests can be specified.  Each test name must be unique when concatenated with the framework name.
@@ -159,17 +311,17 @@ Using `localhost` in the raw configuration file is not a requirement as long as
 
 
 Here is an example of Wicket's setup file.
 Here is an example of Wicket's setup file.
 
 
-	import subprocess
-	import sys
-	import setup_util
+    import subprocess
+    import sys
+    import setup_util
 
 
-	##################################################
-	# start(args, logfile, errfile)
-	#
-	# Starts the server for Wicket
-	# returns 0 if everything completes, 1 otherwise
-	##################################################
-	def start(args, logfile, errfile):
+    ##################################################
+    # start(args, logfile, errfile)
+    #
+    # Starts the server for Wicket
+    # returns 0 if everything completes, 1 otherwise
+    ##################################################
+    def start(args, logfile, errfile):
 
 
     # setting the database url
     # setting the database url
     setup_util.replace_text("wicket/src/main/webapp/WEB-INF/resin-web.xml", "mysql:\/\/.*:3306", "mysql://" + args.database_host + ":3306")
     setup_util.replace_text("wicket/src/main/webapp/WEB-INF/resin-web.xml", "mysql:\/\/.*:3306", "mysql://" + args.database_host + ":3306")
@@ -187,13 +339,13 @@ Here is an example of Wicket's setup file.
     except subprocess.CalledProcessError:
     except subprocess.CalledProcessError:
       return 1
       return 1
 
 
-	##################################################
-	# stop(logfile, errfile)
-	#
-	# Stops the server for Wicket
-	# returns 0 if everything completes, 1 otherwise
-	##################################################
-	def stop(logfile):
+    ##################################################
+    # stop(logfile, errfile)
+    #
+    # Stops the server for Wicket
+    # returns 0 if everything completes, 1 otherwise
+    ##################################################
+    def stop(logfile):
     try:
     try:
       subprocess.check_call("$RESIN_HOME/bin/resinctl shutdown", shell=True, stderr=errfile, stdout=logfile)
       subprocess.check_call("$RESIN_HOME/bin/resinctl shutdown", shell=True, stderr=errfile, stdout=logfile)
       return 0
       return 0

+ 4 - 2
beego/benchmark_config

@@ -4,13 +4,15 @@
     "default": {
     "default": {
       "setup_file": "setup",
       "setup_file": "setup",
       "json_url": "/json",
       "json_url": "/json",
+      "db_url": "/db",
+      "plaintext_url": "/plaintext",
       "port": 8080,
       "port": 8080,
       "approach": "Realistic",
       "approach": "Realistic",
       "classification": "Micro",
       "classification": "Micro",
-      "database": "None",
+      "database": "MySQL",
       "framework": "beego",
       "framework": "beego",
       "language": "Go",
       "language": "Go",
-      "orm": "Raw",
+      "orm": "beego ORM",
       "platform": "Go",
       "platform": "Go",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",

+ 55 - 0
beego/src/hello/hello.go

@@ -1,14 +1,38 @@
 package main
 package main
 
 
 import (
 import (
+	"log"
+	"math/rand"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego"
+	"github.com/astaxie/beego/orm"
+
+	_ "github.com/go-sql-driver/mysql"
 	//"runtime"
 	//"runtime"
 )
 )
 
 
+const (
+	// Database
+	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world"
+	worldRowCount      = 10000
+	macIdleConnection  = 30
+	maxConnectionCount = 256
+
+	helloWorldString = "Hello, World!"
+)
+
+var (
+	helloWorldBytes = []byte(helloWorldString)
+)
+
 type MessageStruct struct {
 type MessageStruct struct {
 	Message string `json:"message"`
 	Message string `json:"message"`
 }
 }
 
 
+type World struct {
+	Id           uint16 `orm:"pk" json:"id"`
+	RandomNumber uint16 `orm:"column(randomNumber)" json:"randomNumber"`
+}
+
 type JsonController struct {
 type JsonController struct {
 	beego.Controller
 	beego.Controller
 }
 }
@@ -19,10 +43,41 @@ func (this *JsonController) Get() {
 	this.ServeJson()
 	this.ServeJson()
 }
 }
 
 
+type PlaintextController struct {
+	beego.Controller
+}
+
+func (this *PlaintextController) Get() {
+	this.Ctx.Output.Header("Content-Type", "text/plain")
+	this.Ctx.Output.Body(helloWorldBytes)
+}
+
+type DBController struct {
+	beego.Controller
+}
+
+func (this *DBController) Get() {
+	o := orm.NewOrm()
+	w := World{Id: uint16(rand.Intn(worldRowCount) + 1)}
+	err := o.Read(&w)
+	if err != nil {
+		log.Fatalf("Error read world row: %s", err.Error())
+	}
+	this.Data["json"] = &w
+	this.ServeJson()
+}
+
 func main() {
 func main() {
 	//don't need this set, beego default set it
 	//don't need this set, beego default set it
 	//runtime.GOMAXPROCS(runtime.NumCPU())
 	//runtime.GOMAXPROCS(runtime.NumCPU())
 	beego.RunMode = "prod"
 	beego.RunMode = "prod"
 	beego.Router("/json", &JsonController{})
 	beego.Router("/json", &JsonController{})
+	beego.Router("/db", &DBController{})
+	beego.Router("/plaintext", &PlaintextController{})
 	beego.Run()
 	beego.Run()
 }
 }
+
+func init() {
+	orm.RegisterModel(new(World))
+	orm.RegisterDataBase("default", "mysql", connectionString, macIdleConnection, maxConnectionCount)
+}

+ 10 - 5
config/benchmark_profile

@@ -1,7 +1,9 @@
+# Start Benchmark profile
+
 export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64
 export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64
 export RESIN_HOME=~/FrameworkBenchmarks/installs/resin-4.0.36
 export RESIN_HOME=~/FrameworkBenchmarks/installs/resin-4.0.36
-export GRAILS_HOME=~/FrameworkBenchmarks/installs/grails-2.4.1
-export VERTX_HOME=~/FrameworkBenchmarks/installs/vert.x-2.1RC3
+export GRAILS_HOME=~/FrameworkBenchmarks/installs/grails-2.4.2
+export VERTX_HOME=~/FrameworkBenchmarks/installs/vert.x-2.1.1
 export GOROOT=~/FrameworkBenchmarks/installs/go
 export GOROOT=~/FrameworkBenchmarks/installs/go
 export GOPATH=~/FrameworkBenchmarks/go:~/FrameworkBenchmarks/webgo:~/FrameworkBenchmarks/revel
 export GOPATH=~/FrameworkBenchmarks/go:~/FrameworkBenchmarks/webgo:~/FrameworkBenchmarks/revel
 export TOMCAT_HOME=~/FrameworkBenchmarks/installs/apache-tomcat-7.0.35
 export TOMCAT_HOME=~/FrameworkBenchmarks/installs/apache-tomcat-7.0.35
@@ -9,7 +11,7 @@ export NODE_HOME=~/FrameworkBenchmarks/installs/node-v0.10.8-linux-x64
 export PLAY_HOME=~/FrameworkBenchmarks/installs/play-2.2.0
 export PLAY_HOME=~/FrameworkBenchmarks/installs/play-2.2.0
 export PLAY1_HOME=~/FrameworkBenchmarks/installs/play-1.2.5
 export PLAY1_HOME=~/FrameworkBenchmarks/installs/play-1.2.5
 export MAVEN_HOME=~/FrameworkBenchmarks/installs/apache-maven-3.0.5
 export MAVEN_HOME=~/FrameworkBenchmarks/installs/apache-maven-3.0.5
-export PERL_HOME=/opt/ActivePerl-5.16
+export PERL_HOME=~/FrameworkBenchmarks/installs/perl-5.18
 export DART_HOME=~/FrameworkBenchmarks/installs/dart-sdk
 export DART_HOME=~/FrameworkBenchmarks/installs/dart-sdk
 export PYTHON_HOME=~/FrameworkBenchmarks/installs/python-2.7.5
 export PYTHON_HOME=~/FrameworkBenchmarks/installs/python-2.7.5
 export RACKET_HOME=~/FrameworkBenchmarks/installs/racket-5.3.6
 export RACKET_HOME=~/FrameworkBenchmarks/installs/racket-5.3.6
@@ -17,7 +19,7 @@ export NIMROD_HOME=~/FrameworkBenchmarks/installs/nimrod
 export NGINX_HOME=/usr/local/nginx
 export NGINX_HOME=/usr/local/nginx
 export ELIXIR_HOME=~/FrameworkBenchmarks/installs/elixir-0.13.3
 export ELIXIR_HOME=~/FrameworkBenchmarks/installs/elixir-0.13.3
 
 
-export PATH="$PYTHON_HOME/bin:$PATH$JAVA_HOME/bin:$GRAILS_HOME/bin:$PLAY_HOME:$PLAY1_HOME:$VERTX_HOME/bin:$GOROOT/bin:$NODE_HOME/bin:$HOME/FrameworkBenchmarks/installs/bin:$MAVEN_HOME/bin:$PERL_HOME/bin:$PERL_HOME/site/bin:$DART_HOME/bin:$RACKET_HOME/bin:$NIMROD_HOME/bin:$NGINX_HOME/sbin:$ELIXIR_HOME/bin:$PATH"
+export PATH="$PYTHON_HOME/bin:$JAVA_HOME/bin:$GRAILS_HOME/bin:$PLAY_HOME:$PLAY1_HOME:$VERTX_HOME/bin:$GOROOT/bin:$NODE_HOME/bin:$HOME/FrameworkBenchmarks/installs/bin:$MAVEN_HOME/bin:$PERL_HOME/bin:$DART_HOME/bin:$RACKET_HOME/bin:$NIMROD_HOME/bin:$NGINX_HOME/sbin:$ELIXIR_HOME/bin:$PATH"
 
 
 export LD_LIBRARY_PATH='$LD_LIBRARY_PATH:/usr/local/apr/lib'
 export LD_LIBRARY_PATH='$LD_LIBRARY_PATH:/usr/local/apr/lib'
 
 
@@ -27,4 +29,7 @@ export TFB_CLIENT_USER='tfb'
 export TFB_CLIENT_IDENTITY_FILE='/home/tfb/.ssh/id_rsa'
 export TFB_CLIENT_IDENTITY_FILE='/home/tfb/.ssh/id_rsa'
 export TFB_DATABASE_HOST='localhost'
 export TFB_DATABASE_HOST='localhost'
 
 
-. ~/.rvm/scripts/'rvm'
+[ -e ~/.rvm ] && . ~/.rvm/scripts/'rvm'
+export LC_ALL='en_US.UTF-8'
+export NUMCPUS=`grep -c '^processor' /proc/cpuinfo`
+export MAKEFLAGS="-j $NUMCPUS -l $NUMCPUS"

+ 5 - 5
config/postgresql.conf

@@ -40,13 +40,13 @@
 
 
 data_directory = '/ssd/postgresql'		# use data in another directory
 data_directory = '/ssd/postgresql'		# use data in another directory
 					# (change requires restart)
 					# (change requires restart)
-hba_file = '/etc/postgresql/9.1/main/pg_hba.conf'	# host-based authentication file
+hba_file = '/etc/postgresql/9.3/main/pg_hba.conf'	# host-based authentication file
 					# (change requires restart)
 					# (change requires restart)
-ident_file = '/etc/postgresql/9.1/main/pg_ident.conf'	# ident configuration file
+ident_file = '/etc/postgresql/9.3/main/pg_ident.conf'	# ident configuration file
 					# (change requires restart)
 					# (change requires restart)
 
 
 # If external_pid_file is not explicitly set, no extra PID file is written.
 # If external_pid_file is not explicitly set, no extra PID file is written.
-external_pid_file = '/var/run/postgresql/9.1-main.pid'		# write an extra PID file
+external_pid_file = '/var/run/postgresql/9.3-main.pid'		# write an extra PID file
 					# (change requires restart)
 					# (change requires restart)
 
 
 
 
@@ -65,7 +65,7 @@ max_connections = 2000			# (change requires restart)
 # Note:  Increasing max_connections costs ~400 bytes of shared memory per
 # Note:  Increasing max_connections costs ~400 bytes of shared memory per
 # connection slot, plus lock space (see max_locks_per_transaction).
 # connection slot, plus lock space (see max_locks_per_transaction).
 #superuser_reserved_connections = 3	# (change requires restart)
 #superuser_reserved_connections = 3	# (change requires restart)
-unix_socket_directory = '/var/run/postgresql'		# (change requires restart)
+unix_socket_directories = '/var/run/postgresql'		# (change requires restart)
 #unix_socket_group = ''			# (change requires restart)
 #unix_socket_group = ''			# (change requires restart)
 #unix_socket_permissions = 0777		# begin with 0 to use octal notation
 #unix_socket_permissions = 0777		# begin with 0 to use octal notation
 					# (change requires restart)
 					# (change requires restart)
@@ -77,7 +77,7 @@ unix_socket_directory = '/var/run/postgresql'		# (change requires restart)
 # - Security and Authentication -
 # - Security and Authentication -
 
 
 #authentication_timeout = 1min		# 1s-600s
 #authentication_timeout = 1min		# 1s-600s
-ssl = true				# (change requires restart)
+ssl = false                             # (change requires restart)
 #ssl_ciphers = 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH'	# allowed SSL ciphers
 #ssl_ciphers = 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH'	# allowed SSL ciphers
 					# (change requires restart)
 					# (change requires restart)
 #ssl_renegotiation_limit = 512MB	# amount of data between renegotiations
 #ssl_renegotiation_limit = 512MB	# amount of data between renegotiations

+ 37 - 0
config/requirements-py3.txt

@@ -0,0 +1,37 @@
+mysqlclient==1.3.1
+PyMySQL==0.6.2
+psycopg2==2.5.3
+
+simplejson==3.5.2
+ujson==1.33
+# gevent==1.0.1 # gevent doesn't support Python 3 yet.
+# uwsgi is released too often to stick on single version.
+uwsgi
+
+gunicorn==19.0
+meinheld==0.5.6
+
+# Tornado
+tornado==3.2.2
+motor==0.3
+# pymongo is installed via motor dependency
+
+# Django
+Django==1.6.5
+
+# Flask
+Jinja2==2.7.3
+Werkzeug==0.9.6
+flask==0.10.1
+SQLAlchemy==0.9.4
+Flask-SQLAlchemy==1.0
+
+# Bottle
+bottle==0.12.7
+bottle-sqlalchemy==0.4.1
+
+# Falcon
+Cython==0.20.1
+falcon==0.1.8
+
+Momoko==1.1.3

+ 36 - 0
config/requirements-pypy.txt

@@ -0,0 +1,36 @@
+mysqlclient==1.3.1
+PyMySQL==0.6.2
+# TODO: Try psycopg2cffi
+#psycopg2==2.5.2
+
+#simplejson==3.4.1
+#ujson==1.33
+#gevent==1.0.1
+# uwsgi is released too often to stick on single version.
+uwsgi
+
+gunicorn==19.0
+meinheld==0.5.6
+
+# Tornado
+tornado==3.2.2
+motor==0.3
+# pymongo is installed via motor dependency
+
+# Django
+Django==1.6.5
+
+# Flask
+Jinja2==2.7.3
+Werkzeug==0.9.6
+flask==0.10.1
+SQLAlchemy==0.9.4
+Flask-SQLAlchemy==1.0
+
+# Bottle
+bottle==0.12.7
+bottle-sqlalchemy==0.4.1
+
+# Falcon
+# Cython==0.20.1
+falcon==0.1.8

+ 38 - 0
config/requirements.txt

@@ -0,0 +1,38 @@
+circus
+mysqlclient==1.3.1
+PyMySQL==0.6.2
+psycopg2==2.5.3
+
+simplejson==3.5.2
+ujson==1.33
+gevent==1.0.1
+# uwsgi is released too often to stick on single version.
+uwsgi
+
+gunicorn==19.0
+meinheld==0.5.6
+
+# Tornado
+tornado==3.2.2
+motor==0.3
+# pymongo is installed via motor dependency
+
+# Django
+Django==1.6.5
+
+# Flask
+Jinja2==2.7.3
+Werkzeug==0.9.6
+flask==0.10.1
+SQLAlchemy==0.9.4
+Flask-SQLAlchemy==1.0
+
+# Bottle
+bottle==0.12.7
+bottle-sqlalchemy==0.4.1
+
+# Falcon
+Cython==0.20.1
+falcon==0.1.8
+
+Momoko==1.1.3

+ 9 - 4
cpoll_cppsp/www/db

@@ -128,13 +128,18 @@ void efdCB(eventfd_t efdVal) {
 void waitCB(eventfd_t efdVal) {
 void waitCB(eventfd_t efdVal) {
 	this->doInit();
 	this->doInit();
 }
 }
-
-%>[<%
+%><%
+if (queries>1) {
+	%>[<%
+}
 for (int i=0;i<queries;i++){
 for (int i=0;i<queries;i++){
 	if(i>0) output.write(',');
 	if(i>0) output.write(',');
 	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
 	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
 }
 }
-
+if (queries>1) {
+	%>]<%
+}
+%><%
 response->headers["Content-Type"]="application/json";
 response->headers["Content-Type"]="application/json";
 response->headers["Server"]="cppsp/0.2";
 response->headers["Server"]="cppsp/0.2";
-%>]
+%>

+ 9 - 4
cpoll_cppsp/www/db_pg_async

@@ -107,13 +107,18 @@ void evtIn(int) {
 	PQclear(res);
 	PQclear(res);
 	beginGetItems();
 	beginGetItems();
 }
 }
-
-%>[<%
+%><%
+if(queries>1) {
+	%>[<%
+}
 for (int i=0;i<queries;i++){
 for (int i=0;i<queries;i++){
 	if(i>0) output.write(',');
 	if(i>0) output.write(',');
 	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
 	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
 }
 }
-
+if(queries>1) {
+        %>]<%
+}
+%><%
 response->headers["Content-Type"]="application/json";
 response->headers["Content-Type"]="application/json";
 response->headers["Server"]="cppsp/0.2";
 response->headers["Server"]="cppsp/0.2";
-%>]
+%>

+ 9 - 4
cpoll_cppsp/www/db_pg_threadpool

@@ -94,13 +94,18 @@ void efdCB(eventfd_t efdVal) {
 void waitCB(eventfd_t efdVal) {
 void waitCB(eventfd_t efdVal) {
 	this->doInit();
 	this->doInit();
 }
 }
-
-%>[<%
+%><%
+if(queries>1) {
+	%>[<%
+}
 for (int i=0;i<queries;i++){
 for (int i=0;i<queries;i++){
 	if(i>0) output.write(',');
 	if(i>0) output.write(',');
 	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
 	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
 }
 }
-
+if(queries>1) {
+        %>]<%
+}
+%><%
 response->headers["Content-Type"]="application/json";
 response->headers["Content-Type"]="application/json";
 response->headers["Server"]="cppsp/0.2";
 response->headers["Server"]="cppsp/0.2";
-%>]
+%>

+ 28 - 16
flask/app.py

@@ -22,10 +22,10 @@ except ImportError:
 # setup
 # setup
 
 
 app = Flask(__name__)
 app = Flask(__name__)
-app.config['SQLALCHEMY_DATABASE_URI'] = mysql_schema + '//benchmarkdbuser:benchmarkdbpass@DBHOSTNAME:3306/hello_world?charset=utf8'
+app.config['SQLALCHEMY_DATABASE_URI'] = mysql_schema + '//benchmarkdbuser:benchmarkdbpass@localhost:3306/hello_world?charset=utf8'
 app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
 app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
 db = SQLAlchemy(app)
 db = SQLAlchemy(app)
-dbraw_engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI'])
+dbraw_engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI'], connect_args={'autocommit': True}, pool_reset_on_return=None)
 
 
 # models
 # models
 
 
@@ -110,7 +110,9 @@ def get_fortunes():
 
 
 @app.route("/fortunesraw")
 @app.route("/fortunesraw")
 def get_forutens_raw():
 def get_forutens_raw():
-    fortunes = list(dbraw_engine.execute("SELECT * FROM Fortune"))
+    res = dbraw_engine.execute("SELECT * FROM Fortune")
+    fortunes = res.fetchall()
+    res.close()
     fortunes.append(Fortune(id=0, message="Additional fortune added at request time."))
     fortunes.append(Fortune(id=0, message="Additional fortune added at request time."))
     fortunes.sort(key=attrgetter('message'))
     fortunes.sort(key=attrgetter('message'))
     return render_template('fortunes.html', fortunes=fortunes)
     return render_template('fortunes.html', fortunes=fortunes)
@@ -138,19 +140,22 @@ def updates():
 @app.route("/raw-updates")
 @app.route("/raw-updates")
 def raw_updates():
 def raw_updates():
     """Test 5: Database Updates"""
     """Test 5: Database Updates"""
-    num_queries = request.args.get('queries', 1, type=int)
-    if num_queries > 500:
-        num_queries = 500
-
-    worlds = []
-    rp = partial(randint, 1, 10000)
-    for i in xrange(num_queries):
-        world = dbraw_engine.execute("SELECT * FROM World WHERE id=%s", (rp(),)).fetchone()
-        randomNumber = rp()
-        worlds.append({'id': world['id'], 'randomNumber': randomNumber})
-        dbraw_engine.execute("UPDATE World SET randomNumber=%s WHERE id=%s",
-                             (randomNumber, world['id']))
-    return json_response(worlds)
+    connection = dbraw_engine.connect()
+    try:
+        num_queries = request.args.get('queries', 1, type=int)
+        if num_queries > 500:
+            num_queries = 500
+
+        worlds = []
+        rp = partial(randint, 1, 10000)
+        for i in xrange(num_queries):
+            world = connection.execute("SELECT * FROM World WHERE id=%s", (rp(),)).fetchone()
+            randomNumber = rp()
+            worlds.append({'id': world['id'], 'randomNumber': randomNumber})
+            connection.execute("UPDATE World SET randomNumber=%s WHERE id=%s", (randomNumber, world['id']))
+        return json_response(worlds)
+    finally:
+        connection.close()
 
 
 
 
 @app.route('/plaintext')
 @app.route('/plaintext')
@@ -161,6 +166,13 @@ def plaintext():
     return response
     return response
 
 
 
 
+try:
+    import meinheld
+    meinheld.server.set_access_logger(None)
+    meinheld.set_keepalive(120)
+except ImportError:
+    pass
+
 # entry point for debugging
 # entry point for debugging
 if __name__ == "__main__":
 if __name__ == "__main__":
     app.run(debug=True)
     app.run(debug=True)

+ 7 - 7
flask/benchmark_config

@@ -16,7 +16,7 @@
       "framework": "flask",
       "framework": "flask",
       "language": "Python",
       "language": "Python",
       "orm": "Full",
       "orm": "Full",
-      "platform": "Gunicorn/Meinheld",
+      "platform": "Meinheld",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",
       "database_os": "Linux",
       "database_os": "Linux",
@@ -37,7 +37,7 @@
       "framework": "flask",
       "framework": "flask",
       "language": "Python",
       "language": "Python",
       "orm": "Raw",
       "orm": "Raw",
-      "platform": "Gunicorn/Meinheld",
+      "platform": "Meinheld",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",
       "database_os": "Linux",
       "database_os": "Linux",
@@ -60,7 +60,7 @@
       "framework": "flask",
       "framework": "flask",
       "language": "Python",
       "language": "Python",
       "orm": "Full",
       "orm": "Full",
-      "platform": "Gunicorn/Meinheld",
+      "platform": "Meinheld",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",
       "database_os": "Linux",
       "database_os": "Linux",
@@ -83,12 +83,12 @@
       "framework": "flask",
       "framework": "flask",
       "language": "Python",
       "language": "Python",
       "orm": "Full",
       "orm": "Full",
-      "platform": "Gunicorn/Tornado",
+      "platform": "Tornado",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",
       "database_os": "Linux",
       "database_os": "Linux",
       "display_name": "flask-pypy",
       "display_name": "flask-pypy",
-      "notes": "PyPy 2.2",
+      "notes": "PyPy 2.3",
       "versus": "wsgi"
       "versus": "wsgi"
     },
     },
     "pypy-mysql-raw": {
     "pypy-mysql-raw": {
@@ -104,12 +104,12 @@
       "framework": "flask",
       "framework": "flask",
       "language": "Python",
       "language": "Python",
       "orm": "Raw",
       "orm": "Raw",
-      "platform": "Gunicorn/Tornado",
+      "platform": "Tornado",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",
       "database_os": "Linux",
       "database_os": "Linux",
       "display_name": "flask-pypy-raw",
       "display_name": "flask-pypy-raw",
-      "notes": "PyPy 2.2",
+      "notes": "PyPy 2.3",
       "versus": "wsgi"
       "versus": "wsgi"
     },
     },
     "nginx-uwsgi": {
     "nginx-uwsgi": {

+ 1 - 0
flask/requirements.txt

@@ -0,0 +1 @@
+Chaussette

+ 28 - 14
flask/setup.py

@@ -6,24 +6,38 @@ import os
 bin_dir = os.path.expanduser('~/FrameworkBenchmarks/installs/py2/bin')
 bin_dir = os.path.expanduser('~/FrameworkBenchmarks/installs/py2/bin')
 NCPU = multiprocessing.cpu_count()
 NCPU = multiprocessing.cpu_count()
 
 
+CIRCUS_INI = """\
+[watcher:app]
+cmd = {BIN}/chaussette --fd=$(circus.sockets.app) --backend=meinheld app.app
+use_sockets = True
+numprocesses = {PROCS}
+
+[socket:app]
+host = 0.0.0.0
+port = 8080
+"""
+
+proc = None
 
 
 def start(args, logfile, errfile):
 def start(args, logfile, errfile):
+    global proc
+
+    subprocess.check_call(bin_dir + "/pip install -r requirements.txt",
+                          cwd="flask", stderr=errfile, stdout=logfile, shell=True)
+
+    with open("flask/circus.ini", "w") as f:
+        f.write(CIRCUS_INI.format(BIN=bin_dir, PROCS=NCPU*3))
+
     setup_util.replace_text("flask/app.py", "DBHOSTNAME", args.database_host)
     setup_util.replace_text("flask/app.py", "DBHOSTNAME", args.database_host)
-    subprocess.Popen([
-        bin_dir + "/gunicorn",
-        "app:app",
-        "-k", "meinheld.gmeinheld.MeinheldWorker",
-        "-b", "0.0.0.0:8080",
-        '-w', str(NCPU*3),
-        "--log-level=critical"],
-        cwd="flask", stderr=errfile, stdout=logfile)
+    proc = subprocess.Popen([bin_dir + "/circusd", "circus.ini"],
+		            cwd="flask", stderr=errfile, stdout=logfile)
     return 0
     return 0
 
 
 def stop(logfile, errfile):
 def stop(logfile, errfile):
-    p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
-    out, err = p.communicate()
-    for line in out.splitlines():
-      if 'FrameworkBenchmarks/installs/py2/bin/' in line:
-        pid = int(line.split(None,2)[1])
-        os.kill(pid, 15)
+    global proc
+    if proc is None:
+        return 0
+    proc.terminate()
+    proc.wait()
+    proc = None
     return 0
     return 0

+ 24 - 9
flask/setup_py3.py

@@ -3,21 +3,36 @@ import setup_util
 import multiprocessing
 import multiprocessing
 import os
 import os
 
 
-bin_dir = os.path.expanduser('~/FrameworkBenchmarks/installs/py3/bin')
+PY2BIN = os.path.expanduser('~/FrameworkBenchmarks/installs/py2/bin')
+PY3BIN = os.path.expanduser('~/FrameworkBenchmarks/installs/py3/bin')
 NCPU = multiprocessing.cpu_count()
 NCPU = multiprocessing.cpu_count()
 
 
+CIRCUS_INI = """\
+[watcher:app]
+cmd = {BIN}/chaussette --fd=$(circus.sockets.app) --backend=meinheld app.app
+use_sockets = True
+numprocesses = {PROCS}
+
+[socket:app]
+host = 0.0.0.0
+port = 8080
+"""
+
+proc = None
+
 
 
 def start(args, logfile, errfile):
 def start(args, logfile, errfile):
     global proc
     global proc
+
+    subprocess.check_call(PY3BIN + "/pip3 install -r requirements.txt",
+                          cwd="flask", stderr=errfile, stdout=logfile, shell=True)
+
+    with open("flask/circus.ini", "w") as f:
+        f.write(CIRCUS_INI.format(BIN=PY3BIN, PROCS=NCPU*3))
+
     setup_util.replace_text("flask/app.py", "DBHOSTNAME", args.database_host)
     setup_util.replace_text("flask/app.py", "DBHOSTNAME", args.database_host)
-    proc = subprocess.Popen([
-        bin_dir + "/gunicorn",
-        "app:app",
-        "-k", "meinheld.gmeinheld.MeinheldWorker",
-        "-b", "0.0.0.0:8080",
-        '-w', str(NCPU*3),
-        "--log-level=critical"],
-        cwd="flask", stderr=errfile, stdout=logfile)
+    proc = subprocess.Popen([PY2BIN + "/circusd", "circus.ini"],
+		            cwd="flask", stderr=errfile, stdout=logfile)
     return 0
     return 0
 
 
 def stop(logfile, errfile):
 def stop(logfile, errfile):

+ 0 - 1
flask/setup_pypy.py

@@ -15,7 +15,6 @@ def start(args, logfile, errfile):
     proc = subprocess.Popen([
     proc = subprocess.Popen([
         bin_dir + "/gunicorn",
         bin_dir + "/gunicorn",
         "app:app",
         "app:app",
-        '-k', 'tornado',
         "-b", "0.0.0.0:8080",
         "-b", "0.0.0.0:8080",
         '-w', str(NCPU*3),
         '-w', str(NCPU*3),
         "--log-level=critical"],
         "--log-level=critical"],

+ 1 - 1
grails/README.md

@@ -4,7 +4,7 @@ This is the Grails portion of a [benchmarking test suite](../) comparing a varie
 
 
 ## Infrastructure Software Versions
 ## Infrastructure Software Versions
 The tests were run with:
 The tests were run with:
-* [Grails 2.4.1](http://grails.org/)
+* [Grails 2.4.2](http://grails.org/)
 
 
 ## Test URLs
 ## Test URLs
 
 

+ 1 - 1
grails/hello/application.properties

@@ -1,6 +1,6 @@
 #Grails Metadata file
 #Grails Metadata file
 #Tue Feb 25 10:29:09 EET 2014
 #Tue Feb 25 10:29:09 EET 2014
-app.grails.version=2.4.1
+app.grails.version=2.4.2
 app.name=hello
 app.name=hello
 app.servlet.version=2.5
 app.servlet.version=2.5
 app.version=0.1
 app.version=0.1

+ 18 - 0
php-pimf/.editorconfig

@@ -0,0 +1,18 @@
+; top-most EditorConfig file
+root = true
+
+; Unix-style newlines
+[*]
+end_of_line = LF
+
+[*.php]
+indent_style = space
+indent_size = 2
+
+[*.test]
+indent_style = space
+indent_size = 2
+
+[*.rst]
+indent_style = space
+indent_size = 2

+ 1 - 0
php-pimf/.gitattributes

@@ -0,0 +1 @@
+* text=auto

+ 3 - 0
php-pimf/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "pimf-framework"]
+	path = pimf-framework
+	url = https://github.com/gjerokrsteski/pimf-framework.git

+ 20 - 0
php-pimf/.htaccess

@@ -0,0 +1,20 @@
+# Apache configuration file
+# http://httpd.apache.org/docs/2.2/mod/quickreference.html
+
+# Note: ".htaccess" files are an overhead for each request. This logic should
+# be placed in your Apache config whenever possible.
+# http://httpd.apache.org/docs/2.2/howto/htaccess.html
+
+# For all files not found in the file system, reroute the request to the
+# "index.php" front controller, keeping the query string intact
+
+<IfModule mod_rewrite.c>
+  RewriteEngine On
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^ index.php [QSA,L]
+</IfModule>
+
+<Files config.php>
+  order allow,deny
+  deny from all
+</Files>

+ 53 - 0
php-pimf/README.md

@@ -0,0 +1,53 @@
+# PIMF Benchmarking Test
+
+This is the PIMF portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+Uses the PHP standard [JSON encoder](http://www.php.net/manual/en/function.json-encode.php).
+
+* [JSON test controller](/app/Vanilla/Controller/Hello.php)
+
+
+### Data-Store/Database Mapping Test
+Uses the PIMF PDO Entity-Manager.
+
+* [DB test controller](/app/Vanilla/Controller/Hello.php)
+
+### Template Test
+Uses PIMF plain vanilla PHTML rendering
+
+* [Template test controller](/app/Vanilla/Controller/Hello.php)
+
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [PIMF Version 1.8.6](http://pimf-framework.de/)
+* [PHP Version 5.4.13](http://www.php.net/) with FPM and APC
+* [nginx 1.4.0](http://nginx.org/)
+* [MySQL 5.5.29](https://dev.mysql.com/)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/json
+
+### Data-Store/Database Mapping Test
+
+http://localhost/db
+
+### Variable Query Test
+    
+http://localhost/queries?queries=10
+
+### Templating Test
+
+http://localhost/fortunes
+
+### Database Updates Test
+
+http://localhost/updates?queries=10
+
+### Plaintext Test
+
+http://localhost/plaintext

+ 0 - 0
php-pimf/__init__.py


+ 109 - 0
php-pimf/app/Vanilla/Controller/Hello.php

@@ -0,0 +1,109 @@
+<?php
+namespace Vanilla\Controller;
+
+use Pimf\Controller\Base, Pimf\View, Pimf\Registry;
+
+class Hello extends Base
+{
+  /**
+   * A index action - this is a framework restriction!
+   */
+  public function indexAction()
+  {
+    $this->plaintextAction();
+  }
+
+  /**
+   * Test 1: JSON serialization
+   */
+  public function jsonAction()
+  {
+    $this->response->asJSON()->send(array("message" => "Hello, World!"));
+  }
+
+  /**
+   * Test 2: Multiple database queries
+   */
+  public function queriesAction()
+  {
+    $queries = max(1, min($this->request->fromGet()->get('queries', 1), 500));
+
+    $worlds = array();
+
+    for ($i = 0; $i < $queries; ++$i) {
+      $worlds[] = Registry::get('em')->world->find(mt_rand(1, 10000));
+    }
+
+    $this->response->asJSON()->send($worlds);
+  }
+
+  /**
+   * Test 3: Single database query
+   */
+  public function dbAction()
+  {
+    $worlds = Registry::get('em')->world->find(mt_rand(1, 10000));
+
+    $this->response->asJSON()->send($worlds);
+  }
+
+  private static function my_cmp($a, $b)
+  {
+    return  strcmp($a["message"], $b["message"]);
+  }
+
+  /**
+   * Test 4: Fortunes
+   */
+  public function fortunesAction()
+  {
+    $fortunes = Registry::get('em')->fortune->getAll();
+
+    $fortunes[] = array(
+      'id' => 0,
+      'message' => 'Additional fortune added at request time.'
+    );
+
+    usort($fortunes, array($this, "my_cmp"));
+
+    $view = new View('table.phtml', array('fortunes' => $fortunes));
+
+    $this->response->asHTML()->send($view);
+  }
+
+  /**
+   * Test 5: Database updates
+   */
+  public function updatesAction()
+  {
+    $queries = max(1, min($this->request->fromGet()->get('queries', 1), 500));
+
+    $worlds = array();
+
+    /* @var $em \Pimf\EntityManager */
+    $em = Registry::get('em');
+
+    $em->beginTransaction();
+
+    for ($i = 0; $i < $queries; ++$i) {
+      $worlds[] = $em->world->find(mt_rand(1, 10000));
+    }
+
+    foreach ($worlds as $row) {
+      $row['randomNumber'] = rand(1, 10000);
+      $em->world->update($row);
+    }
+
+    $em->commitTransaction();
+
+    $this->response->asJSON()->send($worlds);
+  }
+
+  /**
+   * Test 6: Plaintext
+   */
+  public function plaintextAction()
+  {
+    $this->response->asTEXT()->send('Hello, World!');
+  }
+}

+ 19 - 0
php-pimf/app/Vanilla/DataMapper/Fortune.php

@@ -0,0 +1,19 @@
+<?php
+namespace Vanilla\DataMapper;
+
+use Pimf\DataMapper\Base;
+
+class Fortune extends Base
+{
+  /**
+   * @return array
+   */
+  public function getAll()
+  {
+    $sth = $this->pdo->prepare('SELECT * FROM Fortune');
+    $sth->execute();
+
+    return $sth->fetchAll(\PDO::FETCH_ASSOC);
+  }
+}
+ 

+ 37 - 0
php-pimf/app/Vanilla/DataMapper/World.php

@@ -0,0 +1,37 @@
+<?php
+namespace Vanilla\DataMapper;
+
+use Pimf\DataMapper\Base;
+
+class World extends Base
+{
+  /**
+   * @param integer $id
+   *
+   * @return array
+   */
+  public function find($id)
+  {
+    $sth = $this->pdo->prepare('SELECT * FROM World WHERE id = :id');
+
+    $sth->bindValue(':id', $id, \PDO::PARAM_INT);
+    $sth->execute();
+
+    return (array) $sth->fetch(\PDO::FETCH_ASSOC);
+  }
+
+  /**
+   * @param array $world
+   *
+   * @return bool
+   */
+  public function update(array $world)
+  {
+    $sth = $this->pdo->prepare('UPDATE World SET randomNumber = :randomNumber WHERE id = :id');
+
+    $sth->bindValue(':randomNumber', $world['randomNumber'], \PDO::PARAM_INT);
+    $sth->bindValue(':id', $world['id'], \PDO::PARAM_INT);
+
+    return $sth->execute();
+  }
+}

+ 60 - 0
php-pimf/app/Vanilla/_database/create.sql

@@ -0,0 +1,60 @@
+# create benchmark user
+GRANT ALL ON *.* TO 'benchmarkdbuser'@'%' IDENTIFIED BY 'benchmarkdbpass';
+GRANT ALL ON *.* TO 'benchmarkdbuser'@'localhost' IDENTIFIED BY 'benchmarkdbpass';
+
+# modified from SO answer http://stackoverflow.com/questions/5125096/for-loop-in-mysql
+DROP DATABASE IF EXISTS hello_world;
+CREATE DATABASE hello_world;
+USE hello_world;
+
+DROP TABLE IF EXISTS World;
+CREATE TABLE  World (
+  id int(10) unsigned NOT NULL auto_increment,
+  randomNumber int NOT NULL default 0,
+  PRIMARY KEY  (id)
+)
+ENGINE=INNODB;
+
+DROP PROCEDURE IF EXISTS load_data;
+
+DELIMITER #
+CREATE PROCEDURE load_data()
+BEGIN
+
+declare v_max int unsigned default 10000;
+declare v_counter int unsigned default 0;
+
+  TRUNCATE TABLE World;
+  START TRANSACTION;
+  while v_counter < v_max do
+    INSERT INTO World (randomNumber) VALUES ( floor(0 + (rand() * 10000)) );
+    SET v_counter=v_counter+1;
+  end while;
+  commit;
+END #
+
+DELIMITER ;
+
+CALL load_data();
+
+DROP TABLE IF EXISTS Fortune;
+CREATE TABLE  Fortune (
+  id int(10) unsigned NOT NULL auto_increment,
+  message varchar(2048) CHARACTER SET 'utf8' NOT NULL,
+  PRIMARY KEY  (id)
+)
+ENGINE=INNODB;
+
+INSERT INTO Fortune (message) VALUES ('fortune: No such file or directory');
+INSERT INTO Fortune (message) VALUES ('A computer scientist is someone who fixes things that aren''t broken.');
+INSERT INTO Fortune (message) VALUES ('After enough decimal places, nobody gives a damn.');
+INSERT INTO Fortune (message) VALUES ('A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1');
+INSERT INTO Fortune (message) VALUES ('A computer program does what you tell it to do, not what you want it to do.');
+INSERT INTO Fortune (message) VALUES ('Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen');
+INSERT INTO Fortune (message) VALUES ('Any program that runs right is obsolete.');
+INSERT INTO Fortune (message) VALUES ('A list is only as strong as its weakest link. — Donald Knuth');
+INSERT INTO Fortune (message) VALUES ('Feature: A bug with seniority.');
+INSERT INTO Fortune (message) VALUES ('Computers make very fast, very accurate mistakes.');
+INSERT INTO Fortune (message) VALUES ('<script>alert("This should not be displayed in a browser alert box.");</script>');
+INSERT INTO Fortune (message) VALUES ('フレームワークのベンチマーク');
+

+ 13 - 0
php-pimf/app/Vanilla/_templates/table.phtml

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+  <tr>
+    <th>id</th>
+    <th>message</th>
+  </tr>
+  <?php echo $this->loop('tr.phtml', $this->fortunes); ?>
+</table>
+</body>
+</html>

+ 4 - 0
php-pimf/app/Vanilla/_templates/tr.phtml

@@ -0,0 +1,4 @@
+<tr>
+  <td><?php echo $this->id ?></td>
+  <td><?php echo e($this->message) ?></td>
+</tr>

+ 4 - 0
php-pimf/app/Vanilla/routes.php

@@ -0,0 +1,4 @@
+<?php
+return array(
+  new \Pimf\Route('/:action', array('controller' => 'hello')),
+);

+ 37 - 0
php-pimf/app/autoload.app.php

@@ -0,0 +1,37 @@
+<?php
+/*
+|--------------------------------------------------------------------------
+| Your Application's PHP classes auto-loading
+|
+| All classes in PIMF are statically mapped. It's just a simple array of
+| class to file path maps for ultra-fast file loading.
+|--------------------------------------------------------------------------
+*/
+spl_autoload_register(
+  function ($class) {
+
+    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
+    // FEEL FREE TO CHANGE THE MAPPINGS AND DIRECTORIES
+    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
+
+    /**
+     * The mappings from class names to file paths.
+     */
+    static $mappings = array(
+      'Vanilla\\Controller\\Hello'   => '/Vanilla/Controller/Hello.php',
+      'Vanilla\\DataMapper\\Fortune' => '/Vanilla/DataMapper/Fortune.php',
+      'Vanilla\\DataMapper\\World'   => '/Vanilla/DataMapper/World.php',
+    );
+
+    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
+    //  END OF USER CONFIGURATION!!!
+    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
+
+    // load the class from the static heap of classes.
+    if (isset($mappings[$class])) {
+      return require __DIR__ . DIRECTORY_SEPARATOR . $mappings[$class];
+    }
+
+    return false;
+  }
+);

+ 18 - 0
php-pimf/app/bootstrap.app.php

@@ -0,0 +1,18 @@
+<?php
+/*
+|--------------------------------------------------------------------------
+| PIMF bootstrap
+|--------------------------------------------------------------------------
+*/
+if(!defined('DS')) define('DS', DIRECTORY_SEPARATOR, true);
+if(!defined('BASE_PATH')) define('BASE_PATH', realpath(dirname(dirname(__FILE__))) . DS, true);
+
+$config = include_once BASE_PATH .'app/config.app.php';
+
+require_once BASE_PATH .'pimf-framework/autoload.core.php';
+require_once BASE_PATH .'app/autoload.app.php';
+require_once BASE_PATH .'pimf-framework/utils.php';
+
+use \Pimf\Application as App;
+
+App::bootstrap($config, $_SERVER);

+ 167 - 0
php-pimf/app/config.app.php

@@ -0,0 +1,167 @@
+<?php
+/*
+|--------------------------------------------------------------------------
+| PIMF Application Configuration
+|--------------------------------------------------------------------------
+|
+| The PIMF configuration is responsible for returning an array
+| of configuration options. By default, we use the variable $config provided 
+| with PIMF - however, you are free to use your own storage mechanism for 
+| configuration arrays.
+|
+*/
+return array(
+
+  /*
+  |------------------------------------------------------------------------
+  | The default environment mode for your application [testing|production]
+  |------------------------------------------------------------------------
+  */
+  'environment' => 'production',
+
+  /*
+  |------------------------------------------------------------------------
+  | The default character encoding used by your application.
+  |------------------------------------------------------------------------
+  */
+  'encoding'    => 'UTF-8',
+
+  /*
+  |------------------------------------------------------------------------
+  | The default timezone of your application.
+  | Supported timezones list: http://www.php.net/manual/en/timezones.php
+  |------------------------------------------------------------------------
+  */
+  'timezone'    => 'UTC',
+
+  /*
+  |--------------------------------------------------------------------------
+  | Is it regular HTTP or secure HTTPS
+  |--------------------------------------------------------------------------
+  */
+  'ssl'         => false,
+
+  /*
+  |------------------------------------------------------------------------
+  | Application meta
+  |------------------------------------------------------------------------
+  */
+  'app'         => array(
+    'name'               => 'Vanilla',
+    'key'                => 'some5secret5key5here', // application key
+    'default_controller' => 'hello', // the name of the fallback controller
+    'routeable'          => true, // get cleaner URLs or not
+    'url'                => 'http://localhost', // URL used to access your application without a trailing slash.
+    'index'              => '', // if you are using mod_rewrite to get cleaner URLs let it empty otherwise set index.php
+    'asset_url'          => '', // the base URL used for your application's asset files
+  ),
+
+  /*
+  |------------------------------------------------------------------------
+  | Production environment settings
+  |------------------------------------------------------------------------
+  */
+  'production'  => array(
+    'db' => array(
+      'driver'   => 'mysql',
+      'host'     => '127.0.0.1',
+      'database' => 'hello_world',
+      'username' => 'benchmarkdbuser',
+      'password' => 'benchmarkdbpass',
+      'charset'  => 'utf8',
+      'port'     => '3306',
+      // 'unix_socket' => '',
+    ),
+  ),
+
+  /*
+  |------------------------------------------------------------------------
+  | Bootstrapping and dependencies to php-version and extensions
+  |------------------------------------------------------------------------
+  */
+  'bootstrap'   => array(
+    'local_temp_directory' => '/tmp/'
+  ),
+
+  /*
+  |------------------------------------------------------------------------
+  | Settings for the error handling behavior
+  |------------------------------------------------------------------------
+  */
+  'error'       => array(
+    'ignore_levels' => array(E_USER_DEPRECATED),
+    'debug_info'    => false, // true = if developing - false = if production
+    'log'           => true,
+  ),
+
+  /*
+  |--------------------------------------------------------------------------
+  | Session settings
+  |--------------------------------------------------------------------------
+  */
+  'session'     => array(
+
+    // Session storage 'cookie', 'file', 'pdo', 'memcached', 'apc', 'redis', 'dba', 'wincache', 'memory'
+    'storage'            => '',
+
+    // If using file storage - default is null
+    'storage_path'       => 'app/Vanilla/_session/',
+
+    // If using the PDO (database) session storage
+    'database'           => array(
+      //'driver' => 'sqlite',
+      //'database' => 'app/Vanilla/_session/session.db',
+    ),
+
+    // Garbage collection has a 2% chance of occurring for any given request to
+    // the application. Feel free to tune this to your requirements.
+    'garbage_collection' => array(2, 100),
+
+    // Session lifetime number of minutes
+    'lifetime'           => 60,
+
+    // Session expiration on web browser close
+    'expire_on_close'    => false,
+
+    // Session cookie name
+    'cookie'             => 'pimf_session',
+
+    // Session cookie path
+    'path'               => '/',
+
+    // Domain for which the session cookie is available.
+    'domain'             => null,
+
+    // If the cookie should only be sent over HTTPS.
+    'secure'             => false,
+  ),
+
+  /*
+  |--------------------------------------------------------------------------
+  | Cache settings
+  |--------------------------------------------------------------------------
+  */
+  'cache'       => array(
+
+    // Cache storage 'pdo', 'file', 'memcached', 'apc', 'redis', 'dba', 'wincache', 'memory'
+    'storage'      => '',
+
+    // If using file storage - default is null
+    'storage_path' => 'app/Vanilla/_cache/',
+
+    // If using the PDO (database) cache storage
+    'database'     => array(
+      //'driver' => 'sqlite',
+      //'database' => 'app/Vanilla/_cache/cache.db',
+    ),
+
+    // If using Memcached and APC to prevent collisions with other applications on the server.
+    'key'          => 'pimfmaster',
+
+    // Memcached servers - for more check out: http://memcached.org
+    'memcached'    => array(
+      'servers' => array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100),
+    ),
+  ),
+
+);

+ 47 - 0
php-pimf/benchmark_config

@@ -0,0 +1,47 @@
+{
+  "framework": "pimf",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MySQL",
+      "framework": "pimf",
+      "language": "PHP",
+      "orm": "Micro",
+      "platform": "PHP-FPM",
+      "webserver": "nginx",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "pimf",
+      "notes": "",
+      "versus": "php"
+    },
+    "raw": {
+      "setup_file": "setup_raw",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MySQL",
+      "framework": "pimf",
+      "language": "PHP",
+      "orm": "Micro",
+      "platform": "PHP-FPM",
+      "webserver": "nginx",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "pimf",
+      "notes": "",
+      "versus": "php"
+    }
+  }]
+}

+ 5 - 0
php-pimf/composer.json

@@ -0,0 +1,5 @@
+{
+    "require": {
+        "gjerokrsteski/pimf": "dev-master"
+    }
+}

+ 133 - 0
php-pimf/deploy/nginx.conf

@@ -0,0 +1,133 @@
+#user  nobody;
+worker_processes  8;
+
+#error_log  logs/error.log;
+#error_log  logs/error.log  notice;
+#error_log  logs/error.log  info;
+error_log /dev/null crit;
+
+#pid        logs/nginx.pid;
+
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       /usr/local/nginx/conf/mime.types;
+    default_type  application/octet-stream;
+
+    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+    #                  '$status $body_bytes_sent "$http_referer" '
+    #                  '"$http_user_agent" "$http_x_forwarded_for"';
+
+    #access_log  logs/access.log  main;
+    access_log off;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+
+    #gzip  on;
+
+    upstream fastcgi_backend {
+        server 127.0.0.1:9001;
+        keepalive 32;
+    }
+
+    server {
+        listen       8080;
+        server_name  localhost;
+
+        #charset koi8-r;
+
+        #access_log  logs/host.access.log  main;
+
+        #location / {
+        #    root   html;
+        #    index  index.html index.htm;
+        #}
+
+        #error_page  404              /404.html;
+
+        # redirect server error pages to the static page /50x.html
+        #
+        #error_page   500 502 503 504  /50x.html;
+        #location = /50x.html {
+        #    root   html;
+        #}
+
+        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
+        #
+        #location ~ \.php$ {
+        #    proxy_pass   http://127.0.0.1;
+        #}
+
+        root /home/ubuntu/FrameworkBenchmarks/php-pimf/;
+        index  index.php;
+
+        location / {
+            try_files $uri $uri/ /index.php?$uri&$args;
+        }
+
+        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+        #
+        location ~ \.php$ {
+            try_files $uri =404;
+            fastcgi_pass   fastcgi_backend;
+            fastcgi_keep_conn on;
+            fastcgi_index  index.php;
+#            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
+            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+            include        /usr/local/nginx/conf/fastcgi_params;
+        }
+
+        # deny access to .htaccess files, if Apache's document root
+        # concurs with nginx's one
+        #
+        #location ~ /\.ht {
+        #    deny  all;
+        #}
+    }
+
+
+    # another virtual host using mix of IP-, name-, and port-based configuration
+    #
+    #server {
+    #    listen       8000;
+    #    listen       somename:8080;
+    #    server_name  somename  alias  another.alias;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+
+    # HTTPS server
+    #
+    #server {
+    #    listen       443;
+    #    server_name  localhost;
+
+    #    ssl                  on;
+    #    ssl_certificate      cert.pem;
+    #    ssl_certificate_key  cert.key;
+
+    #    ssl_session_timeout  5m;
+
+    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
+    #    ssl_ciphers  HIGH:!aNULL:!MD5;
+    #    ssl_prefer_server_ciphers   on;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+}

+ 127 - 0
php-pimf/deploy/nginx_raw.conf

@@ -0,0 +1,127 @@
+#user  nobody;
+worker_processes  8;
+
+#error_log  logs/error.log;
+#error_log  logs/error.log  notice;
+#error_log  logs/error.log  info;
+error_log /dev/null crit;
+
+#pid        logs/nginx.pid;
+
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       /usr/local/nginx/conf/mime.types;
+    default_type  application/octet-stream;
+
+    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+    #                  '$status $body_bytes_sent "$http_referer" '
+    #                  '"$http_user_agent" "$http_x_forwarded_for"';
+
+    #access_log  logs/access.log  main;
+    access_log off;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+
+    #gzip  on;
+
+    server {
+        listen       8080;
+        server_name  localhost;
+
+        #charset koi8-r;
+
+        #access_log  logs/host.access.log  main;
+
+        #location / {
+        #    root   html;
+        #    index  index.html index.htm;
+        #}
+
+        #error_page  404              /404.html;
+
+        # redirect server error pages to the static page /50x.html
+        #
+        #error_page   500 502 503 504  /50x.html;
+        #location = /50x.html {
+        #    root   html;
+        #}
+
+        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
+        #
+        #location ~ \.php$ {
+        #    proxy_pass   http://127.0.0.1;
+        #}
+
+        root /home/tfb/FrameworkBenchmarks/php-pimf/;
+        index  index.php;
+
+        location / {
+            try_files $uri $uri/ /index.php?$uri&$args;
+        }
+
+        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+        #
+        location ~ \.php$ {
+            try_files $uri =404;
+            fastcgi_pass   127.0.0.1:9001;
+            fastcgi_index  index.php;
+#            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
+            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+            include        /usr/local/nginx/conf/fastcgi_params;
+        }
+
+        # deny access to .htaccess files, if Apache's document root
+        # concurs with nginx's one
+        #
+        #location ~ /\.ht {
+        #    deny  all;
+        #}
+    }
+
+
+    # another virtual host using mix of IP-, name-, and port-based configuration
+    #
+    #server {
+    #    listen       8000;
+    #    listen       somename:8080;
+    #    server_name  somename  alias  another.alias;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+
+    # HTTPS server
+    #
+    #server {
+    #    listen       443;
+    #    server_name  localhost;
+
+    #    ssl                  on;
+    #    ssl_certificate      cert.pem;
+    #    ssl_certificate_key  cert.key;
+
+    #    ssl_session_timeout  5m;
+
+    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
+    #    ssl_ciphers  HIGH:!aNULL:!MD5;
+    #    ssl_prefer_server_ciphers   on;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+}

+ 9 - 0
php-pimf/deploy/php-pimf

@@ -0,0 +1,9 @@
+<VirtualHost *:8080>
+  Alias /php-pimf/ "/home/tfb/FrameworkBenchmarks/php-pimf/"
+  <Directory /home/tfb/FrameworkBenchmarks/php-pimf/>
+          Options Indexes FollowSymLinks MultiViews
+          #AllowOverride None
+          Order allow,deny
+          allow from all
+  </Directory>
+</VirtualHost>

+ 0 - 0
php-pimf/favicon.ico


+ 13 - 0
php-pimf/index.php

@@ -0,0 +1,13 @@
+<?php
+/*
+|--------------------------------------------------------------------------
+| PIMF Application gateway/runner
+|--------------------------------------------------------------------------
+*/
+include_once 'app/bootstrap.app.php';
+
+use \Pimf\Application as App;
+
+App::run($_GET, $_POST, $_COOKIE);
+
+App::finish();

+ 25 - 0
php-pimf/license.txt

@@ -0,0 +1,25 @@
+New BSD License
+
+Copyright (c) 2013, Gjero Krsteski (http://krsteski.de)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+disclaimer in the documentation and/or other materials provided with the distribution.
+
+* Neither the name of Gjero Krsteski nor the names of its contributors may be used to endorse or promote products
+derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 4 - 0
php-pimf/pimf

@@ -0,0 +1,4 @@
+#!/usr/bin/php
+<?php
+require 'index.php';
+?>

+ 18 - 0
php-pimf/pimf-framework/.editorconfig

@@ -0,0 +1,18 @@
+; top-most EditorConfig file
+root = true
+
+; Unix-style newlines
+[*]
+end_of_line = LF
+
+[*.php]
+indent_style = space
+indent_size = 2
+
+[*.test]
+indent_style = space
+indent_size = 2
+
+[*.rst]
+indent_style = space
+indent_size = 2

+ 1 - 0
php-pimf/pimf-framework/.gitattributes

@@ -0,0 +1 @@
+/tests export-ignore

+ 31 - 0
php-pimf/pimf-framework/.gitignore

@@ -0,0 +1,31 @@
+# OS or Editor folders
+.DS_Store
+Thumbs.db
+.cache
+.project
+.settings
+.tmproj
+*.esproj
+nbproject
+
+# Numerous always-ignore extensions
+*.diff
+*.err
+*.orig
+*.log
+*.rej
+*.swo
+*.swp
+*.vi
+*~
+*.sass-cache
+
+# Folders to ignore
+.hg
+.svn
+.CVS
+intermediate
+publish
+.idea
+_coverage
+coverage

+ 68 - 0
php-pimf/pimf-framework/.scrutinizer.yml

@@ -0,0 +1,68 @@
+imports:
+    - php
+
+tools:
+
+    # Code Coverage
+    external_code_coverage:
+        enabled: true
+        filter:
+            excluded_paths:
+                - 'tests/*'
+
+    # Copy/Paste Detector
+    php_cpd:
+        enabled:              true
+        command:              phpcpd
+        excluded_dirs:
+            - tests
+        
+    
+    # PHP CS Fixer (http://http://cs.sensiolabs.org/).
+    php_cs_fixer:
+        enabled:              true
+        command:              php-cs-fixer
+        config:
+            level:            psr2
+        filter:
+            excluded_paths:
+                - 'tests/*'
+        
+    
+    # Analyzes the size and structure of a PHP project.
+    php_loc:
+        enabled:              true
+        command:              phploc
+        excluded_dirs:
+            - tests
+        
+    
+    # PHP Mess Detector (http://phpmd.org).
+    php_mess_detector:
+        enabled:              true
+        command:              phpmd
+        config:
+            rulesets:
+                - codesize
+                - unusedcode
+                - naming
+                - design
+        filter:
+            excluded_paths:
+                - 'tests/*'
+        
+    
+    # Analyzes the size and structure of a PHP project.
+    php_pdepend:
+        enabled:              true
+        command:              pdepend
+        excluded_dirs:
+            - tests
+        
+    # Runs Scrutinizer's PHP Analyzer Tool
+    php_analyzer:
+        enabled:              true
+        filter:
+            excluded_paths:
+                - 'tests/*'
+

+ 20 - 0
php-pimf/pimf-framework/.travis.yml

@@ -0,0 +1,20 @@
+language: php
+
+php:
+  - 5.6
+  - 5.5
+  - 5.4
+  - 5.3
+
+before_script:
+  - phpenv rehash
+
+script:
+  - phpunit --configuration phpunit.xml --coverage-clover=coverage.clover
+  - wget https://scrutinizer-ci.com/ocular.phar
+  - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
+
+notifications:
+  email:
+    - [email protected]
+

+ 52 - 0
php-pimf/pimf-framework/README.md

@@ -0,0 +1,52 @@
+Welcome to PIMF
+===============
+Have you ever wished a PHP framework that perfectly adapts to your projects needs, your programming experience and your customers budget? A thin PHP framewrok with less implementing rools and easy to learn how to use it? PIMF is about to satisfy your demands!
+
+[![Build Status](https://travis-ci.org/gjerokrsteski/pimf-framework.png?branch=master)](https://travis-ci.org/gjerokrsteski/pimf-framework) 
+[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/gjerokrsteski/pimf-framework/badges/quality-score.png?s=6455d019460628327434d85520bd13c4d03a2673)](https://scrutinizer-ci.com/g/gjerokrsteski/pimf-framework/)
+[![Code Coverage](https://scrutinizer-ci.com/g/gjerokrsteski/pimf-framework/badges/coverage.png?s=ded7f154ac78cbdbabc166e32fd2f54c009d2c67)](https://scrutinizer-ci.com/g/gjerokrsteski/pimf-framework/)
+
+**Note:** This repository contains the core code of the PIMF framework. If you want to build an application using PIMF, please use ono of the quick starting bundles below.
+
+PIMF Philosophy
+---------------
+A good and robust business-logic is better that fat and complex framework. Most of the PHP framewroks are bigger than your problem. At all you need less than 20% of the functionality of the framework to solve you problem. Therefore we belive that the “right” 20% of the effort is the 80% of the results - and that is PIMF.
+
+The aim was to create robust and secure projects and deliver them fast. We wanted just one easy framewrok, who can be used once for all  our projects. And than - PIMF was born!
+
+PIMFs implementation is based on well proved design patterns as well as fast objects relation mapping mechanism - like famous PHP frameworks had. The architecture is designed upgrade friendly - so you can upgrade to newer versions without to override your projects. And for all of you out there, who like to create rich application interfaces with ExtJs or Dojo - we have created mechanism to couple your GUI to the controllers in a easy and fast way.
+
+
+Quickstart with PIMF-Blog bundle using SQLite
+---------------------------------------------
+This Blog is a run ready bundle, which uses PIMF framework including a blog application based on SQLite database. Here you can learn how to work with \Pimf\EntityManager, \Pimf\Util\Validator and \Pimf\View. https://github.com/gjerokrsteski/pimf-blog
+
+Quickstart with PIMF-Blog bundle using MySQL and SQLite
+-------------------------------------------------------
+This Blog is a run ready bundle, which uses PIMF framework including a blog application based on MySQL database. The session will be stored at SQLite database.
+Here you can learn how to work with Pimf\EntityManager, Pimf\Util\Validator and Pimf\View. https://github.com/gjerokrsteski/pimf-blog-mysql
+
+Quickstart with PIMF-Vanilla bundle
+------------------------------
+This is a run ready "Hello world" bundle using PIMF micro framework. Here you can learn how to work with \Pimf\View and \Pimf\Router. https://github.com/gjerokrsteski/pimf-vanilla
+
+Quickstart with PIMF-Haanga bundle
+-----------------------------
+This is a run ready "Hello world" application using PIMF micro framework and Haanga (http://haanga.org/documentation) a fast and secure template engine for PHP that uses Django syntax.
+Here you can learn how to work with \Pimf\View\Haanga template engine and \Pimf\Router. https://github.com/gjerokrsteski/pimf-haanga
+
+Quickstart with PIMF-Twig bundle
+-----------------------------
+This is a run ready "Hello world" application using PIMF micro framework and Twig a flexible, fast, and secure template
+engine for PHP (http://twig.sensiolabs.org) brought for us by Symfony framework developers. Here you can learn how to work
+with \Pimf\View\Twig template engine and \Pimf\Router. https://github.com/gjerokrsteski/pimf-twig
+
+Learning PIMF
+-------------
+One of the best ways to learn PIMF is to read through the entirety of its documentation. This guide details all aspects of the framework and how to apply them to your application.
+
+Please read here: https://github.com/gjerokrsteski/pimf/wiki
+
+Framework Sponsor
+-------------------
+JetBRAINS supports the development of the PIMF with PHPStorm licenses and we feel confidential that PHPStorm strongly influences the PIMF's quality. Use PHPStorm! http://www.jetbrains.com/phpstorm/

+ 106 - 0
php-pimf/pimf-framework/autoload.core.php

@@ -0,0 +1,106 @@
+<?php
+// @codingStandardsIgnoreFile
+// @codeCoverageIgnoreStart
+// this is an auto-generated file, please do not edit!
+spl_autoload_register(
+  function ($class) {
+    static $classes = null;
+    if ($classes === null) {
+      $classes = array(
+        'Pimf\\Application'                  => '/Pimf/Application.php',
+        'Pimf\\Cache'                        => '/Pimf/Cache.php',
+        'Pimf\\Cache\\Storages\\Apc'         => '/Pimf/Cache/Storages/Apc.php',
+        'Pimf\\Cache\\Storages\\Dba'         => '/Pimf/Cache/Storages/Dba.php',
+        'Pimf\\Cache\\Storages\\File'        => '/Pimf/Cache/Storages/File.php',
+        'Pimf\\Cache\\Storages\\Memcached'   => '/Pimf/Cache/Storages/Memcached.php',
+        'Pimf\\Cache\\Storages\\Memory'      => '/Pimf/Cache/Storages/Memory.php',
+        'Pimf\\Cache\\Storages\\Database'    => '/Pimf/Cache/Storages/Database.php',
+        'Pimf\\Cache\\Storages\\Redis'       => '/Pimf/Cache/Storages/Redis.php',
+        'Pimf\\Cache\\Storages\\Storage'     => '/Pimf/Cache/Storages/Storage.php',
+        'Pimf\\Cache\\Storages\\Wincache'    => '/Pimf/Cache/Storages/Wincache.php',
+        'Pimf\\Cache\\Storages\\Pdo'         => '/Pimf/Cache/Storages/Pdo.php',
+        'Pimf\\Cli'                          => '/Pimf/Cli.php',
+        'Pimf\\Cli\\Std'                     => '/Pimf/Cli/Std.php',
+        'Pimf\\Contracts\\Arrayable'         => '/Pimf/Contracts/Arrayable.php',
+        'Pimf\\Contracts\\Cleanable'         => '/Pimf/Contracts/Cleanable.php',
+        'Pimf\\Contracts\\Jsonable'          => '/Pimf/Contracts/Jsonable.php',
+        'Pimf\\Contracts\\MessageProvider'   => '/Pimf/Contracts/MessageProvider.php',
+        'Pimf\\Contracts\\Renderable'        => '/Pimf/Contracts/Renderable.php',
+        'Pimf\\Contracts\\Reunitable'        => '/Pimf/Contracts/Reunitable.php',
+        'Pimf\\Controller\\Base'             => '/Pimf/Controller/Base.php',
+        'Pimf\\Controller\\Core'             => '/Pimf/Controller/Core.php',
+        'Pimf\\Controller\\Exception'        => '/Pimf/Controller/Exception.php',
+        'Pimf\\Cookie'                       => '/Pimf/Cookie.php',
+        'Pimf\\DataMapper\\Base'             => '/Pimf/DataMapper/Base.php',
+        'Pimf\\EntityManager'                => '/Pimf/EntityManager.php',
+        'Pimf\\Environment'                  => '/Pimf/Environment.php',
+        'Pimf\\Error'                        => '/Pimf/Error.php',
+        'Pimf\\Event'                        => '/Pimf/Event.php',
+        'Pimf\\Logger'                       => '/Pimf/Logger.php',
+        'Pimf\\Memcached'                    => '/Pimf/Memcached.php',
+        'Pimf\\Model\\AsArray'               => '/Pimf/Model/AsArray.php',
+        'Pimf\\Param'                        => '/Pimf/Param.php',
+        'Pimf\\Database'                     => '/Pimf/Database.php',
+        'Pimf\\Pdo\\Connector'               => '/Pimf/Pdo/Connector.php',
+        'Pimf\\Pdo\\Factory'                 => '/Pimf/Pdo/Factory.php',
+        'Pimf\\Pdo\\Mysql'                   => '/Pimf/Pdo/Mysql.php',
+        'Pimf\\Pdo\\Postgre'                 => '/Pimf/Pdo/Postgre.php',
+        'Pimf\\Pdo\\Sqlite'                  => '/Pimf/Pdo/Sqlite.php',
+        'Pimf\\Pdo\\Sqlserver'               => '/Pimf/Pdo/Sqlserver.php',
+        'Pimf\\Redis'                        => '/Pimf/Redis.php',
+        'Pimf\\Registry'                     => '/Pimf/Registry.php',
+        'Pimf\\Request'                      => '/Pimf/Request.php',
+        'Pimf\\Response'                     => '/Pimf/Response.php',
+        'Pimf\\Resolver'                     => '/Pimf/Resolver.php',
+        'Pimf\\Route'                        => '/Pimf/Route.php',
+        'Pimf\\Route\\Target'                => '/Pimf/Route/Target.php',
+        'Pimf\\Router'                       => '/Pimf/Router.php',
+        'Pimf\\Resolver\\Exception'          => '/Pimf/Resolver/Exception.php',
+        'Pimf\\Sapi'                         => '/Pimf/Sapi.php',
+        'Pimf\\Session'                      => '/Pimf/Session.php',
+        'Pimf\\Session\\Payload'             => '/Pimf/Session/Payload.php',
+        'Pimf\\Session\\Storages\\Apc'       => '/Pimf/Session/Storages/Apc.php',
+        'Pimf\\Session\\Storages\\Cookie'    => '/Pimf/Session/Storages/Cookie.php',
+        'Pimf\\Session\\Storages\\Dba'       => '/Pimf/Session/Storages/Dba.php',
+        'Pimf\\Session\\Storages\\File'      => '/Pimf/Session/Storages/File.php',
+        'Pimf\\Session\\Storages\\Memcached' => '/Pimf/Session/Storages/Memcached.php',
+        'Pimf\\Session\\Storages\\Memory'    => '/Pimf/Session/Storages/Memory.php',
+        'Pimf\\Session\\Storages\\Database'  => '/Pimf/Session/Storages/Database.php',
+        'Pimf\\Session\\Storages\\Redis'     => '/Pimf/Session/Storages/Redis.php',
+        'Pimf\\Session\\Storages\\Storage'   => '/Pimf/Session/Storages/Storage.php',
+        'Pimf\\Session\\Storages\\Pdo'       => '/Pimf/Session/Storages/Pdo.php',
+        'Pimf\\Uri'                          => '/Pimf/Uri.php',
+        'Pimf\\Url'                          => '/Pimf/Url.php',
+        'Pimf\\Util\\Dom'                    => '/Pimf/Util/Dom.php',
+        'Pimf\\Util\\File'                   => '/Pimf/Util/File.php',
+        'Pimf\\Util\\Header'                 => '/Pimf/Util/Header.php',
+        'Pimf\\Util\\Header\\ResponseStatus' => '/Pimf/Util/Header/ResponseStatus.php',
+        'Pimf\\Util\\Header\\ContentType'    => '/Pimf/Util/Header/ContentType.php',
+        'Pimf\\Util\\Identifier'             => '/Pimf/Util/Identifier.php',
+        'Pimf\\Util\\IdentityMap'            => '/Pimf/Util/IdentityMap.php',
+        'Pimf\\Util\\Json'                   => '/Pimf/Util/Json.php',
+        'Pimf\\Util\\Message'                => '/Pimf/Util/Message.php',
+        'Pimf\\Util\\Serializer'             => '/Pimf/Util/Serializer.php',
+        'Pimf\\Util\\String'                 => '/Pimf/Util/String.php',
+        'Pimf\\Util\\String\\Clean'          => '/Pimf/Util/String/Clean.php',
+        'Pimf\\Util\\Uploaded'               => '/Pimf/Util/Uploaded.php',
+        'Pimf\\Util\\Uploaded\\Factory'      => '/Pimf/Util/Uploaded/Factory.php',
+        'Pimf\\Util\\Uuid'                   => '/Pimf/Util/Uuid.php',
+        'Pimf\\Util\\Validator'              => '/Pimf/Util/Validator.php',
+        'Pimf\\Util\\Validator\\Factory'     => '/Pimf/Util/Validator/Factory.php',
+        'Pimf\\Util\\Xml'                    => '/Pimf/Util/Xml.php',
+        'Pimf\\View'                         => '/Pimf/View.php',
+        'Pimf\\View\\Haanga'                 => '/Pimf/View/Haanga.php',
+        'Pimf\\View\\Json'                   => '/Pimf/View/Json.php',
+        'Pimf\\View\\Twig'                   => '/Pimf/View/Twig.php'
+      );
+    }
+
+    if (isset($classes[$class])) {
+      require __DIR__ . '/core' . $classes[$class];
+    }
+
+    return false;
+  }
+);
+// @codeCoverageIgnoreEnd

+ 25 - 0
php-pimf/pimf-framework/composer.json

@@ -0,0 +1,25 @@
+{
+    "name": "gjerokrsteski/pimf-framework",
+    "description": "Micro framework for PHP that emphasises minimalism and simplicity",
+    "version": "1.8.*",
+    "license": "BSD",
+    "homepage": "http://pimf-framework.de",
+    "minimum-stability": "stable",
+    "keywords": ["framework", "pimf"],
+    "support": {
+        "email": "[email protected]",
+        "wiki": "https://github.com/gjerokrsteski/pimf/wiki",
+        "source": "https://github.com/gjerokrsteski/pimf-framework"
+    },
+    "authors" : [
+    {
+      "name" : "Gjero Krsteski",
+      "email" : "[email protected]",
+      "homepage" : "http://krsteski.de/",
+      "role" : "Developer"
+    }
+  ],
+  "require" : {
+    "php" : ">=5.3"
+  }
+}

+ 222 - 0
php-pimf/pimf-framework/core/Pimf/Application.php

@@ -0,0 +1,222 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c) Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+use Pimf\Util\String as Str;
+
+/**
+ * Provides a facility for applications which provides reusable resources,
+ * common-based bootstrapping and dependency checking.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ */
+final class Application
+{
+  const VERSION = '1.8.6';
+
+  /**
+   * Mechanism used to do some initial config before a Application runs.
+   *
+   * @param array $conf   The array of configuration options.
+   * @param array $server Array of information such as headers, paths, and script locations.
+   *
+   * @return boolean|null
+   */
+  public static function bootstrap(array $conf, array $server = array())
+  {
+    $problems = array();
+
+    try {
+
+      ini_set('default_charset', $conf['encoding']);
+      date_default_timezone_set($conf['timezone']);
+
+      self::registerLocalEnvironment($conf, $server);
+      self::setupErrorHandling($conf);
+      self::loadPdoDriver($conf);
+      self::loadRoutes($conf['app']['routeable'], BASE_PATH . 'app/' . $conf['app']['name'] . '/routes.php');
+      self::loadListeners(BASE_PATH . 'app/' . $conf['app']['name'] . '/events.php');
+
+    } catch (\Exception $exception) {
+      $problems[] = $exception->getMessage();
+    }
+
+    self::reportIf($problems, PHP_VERSION);
+  }
+
+  /**
+   * Please bootstrap first, than run the application!
+   *
+   * Run a application, let application accept a request, route the request,
+   * dispatch to controller/action, render response and return response to client finally.
+   *
+   * @param array $get    Array of variables passed to the current script via the URL parameters.
+   * @param array $post   Array of variables passed to the current script via the HTTP POST method.
+   * @param array $cookie Array of variables passed to the current script via HTTP Cookies.
+   *
+   * @throws \LogicException If application not bootstrapped.
+   * @return void
+   */
+  public static function run(array $get, array $post, array $cookie)
+  {
+    $cli = array();
+    if (Sapi::isCli()) {
+      $cli = Cli::parse((array)Registry::get('env')->argv);
+      if (count($cli) < 1 || isset($cli['list'])) {
+        Cli::absorb();
+        exit(0);
+      }
+    }
+
+    $conf       = Registry::get('conf');
+    $prefix     = Str::ensureTrailing('\\', $conf['app']['name']);
+    $repository = BASE_PATH . 'app/' . $conf['app']['name'] . '/Controller';
+
+    if (isset($cli['controller']) && $cli['controller'] == 'core') {
+      $prefix     = 'Pimf\\';
+      $repository = BASE_PATH . 'pimf-framework/core/Pimf/Controller';
+    }
+
+    $resolver = new Resolver(new Request($get, $post, $cookie, $cli), $repository, $prefix);
+
+    $sessionized = (Sapi::isWeb() && $conf['session']['storage'] !== '');
+
+    if ($sessionized) {
+      Session::load();
+    }
+
+    $pimf = $resolver->process();
+
+    if ($sessionized) {
+      Session::save();
+      Cookie::send();
+    }
+
+    $pimf->render();
+  }
+
+  /**
+   * @param array $conf
+   * @param array $server
+   */
+  private static function registerLocalEnvironment(array $conf, array $server)
+  {
+    Registry::set('conf', $conf);
+    Registry::set('env', new Environment($server));
+    Registry::set('logger', new Logger($conf['bootstrap']['local_temp_directory']));
+
+    Registry::get('logger')->init();
+  }
+
+  /**
+   * @param array $conf
+   */
+  private static function setupErrorHandling(array $conf)
+  {
+    if ($conf['environment'] == 'testing') {
+      error_reporting(E_ALL | E_STRICT);
+    } else {
+
+      set_exception_handler(
+        function ($exception) {
+          Error::exception($exception);
+        }
+      );
+
+      set_error_handler(
+        function ($code, $error, $file, $line) {
+          Error::native($code, $error, $file, $line);
+        }
+      );
+
+      register_shutdown_function(
+        function () {
+          Error::shutdown();
+        }
+      );
+
+      error_reporting(-1);
+    }
+  }
+
+  /**
+   * @param array $conf
+   */
+  private static function loadPdoDriver(array $conf)
+  {
+    $dbConf = $conf[$conf['environment']]['db'];
+
+    if (is_array($dbConf) && $conf['environment'] != 'testing') {
+      Registry::set('em', new EntityManager(Pdo\Factory::get($dbConf), $conf['app']['name']));
+    }
+  }
+
+  /**
+   * @param boolean $routeable
+   * @param string  $routes Path to routes definition file.
+   */
+  private static function loadRoutes($routeable, $routes)
+  {
+    if ($routeable === true && file_exists($routes)) {
+
+      Registry::set('router', new Router());
+
+      foreach ((array)(include $routes) as $route) {
+
+        Registry::get('router')->map($route);
+
+      }
+    }
+  }
+
+  /**
+   * @param string $events Path to event listeners
+   */
+  private static function loadListeners($events)
+  {
+    if (file_exists($events)) {
+      include_once $events;
+    }
+  }
+
+  /**
+   * @param array $problems
+   * @param float $version
+   * @param bool  $die
+   *
+   * @return array|void
+   */
+  private static function reportIf(array $problems, $version, $die = true)
+  {
+    if (version_compare($version, 5.3) == -1) {
+      $problems[] = 'You have PHP ' . $version . ' and you need 5.3 or higher!';
+    }
+
+    if (!empty($problems)) {
+      return ($die === true) ? die(implode(PHP_EOL . PHP_EOL, $problems)) : $problems;
+    }
+  }
+
+  /**
+   * PIMF Application can not be cloned.
+   */
+  private function __clone() { }
+
+  /**
+   * Stopping the PHP process for PHP-FastCGI users to speed up some PHP queries.
+   */
+  public static function finish()
+  {
+    if (function_exists('fastcgi_finish_request')) {
+      fastcgi_finish_request();
+    }
+  }
+}

+ 115 - 0
php-pimf/pimf-framework/core/Pimf/Cache.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+use Pimf\Util\String;
+use Pimf\Cache\Storages as CS;
+
+/**
+ * Cache usage
+ *
+ * <code>
+ *    // Get the default cache storage instance
+ *    $storage = Cache::storage();
+ *
+ *    // Get a specific cache storage instance by name
+ *    $storage = Cache::storage('memcached');
+ *
+ *    // Call the "get" method on the default cache storage
+ *    $name = Cache::get('name');
+ *
+ *    // Call the "put" method on the default cache storage
+ *    Cache::put('name', 'Robin', 15);
+ * </code>
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Cache
+{
+  /**
+   * All of the active cache storages.
+   *
+   * @var \Pimf\Cache\Storages\Storage[]
+   */
+  public static $storages = array();
+
+  /**
+   * Get a cache storage instance.
+   *
+   * @param string $storage
+   *
+   * @return CS\Apc|CS\Dba|CS\File|CS\Memcached|CS\Memory|CS\Pdo|CS\Redis|CS\WinCache
+   */
+  public static function storage($storage = 'memory')
+  {
+    if (!isset(static::$storages[$storage])) {
+      static::$storages[$storage] = static::factory($storage);
+    }
+
+    return static::$storages[$storage];
+  }
+
+  /**
+   * Create a new cache storage instance.
+   *
+   * @param string $storage
+   *
+   * @return CS\Apc|CS\Dba|CS\File|CS\Memcached|CS\Memory|CS\Pdo|CS\Redis|CS\WinCache
+   * @throws \RuntimeException
+   */
+  protected static function factory($storage)
+  {
+    $conf = Registry::get('conf');
+
+    switch ($storage) {
+      case 'apc':
+        return new CS\Apc($conf['cache']['key']);
+
+      case 'file':
+        return new CS\File($conf['cache']['storage_path']);
+
+      case 'pdo':
+        return new CS\Pdo(Pdo\Factory::get($conf['cache']['database']), $conf['cache']['key']);
+
+      case 'memcached':
+        return new CS\Memcached(Memcached::connection(), $conf['cache']['key']);
+
+      case 'memory':
+        return new CS\Memory();
+
+      case 'redis':
+        return new CS\Redis(Redis::database());
+
+      case 'wincache':
+        return new CS\WinCache($conf['cache']['key']);
+
+      case 'dba':
+        return new CS\Dba(String::ensureTrailing('/', $conf['cache']['storage_path']) . $conf['cache']['key']);
+
+      default:
+        throw new \RuntimeException("Cache storage {$storage} is not supported.");
+    }
+  }
+
+  /**
+   * Magic Method for calling the methods on the default cache storage.
+   *
+   * @param $method
+   * @param $parameters
+   *
+   * @return mixed
+   */
+  public static function __callStatic($method, $parameters)
+  {
+    return call_user_func_array(
+      array(static::storage(), $method), $parameters
+    );
+  }
+}

+ 110 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Apc.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Apc extends Storage
+{
+  /**
+   * The cache key from the cache configuration file.
+   *
+   * @var string
+   */
+  protected $key;
+
+  /**
+   * Is APCu is supported.
+   *
+   * @var bool
+   */
+  protected $apcu = false;
+
+  /**
+   * Create a new APC cache storage instance.
+   *
+   * @param string $key
+   */
+  public function __construct($key)
+  {
+    $this->key  = (string)$key;
+    $this->apcu = function_exists('apcu_fetch');
+  }
+
+  /**
+   * Retrieve an item from the cache storage.
+   *
+   * @param string $key
+   *
+   * @return mixed
+   */
+  protected function retrieve($key)
+  {
+    return $this->apcu ? apcu_fetch($this->key . $key) : apc_fetch($this->key . $key);
+  }
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * <code>
+   *    // Put an item in the cache for 15 minutes
+   *    Cache::put('name', 'Robin', 15);
+   * </code>
+   *
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   *
+   * @return bool
+   */
+  public function put($key, $value, $minutes)
+  {
+    return $this->apcu
+      ? apcu_store('' . $this->key . $key, $value, (int)$minutes * 60)
+      : apc_store('' . $this->key . $key, $value, (int)$minutes * 60
+      );
+  }
+
+  /**
+   * Write an item to the cache that lasts forever.
+   *
+   * @param  string $key
+   * @param  mixed  $value
+   *
+   * @return boolean
+   */
+  public function forever($key, $value)
+  {
+    return $this->put($key, $value, 0);
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   *
+   * @return bool
+   */
+  public function forget($key)
+  {
+    return $this->apcu ? apcu_delete($key) : apc_delete($key);
+  }
+
+  /**
+   * Remove all items from the cache.
+   *
+   * @return void
+   */
+  public function flush()
+  {
+    $this->apcu ? apcu_clear_cache() : apc_clear_cache('user');
+  }
+}

+ 240 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Dba.php

@@ -0,0 +1,240 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * This class provides the functionality required to store
+ * and retrieve PHP strings, integers or arrays.
+ *
+ * It uses the database (dbm-style) abstraction layer for persistence.
+ * Even instances of SimpleXMLElement can be stored. You don't have
+ * to matter about the size of the cache-file. It depends on the free
+ * space of your disk.
+ *
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Dba extends Storage
+{
+  /**
+   * @var resource
+   */
+  protected $dba;
+
+  /**
+   * @var resource
+   */
+  protected $handler;
+
+  /**
+   * @var string
+   */
+  protected $file;
+
+  /**
+   * @param string  $file    the cache-file.
+   *
+   * @param string  $handler the dba handler.
+   *
+   * You have to install one of this handlers before use.
+   *
+   * cdb      = Tiny Constant Database - for reading.
+   * cdb_make = Tiny Constant Database - for writing.
+   * db4      = Oracle Berkeley DB 4   - for reading and writing.
+   * qdbm     = Quick Database Manager - for reading and writing.
+   * gdbm     = GNU Database Manager   - for reading and writing.
+   * flatfile = default dba extension  - for reading and writing.
+   *
+   * Use flatfile-handler only when you cannot install one,
+   * of the libraries required by the other handlers,
+   * and when you cannot use bundled cdb handler.
+   *
+   * @param string  $mode    For read/write access, database creation if it doesn't currently exist.
+   *
+   * @param boolean $persistently
+   *
+   * @throws \RuntimeException If no DBA extension or handler installed.
+   */
+  public function __construct($file, $handler = 'flatfile', $mode = 'c', $persistently = true)
+  {
+    if (false === extension_loaded('dba')) {
+      throw new \RuntimeException('The DBA extension is required for this wrapper, but the extension is not loaded');
+    }
+
+    if (false === in_array($handler, dba_handlers(false))) {
+      throw new \RuntimeException('The ' . $handler . ' handler is required for the DBA extension, but the handler is not installed');
+    }
+
+    $this->dba = (true === $persistently) ? dba_popen($file, $mode, $handler) : dba_open($file, $mode, $handler);
+
+    $this->file    = $file;
+    $this->handler = $handler;
+  }
+
+  /**
+   * Closes an open dba resource
+   *
+   * @return void
+   */
+  public function __destruct()
+  {
+    if ($this->dba) {
+      @dba_close($this->dba);
+      $this->dba = null;
+    }
+  }
+
+  /**
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   *
+   * @return bool
+   */
+  public function put($key, $value, $minutes)
+  {
+    if ($minutes <= 0) {
+      return;
+    }
+
+    $value = $this->expiration($minutes) . serialize($value);
+
+    if (true === $this->has($key)) {
+      return dba_replace($key, $value, $this->dba);
+    }
+
+    return dba_insert($key, $value, $this->dba);
+  }
+
+  /**
+   * @param string $key
+   * @param null $default
+   *
+   * @return bool|mixed|null
+   */
+  public function get($key, $default = null)
+  {
+    $res = $this->retrieve($key);
+
+    if (false === $res) {
+      $this->forget($key);
+
+      return false;
+    }
+
+    return $res;
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return bool|mixed
+   */
+  protected function retrieve($key)
+  {
+    $value = dba_fetch($key, $this->dba);
+
+    if (false === $value) {
+      return false;
+    }
+
+    // compare the timestamp to the current time when we read the value.
+    if (time() >= substr($value, 0, 10)) {
+      return $this->forget($key);
+    }
+
+    return unserialize(substr($value, 10));
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return boolean
+   */
+  public function forget($key)
+  {
+    if (false === is_resource($this->dba)) {
+      return false;
+    }
+
+    return dba_delete($key, $this->dba);
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return boolean
+   */
+  public function has($key)
+  {
+    return dba_exists($key, $this->dba);
+  }
+
+  /**
+   * Write an item to the cache for five years.
+   *
+   * @param $key
+   * @param $value
+   *
+   * @return boolean
+   */
+  public function forever($key, $value)
+  {
+    return $this->put($key, $value, 2628000);
+  }
+
+  /**
+   * Cleans and optimizes the cache from all expired entries.
+   *
+   * @return bool
+   */
+  public function clean()
+  {
+    $dba = $this->dba;
+    $key = dba_firstkey($dba);
+
+    while ($key !== false && $key !== null) {
+      $this->retrieve($key);
+      $key = dba_nextkey($dba);
+    }
+
+    return dba_optimize($dba);
+  }
+
+  /**
+   * Flush the whole storage.
+   *
+   * @return bool
+   */
+  public function flush()
+  {
+    if (file_exists($this->file)) {
+
+      // We close the dba file before deleting
+      // and reopen on next use.
+      $this->__destruct();
+
+      @unlink($this->file);
+
+      clearstatcache();
+
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * @return string
+   */
+  public function getFile()
+  {
+    return $this->file;
+  }
+}

+ 109 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/File.php

@@ -0,0 +1,109 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class File extends Storage
+{
+  /**
+   * The path to which the cache files should be written.
+   *
+   * @var string
+   */
+  protected $path;
+
+  /**
+   * @param $path
+   */
+  public function __construct($path)
+  {
+    $this->path = $path;
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return bool|mixed|null
+   */
+  protected function retrieve($key)
+  {
+    if (!file_exists($this->path . $key)) {
+      return null;
+    }
+
+    // compare the timestamp to the current time when we read the file.
+    if (time() >= substr($cache = file_get_contents($this->path . $key), 0, 10)) {
+      return $this->forget($key);
+    }
+
+    return unserialize(substr($cache, 10));
+  }
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * <code>
+   *    // Put an item in the cache for 15 minutes
+   *    Cache::put('name', 'Robin', 15);
+   * </code>
+   *
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   *
+   * @return int|void
+   */
+  public function put($key, $value, $minutes)
+  {
+    if ((int)$minutes <= 0) {
+      return null;
+    }
+
+    $value = $this->expiration($minutes) . serialize($value);
+
+    return file_put_contents($this->path . $key, $value, LOCK_EX);
+  }
+
+  /**
+   * Write an item to the cache for five years.
+   *
+   * @param $key
+   * @param $value
+   *
+   * @return int|void
+   */
+  public function forever($key, $value)
+  {
+    return $this->put($key, $value, 2628000);
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   *
+   * @return bool
+   */
+  public function forget($key)
+  {
+    if (file_exists($this->path . $key)) {
+
+      @unlink($this->path . $key);
+
+      clearstatcache();
+
+      return true;
+    }
+
+    return false;
+  }
+}

+ 96 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Memcached.php

@@ -0,0 +1,96 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Memcached extends Storage
+{
+  /**
+   * The Memcache instance.
+   *
+   * @var \Memcached
+   */
+  public $memcache;
+
+  /**
+   * The cache key from the cache configuration file.
+   *
+   * @var string
+   */
+  protected $key;
+
+  /**
+   * @param \Memcached $memcache
+   * @param            $key
+   */
+  public function __construct(\Memcached $memcache, $key)
+  {
+    $this->key      = $key;
+    $this->memcache = $memcache;
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return mixed
+   */
+  protected function retrieve($key)
+  {
+    if (($cache = $this->memcache->get($this->key . $key)) !== false) {
+      return $cache;
+    }
+  }
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * <code>
+   *    // Put an item in the cache for 15 minutes
+   *    Cache::put('name', 'Robin', 15);
+   * </code>
+   *
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   *
+   * @return bool|void
+   */
+  public function put($key, $value, $minutes)
+  {
+    return $this->memcache->set($this->key . $key, $value, $minutes * 60);
+  }
+
+  /**
+   * Write an item to the cache that lasts forever.
+   *
+   * @param $key
+   * @param $value
+   *
+   * @return bool|void
+   */
+  public function forever($key, $value)
+  {
+    return $this->put($key, $value, 0);
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   *
+   * @return bool|void
+   */
+  public function forget($key)
+  {
+    return $this->memcache->delete($this->key . $key);
+  }
+}

+ 85 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Memory.php

@@ -0,0 +1,85 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Memory extends Storage
+{
+  /**
+   * The in-memory array of cached items.
+   *
+   * @var array
+   */
+  public $storage = array();
+
+  /**
+   * Retrieve an item from the cache storage.
+   *
+   * @param string $key
+   *
+   * @return mixed|null
+   */
+  protected function retrieve($key)
+  {
+    if (array_key_exists($key, $this->storage)) {
+      return $this->storage[$key];
+    }
+
+    return null;
+  }
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * <code>
+   *    // Put an item in the cache for 15 minutes
+   *    Cache::put('name', 'Robin', 15);
+   * </code>
+   *
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   */
+  public function put($key, $value, $minutes)
+  {
+    $this->storage[$key] = $value;
+  }
+
+  /**
+   * Write an item to the cache that lasts forever.
+   *
+   * @param $key
+   * @param $value
+   */
+  public function forever($key, $value)
+  {
+    $this->put($key, $value, 0);
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   */
+  public function forget($key)
+  {
+    unset($this->storage[$key]);
+  }
+
+  /**
+   * Flush the entire cache.
+   */
+  public function flush()
+  {
+    $this->storage = array();
+  }
+}

+ 136 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Pdo.php

@@ -0,0 +1,136 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Pdo extends Storage
+{
+  /**
+   * The cache key from the cache configuration file.
+   *
+   * @var string
+   */
+  protected $key;
+
+  /**
+   * @var \Pimf\Database
+   */
+  protected $pdo;
+
+  /**
+   * Create a new database cache storage instance.
+   *
+   * @param \Pimf\Database $pdo
+   * @param string $key
+   */
+  public function __construct(\Pimf\Database $pdo, $key)
+  {
+    $this->pdo = $pdo;
+    $this->key = (string)$key;
+  }
+
+  /**
+   * Retrieve an item from the cache storage.
+   *
+   * @param string $key
+   *
+   * @return mixed|void
+   */
+  protected function retrieve($key)
+  {
+    $sth = $this->pdo->prepare(
+      'SELECT * FROM pimf_cache WHERE key = :key'
+    );
+
+    $sth->bindValue(':key', $this->key . $key);
+    $sth->execute();
+
+    $cache = $sth->fetchObject();
+
+    if ($cache instanceof \stdClass) {
+
+      if (time() >= $cache->expiration) {
+        return $this->forget($key);
+      }
+
+      return unserialize($cache->value);
+    }
+  }
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * <code>
+   *    // Put an item in the cache for 15 minutes
+   *    Cache::put('name', 'Robin', 15);
+   * </code>
+   *
+   * @param  string $key
+   * @param  mixed  $value
+   * @param  int    $minutes
+   *
+   * @return bool
+   */
+  public function put($key, $value, $minutes)
+  {
+    $key        = $this->key . $key;
+    $value      = serialize($value);
+    $expiration = $this->expiration($minutes);
+
+    try {
+      $sth = $this->pdo->prepare(
+        "INSERT INTO pimf_cache (key, value, expiration) VALUES (:key, :value, :expiration)"
+      );
+    } catch (\Exception $exception) {
+      $sth = $this->pdo->prepare(
+        "UPDATE pimf_cache SET value = :value, expiration = :expiration WHERE key = :key"
+      );
+    }
+
+    $sth->bindValue(':key', $key);
+    $sth->bindValue(':value', $value);
+    $sth->bindValue(':expiration', $expiration);
+
+    return $sth->execute();
+  }
+
+  /**
+   * Write an item to the cache for five years.
+   *
+   * @param $key
+   * @param $value
+   *
+   * @return bool
+   */
+  public function forever($key, $value)
+  {
+    return $this->put($key, $value, 2628000);
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   *
+   * @return boolean
+   */
+  public function forget($key)
+  {
+    $sth = $this->pdo->prepare(
+      "DELETE FROM pimf_cache WHERE key = :key"
+    );
+
+    $sth->bindValue(':key', $this->key . $key);
+
+    return $sth->execute();
+  }
+}

+ 98 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Redis.php

@@ -0,0 +1,98 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * Redis usage
+ *
+ * <code>
+ *    // Put an item in the cache for 15 minutes
+ *    Cache::put('name', 'Robin', 15);
+ * </code>
+ *
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Redis extends Storage
+{
+  /**
+   * The Redis database instance.
+   *
+   * @var \Pimf\Redis
+   */
+  protected $redis;
+
+  /**
+   * @param \Pimf\Redis $redis
+   */
+  public function __construct(\Pimf\Redis $redis)
+  {
+    $this->redis = $redis;
+  }
+
+  /**
+   * Determine if an item exists in the cache.
+   *
+   * @param string $key
+   *
+   * @return bool
+   */
+  public function has($key)
+  {
+    return ($this->redis->get($key) !== null);
+  }
+
+  /**
+   * Retrieve an item from the cache storage.
+   *
+   * @param string $key
+   *
+   * @return mixed
+   */
+  protected function retrieve($key)
+  {
+    if (!is_null($cache = $this->redis->get($key))) {
+      return unserialize($cache);
+    }
+  }
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   */
+  public function put($key, $value, $minutes)
+  {
+    $this->forever($key, $value);
+    $this->redis->expire($key, $minutes * 60);
+  }
+
+  /**
+   * Write an item to the cache that lasts forever.
+   *
+   * @param string $key
+   * @param $value
+   */
+  public function forever($key, $value)
+  {
+    $this->redis->set($key, serialize($value));
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   */
+  public function forget($key)
+  {
+    $this->redis->del($key);
+  }
+}

+ 137 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Storage.php

@@ -0,0 +1,137 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+abstract class Storage
+{
+  /**
+   * Determine if an item exists in the cache.
+   *
+   * @param $key
+   *
+   * @return bool
+   */
+  public function has($key)
+  {
+    return ($this->get($key) !== null);
+  }
+
+  /**
+   * Get an item from the cache.
+   *
+   * <code>
+   *    // Get an item from the cache storage
+   *    $name = Cache::storage('name');
+   *
+   *    // Return a default value if the requested item isn't cached
+   *    $name = Cache::get('name', 'Robin');
+   * </code>
+   *
+   * @param      $key
+   * @param null $default
+   *
+   * @return mixed|null
+   */
+  public function get($key, $default = null)
+  {
+    return (!is_null($item = $this->retrieve($key))) ? $item : $default;
+  }
+
+  /**
+   * Retrieve an item from the cache storage.
+   *
+   * @param string $key
+   *
+   * @return mixed
+   */
+  abstract protected function retrieve($key);
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * <code>
+   *    // Put an item in the cache for 15 minutes
+   *    Cache::put('name', 'Robin', 15);
+   * </code>
+   *
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   *
+   * @return void
+   */
+  abstract public function put($key, $value, $minutes);
+
+  /**
+   * Get an item from the cache, or cache and return the default value.
+   *
+   * <code>
+   *    // Get an item from the cache, or cache a value for 15 minutes
+   *    $name = Cache::remember('name', 'Robin', 15);
+   *
+   *    // Use a closure for deferred execution
+   *    $count = Cache::remember('count', function () { return User::count(); }, 15);
+   * </code>
+   *
+   * @param string $key
+   * @param mixed  $default
+   * @param int    $minutes
+   * @param string $function
+   *
+   * @return mixed
+   */
+  public function remember($key, $default, $minutes, $function = 'put')
+  {
+    if (!is_null($item = $this->get($key, null))) {
+      return $item;
+    }
+
+    $this->$function($key, $default, $minutes);
+
+    return $default;
+  }
+
+  /**
+   * Get an item from the cache, or cache the default value forever.
+   *
+   * @param string $key
+   * @param mixed  $default
+   *
+   * @return mixed
+   */
+  public function sear($key, $default)
+  {
+    return $this->remember($key, $default, null, 'forever');
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   *
+   * @return boolean
+   */
+  abstract public function forget($key);
+
+  /**
+   * Get the expiration time as a UNIX timestamp.
+   *
+   * @param int $minutes
+   *
+   * @return int
+   */
+  protected function expiration($minutes)
+  {
+    return time() + ($minutes * 60);
+  }
+}

+ 89 - 0
php-pimf/pimf-framework/core/Pimf/Cache/Storages/Wincache.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cache\Storages;
+
+/**
+ * @package Cache_Storages
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Wincache extends Storage
+{
+  /**
+   * The cache key from the cache configuration file.
+   *
+   * @var string
+   */
+  protected $key;
+
+  /**
+   * @param $key
+   */
+  public function __construct($key)
+  {
+    $this->key = $key;
+  }
+
+  /**
+   * Retrieve an item from the cache storage.
+   *
+   * @param string $key
+   *
+   * @return mixed
+   */
+  protected function retrieve($key)
+  {
+    if (($cache = wincache_ucache_get($this->key . $key)) !== false) {
+      return $cache;
+    }
+  }
+
+  /**
+   * Write an item to the cache for a given number of minutes.
+   *
+   * <code>
+   *    // Put an item in the cache for 15 minutes
+   *    Cache::put('name', 'Robin', 15);
+   * </code>
+   *
+   * @param string $key
+   * @param mixed  $value
+   * @param int    $minutes
+   *
+   * @return bool|void
+   */
+  public function put($key, $value, $minutes)
+  {
+    return wincache_ucache_add($this->key . $key, $value, $minutes * 60);
+  }
+
+  /**
+   * Write an item to the cache that lasts forever.
+   *
+   * @param $key
+   * @param $value
+   *
+   * @return bool|void
+   */
+  public function forever($key, $value)
+  {
+    return $this->put($key, $value, 0);
+  }
+
+  /**
+   * Delete an item from the cache.
+   *
+   * @param string $key
+   *
+   * @return bool|void
+   */
+  public function forget($key)
+  {
+    return wincache_ucache_delete($this->key . $key);
+  }
+}

+ 135 - 0
php-pimf/pimf-framework/core/Pimf/Cli.php

@@ -0,0 +1,135 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+use Pimf\Util\String;
+
+/**
+ * A full featured package for managing command-line options and arguments,
+ * it allows the developer to easily build complex command line interfaces.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+final class Cli
+{
+  /**
+   * Prints out a list of CLI commands from the system,
+   * which is defined at the controllers with the "CliAction()" suffix at the method-name.
+   *
+   * @param string $appClr  Path to application controller repository
+   * @param string $coreClr Path to core controller repository
+   * @param string $root    Path to home directory
+   */
+  public static function absorb($appClr = null, $coreClr = null, $root = null)
+  {
+    echo PHP_EOL . 'PIMF v' . \Pimf\Application::VERSION . ' PHP Command Line Interface by Gjero Krsteski' . PHP_EOL;
+
+    echo '+------------------------------------------------------+' . PHP_EOL;
+
+    self::reflect(self::collect($appClr, $coreClr, $root));
+  }
+
+  /**
+   * @param array $classes
+   */
+  public static function reflect(array $classes)
+  {
+    array_map(
+      function ($class) {
+
+        $reflection = new \ReflectionClass($class);
+
+        if ($reflection->isSubclassOf('\Pimf\Controller\Base')) {
+
+          $methods    = $reflection->getMethods();
+          $controller = explode('_', $class);
+
+          echo 'controller: ' . strtolower(end($controller)) . '' . PHP_EOL;
+
+          array_map(
+            function ($method) {
+              if (false !== $command = strstr($method->getName(), 'CliAction', true)) {
+                echo PHP_EOL . ' action: ' . $command . ' ' . PHP_EOL;
+              }
+            }, $methods
+          );
+
+          echo PHP_EOL . '+------------------------------------------------------+' . PHP_EOL;
+        }
+
+      }, $classes
+    );
+  }
+
+  /**
+   * @param string $appClr
+   * @param string $coreClr
+   * @param string $root
+   *
+   * @return array
+   */
+  public static function collect($appClr = null, $coreClr = null, $root = null)
+  {
+    $classes = array();
+    $conf    = Registry::get('conf');
+    $dis     = DIRECTORY_SEPARATOR;
+
+    if (!$root && !$coreClr && !$appClr) {
+      // compute the PIMF framework path restriction.
+      $root    = dirname(dirname(dirname(dirname(__FILE__))));
+      $coreClr = str_replace('/', $dis, $root . '/pimf-framework/core/Pimf/Controller/');
+      $appClr  = str_replace('/', $dis, $root . '/app/' . $conf['app']['name'] . '/Controller/');
+    }
+
+    foreach (array($appClr, $coreClr) as $dir) {
+
+      $iterator
+        = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)), '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH);
+
+      foreach (iterator_to_array($iterator, false) as $file) {
+        $file = str_replace("\\", '/', current($file));
+        $file = str_replace('/', $dis, $file);
+        $name = str_replace(
+          array($root . $dis . 'pimf-framework' . $dis . 'core' . $dis, $root . $dis . 'app' . $dis), '', $file
+        );
+
+        $name      = str_replace($dis, '\\', $name);
+        $name      = str_replace('.php', '', $name);
+        $classes[] = '\\' . $name;
+      }
+    }
+
+    return $classes;
+  }
+
+  /**
+   * @param array $commands
+   *
+   * @return array
+   */
+  public static function parse(array $commands)
+  {
+    $cli = array();
+
+    parse_str(implode('&', array_slice($commands, 1)), $cli);
+
+    $command = current(array_keys((array)$cli, ''));
+
+    if (String::contains($command, ':')) {
+
+      list($controller, $action) = explode(':', $command);
+
+      $cli['controller'] = $controller;
+      $cli['action']     = $action;
+    }
+
+    return $cli;
+  }
+}

+ 81 - 0
php-pimf/pimf-framework/core/Pimf/Cli/Color.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Cli
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cli;
+
+use Pimf\Sapi;
+
+/**
+ * For easily use ANSI console colors in your application.
+ *
+ * @package Cli
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Color
+{
+  protected static $foreground = array(
+      'black'        => '0;30',
+      'dark_gray'    => '1;30',
+      'blue'         => '0;34',
+      'light_blue'   => '1;34',
+      'green'        => '0;32',
+      'light_green'  => '1;32',
+      'cyan'         => '0;36',
+      'light_cyan'   => '1;36',
+      'red'          => '0;31',
+      'light_red'    => '1;31',
+      'purple'       => '0;35',
+      'light_purple' => '1;35',
+      'brown'        => '0;33',
+      'yellow'       => '1;33',
+      'light_gray'   => '0;37',
+      'white'        => '1;37',
+    );
+
+  protected static $background = array(
+      'black'      => '40',
+      'red'        => '41',
+      'green'      => '42',
+      'yellow'     => '43',
+      'blue'       => '44',
+      'magenta'    => '45',
+      'cyan'       => '46',
+      'light_gray' => '47',
+    );
+
+  /**
+   * Returns colored string
+   *
+   * @param string      $string
+   * @param string|null $foregroundColor
+   * @param null        $backgroundColor
+   *
+   * @return string
+   */
+  public static function paint($string, $foregroundColor = 'cyan', $backgroundColor = null)
+  {
+    if (Sapi::isWindows()) {
+      return $string;
+    }
+
+    $colored = "";
+
+    // check if given foreground color found
+    if (isset(self::$foreground[$foregroundColor])) {
+      $colored .= "\033[" . self::$foreground[$foregroundColor] . "m";
+    }
+
+    // check if given background color found
+    if (isset(self::$background[$backgroundColor])) {
+      $colored .= "\033[" . static::$background[$backgroundColor] . "m";
+    }
+
+    // add string and end coloring
+    return $colored . $string . "\033[0m";
+  }
+}

+ 103 - 0
php-pimf/pimf-framework/core/Pimf/Cli/Std.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * Cli
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Cli;
+
+/**
+ * Responsible for accessing I/O streams that allow access to PHP's own input and output streams.
+ *
+ * @package Cli
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Std
+{
+  /**
+   * @var resource
+   */
+  private $handle;
+
+  /**
+   * @param string $stream
+   */
+  public function __construct($stream = 'php://stdin')
+  {
+    $this->handle = fopen($stream, 'r');
+  }
+
+  /**
+   * @return string
+   */
+  public function value()
+  {
+    return substr(fgets($this->handle, 1024), 0, -1);
+  }
+
+  public function __destruct()
+  {
+    fclose($this->handle);
+  }
+
+  /**
+   * Allow direct access to the corresponding input stream of the PHP process.
+   *
+   * @param string $prompt
+   * @param string $validation A regex pattern
+   *
+   * <code>
+   *
+   *  Have a look at the examples for $validation:
+   *
+   *  Regular Expression  | Will match...
+   *  -------------------------------------------------------------
+   *  .*                  | Not empty
+   *  foo                 | The string "foo"
+   *  ^foo                | "foo" at the start of a string
+   *  foo$                | "foo" at the end of a string
+   *  ^foo$               | "foo" when it is alone on a string
+   *  [abc]               | a, b, or c
+   *  [a-z]               | Any lowercase letter
+   *  [^A-Z]              | Any character that is not a uppercase letter
+   *  (gif|jpg)           | Matches either "gif" or "jpeg"
+   *  [a-z]+              | One or more lowercase letters
+   *  [0-9\.\-]           | Аny number, dot, or minus sign
+   *
+   * </code>
+   *
+   * @return string
+   */
+  public function read($prompt, $validation = "/.*/")
+  {
+    $value = '';
+
+    while (true) {
+
+      echo "Please enter a " . $prompt . ":\n";
+
+      $value = $this->value();
+
+      if ($this->valid($validation, $value)) {
+        break;
+      }
+
+      echo "[ Value format for " . $prompt . " is invalid! ]\n";
+    }
+
+    return $value;
+  }
+
+  /**
+   * @param string $validation A regex pattern
+   * @param string $value
+   *
+   * @return bool
+   */
+  public function valid($validation, $value)
+  {
+    return strlen($value) > 0 && preg_match($validation, $value);
+  }
+}

+ 26 - 0
php-pimf/pimf-framework/core/Pimf/Contracts/Arrayable.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Contracts;
+
+/**
+ * A simply interface to get instance as an array.
+ *
+ * @package Contracts
+ * @author  Gjero Krsteski <[email protected]>
+ */
+interface Arrayable
+{
+  /**
+   * Get the instance as an array.
+   *
+   * @return array
+   */
+  public function toArray();
+
+}

+ 28 - 0
php-pimf/pimf-framework/core/Pimf/Contracts/Cleanable.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Contracts;
+
+/**
+ * A simply interface to delete all expired data from persistent storage of the instance.
+ *
+ * @package Contracts
+ * @author  Gjero Krsteski <[email protected]>
+ */
+interface Cleanable
+{
+  /**
+   * Delete all expired instance-data from persistent storage.
+   *
+   * @param int $expiration
+   *
+   * @return mixed
+   */
+  public function clean($expiration);
+
+}

+ 28 - 0
php-pimf/pimf-framework/core/Pimf/Contracts/Jsonable.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Contracts;
+
+/**
+ * A simply interface to get instance to its JSON representation.
+ *
+ * @package Contracts
+ * @author  Gjero Krsteski <[email protected]>
+ */
+interface Jsonable
+{
+  /**
+   * Convert the object to its JSON representation.
+   *
+   * @param  int $options
+   *
+   * @return string
+   */
+  public function toJson($options = 0);
+
+}

+ 26 - 0
php-pimf/pimf-framework/core/Pimf/Contracts/MessageProvider.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Contracts;
+
+/**
+ * A simply interface to get messages for the instance.
+ *
+ * @package Contracts
+ * @author  Gjero Krsteski <[email protected]>
+ */
+interface MessageProvider
+{
+  /**
+   * Get the messages for the instance.
+   *
+   * @return \Pimf\Util\Message[]
+   */
+  public function getMessages();
+
+}

+ 26 - 0
php-pimf/pimf-framework/core/Pimf/Contracts/Renderable.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Contracts;
+
+/**
+ * A simply interface to give the view-adapters teh contents of the object.
+ *
+ * @package Contracts
+ * @author  Gjero Krsteski <[email protected]>
+ */
+interface Renderable
+{
+  /**
+   * Get the evaluated contents of the object.
+   *
+   * @return string
+   */
+  public function render();
+
+}

+ 26 - 0
php-pimf/pimf-framework/core/Pimf/Contracts/Reunitable.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Contracts;
+
+/**
+ * A simply interface to give the view-adapters ro re-unite the template an the variables.
+ *
+ * @package Contracts
+ * @author  Gjero Krsteski <[email protected]>
+ */
+interface Reunitable
+{
+  /**
+   * Puts the template an the variables together.
+   *
+   * @throws \Exception
+   * @return string
+   */
+  public function reunite();
+}

+ 110 - 0
php-pimf/pimf-framework/core/Pimf/Controller/Base.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Controller
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Controller;
+
+use \Pimf\Param, \Pimf\Registry, \Pimf\Sapi, \Pimf\Controller\Exception as Bomb, \Pimf\Request, \Pimf\Util\Header, \Pimf\Url,
+  \Pimf\Response;
+
+/**
+ * Defines the general controller behaviour - you have to extend it.
+ *
+ * @package Controller
+ * @author  Gjero Krsteski <[email protected]>
+ */
+abstract class Base
+{
+  /**
+   * @var \Pimf\Request
+   */
+  protected $request;
+
+  /**
+   * @var \Pimf\Response
+   */
+  protected $response;
+
+  /**
+   * @param Request  $request
+   * @param Response $response
+   */
+  public function __construct(\Pimf\Request $request, \Pimf\Response $response = null)
+  {
+    $this->request  = $request;
+    $this->response = $response;
+  }
+
+  abstract public function indexAction();
+
+  /**
+   * Method to show the content.
+   *
+   * @return mixed
+   * @throws \Exception If not supported request method or bad controller
+   */
+  public function render()
+  {
+    $conf = Registry::get('conf');
+
+    if (Sapi::isCli() && $conf['environment'] == 'production') {
+
+      $suffix = 'CliAction';
+      $action = $this->request->fromCli()->get('action', 'index');
+
+    } else {
+
+      $suffix        = 'Action';
+      $bag           = 'from' . ucfirst(strtolower($this->response->getMethod()));
+      $action        = $this->request->{$bag}()->get('action', 'index');
+
+      if ($conf['app']['routeable'] === true) {
+
+        $target = Registry::get('router')->find();
+
+        if ($target instanceof \Pimf\Route\Target) {
+
+          $action = $target->getAction();
+
+          Request::$getData = new Param((array)Request::stripSlashesIfMagicQuotes(
+            array_merge($target->getParams(), Request::$getData->getAll())
+          ));
+        }
+      }
+    }
+
+    $action = strtolower($action) . $suffix;
+
+    if (method_exists($this, 'init')) {
+      call_user_func(array($this, 'init'));
+    }
+
+    if (!method_exists($this, $action)) {
+      throw new Bomb("no action '{$action}' defined at controller " . get_class($this));
+    }
+
+    return call_user_func(array($this, $action));
+  }
+
+  /**
+   * Prepares the response object to return an HTTP Redirect response to the client.
+   *
+   * @param string  $route     The redirect destination like controller/action
+   * @param boolean $permanent If permanent redirection or not.
+   * @param boolean $exit
+   */
+  public function redirect($route, $permanent = false, $exit = true)
+  {
+    $url = Url::compute($route);
+
+    Header::clear();
+
+    ($permanent === true) ? Header::sendMovedPermanently() : Header::sendFound();
+
+    Header::toLocation($url, $exit);
+  }
+}

+ 139 - 0
php-pimf/pimf-framework/core/Pimf/Controller/Core.php

@@ -0,0 +1,139 @@
+<?php
+/**
+ * Controller
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Controller;
+
+use Pimf\Registry, Pimf\Util\String, Pimf\Cli\Std, Pimf\Pdo\Factory, \Pimf\Controller\Exception as Bomb, Pimf\Util\File;
+
+/**
+ * @package Controller
+ * @author  Gjero Krsteski <[email protected]>
+ * @codeCoverageIgnore
+ */
+class Core extends Base
+{
+  /**
+   * Because it is a PIMF restriction!
+   */
+  public function indexAction()
+  {
+
+  }
+
+  /**
+   * Checks the applications architecture and creates some security and safety measures.
+   */
+  public function initCliAction()
+  {
+    clearstatcache();
+
+    $conf = Registry::get('conf');
+    $app  = 'app/' . $conf['app']['name'] . '/';
+
+    $assets = array(
+      BASE_PATH . $app . '_session/',
+      BASE_PATH . $app . '_cache/',
+      BASE_PATH . $app . '_database/',
+      BASE_PATH . $app . '_templates/',
+    );
+
+    echo 'Check app assets' . PHP_EOL;
+
+    foreach ($assets as $asset) {
+
+      if (!is_dir($asset)) {
+        echo "[ Please create '$asset' directory! ]" . PHP_EOL;
+      }
+
+      if (!is_writable($asset)) {
+        echo "[ Please make '$asset' writable! ]" . PHP_EOL;
+      }
+    }
+
+    echo 'Secure root directory' . PHP_EOL;
+    chmod(BASE_PATH, 0755);
+
+    echo 'Secure .htaccess' . PHP_EOL;
+    chmod(BASE_PATH . '.htaccess', 0644);
+
+    echo 'Secure index.php' . PHP_EOL;
+    chmod(BASE_PATH . 'index.php', 0644);
+
+    echo 'Secure autoload.core.php' . PHP_EOL;
+    chmod(BASE_PATH . 'pimf-framework/autoload.core.php', 0644);
+
+    echo 'Create logging files' . PHP_EOL;
+    $handle = fopen($file = $conf['bootstrap']['local_temp_directory'] . 'pimf-logs.txt', "at+");
+    fclose($handle);
+    chmod($file, 0777);
+    $handle = fopen($file = $conf['bootstrap']['local_temp_directory'] . 'pimf-warnings.txt', "at+");
+    fclose($handle);
+    chmod($file, 0777);
+    $handle = fopen($file = $conf['bootstrap']['local_temp_directory'] . 'pimf-errors.txt', "at+");
+    fclose($handle);
+    chmod($file, 0777);
+
+    clearstatcache();
+  }
+
+  public function create_session_tableCliAction()
+  {
+    $std  = new Std();
+    $type = $std->read('database type [mysql|sqlite]', '(mysql|sqlite)');
+
+    var_dump(
+      $this->createTable($type, 'session')
+    );
+  }
+
+  public function create_cache_tableCliAction()
+  {
+    $std  = new Std();
+    $type = $std->read('database type [mysql|sqlite]', '(mysql|sqlite)');
+
+    var_dump(
+      $this->createTable($type, 'cache')
+    );
+  }
+
+  /**
+   * @param string $type
+   * @param string $for
+   *
+   * @return bool
+   * @throws \DomainException
+   */
+  protected function createTable($type, $for)
+  {
+    $type = trim($type);
+
+    try {
+      $pdo = $file = null;
+
+      $conf = Registry::get('conf');
+
+      switch ($for) {
+        case 'cache':
+          $pdo  = Factory::get($conf['cache']['database']);
+          $file = 'create-cache-table-' . $type . '.sql';
+          break;
+        case 'session':
+          $pdo  = Factory::get($conf['session']['database']);
+          $file = 'create-session-table-' . $type . '.sql';
+          break;
+      }
+
+      $file = str_replace('/', DS, BASE_PATH . 'pimf-framework/core/Pimf/_database/' . $file);
+
+      return $pdo->exec(file_get_contents(new File($file))) or print_r($pdo->errorInfo(), true);
+
+    } catch (\PDOException $pdoe) {
+      throw new Bomb($pdoe->getMessage());
+    }
+  }
+}

+ 22 - 0
php-pimf/pimf-framework/core/Pimf/Controller/Exception.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * Controller
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Controller;
+
+/**
+ * Use this exception when you want slightly to show in a application.
+ *
+ * @package Controller
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ * @codeCoverageIgnore
+ */
+class Exception extends \DomainException
+{
+
+}

+ 199 - 0
php-pimf/pimf-framework/core/Pimf/Cookie.php

@@ -0,0 +1,199 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * Using the cookie
+ *
+ * <code>
+ *    // Get the value of the "favorite" cookie
+ *    $favorite = Cookie::get('favorite');
+ *
+ *    // Get the value of a cookie or return a default value
+ *    $favorite = Cookie::get('framework', 'Pimf');
+ *
+ *    // Set the value of the "favorite" cookie
+ *    Cookie::put('favorite', 'Pimf');
+ *
+ *    // Set the value of the "favorite" cookie for twenty minutes
+ *    Cookie::put('favorite', 'Pimf', 20);
+ *
+ *    // Set a cookie that should last one year
+ *    Cookie::forever('favorite', 'Blue');
+ *
+ * </code>
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Cookie
+{
+  /**
+   * How long is forever (in minutes)?
+   *
+   * @var int
+   */
+  const FOREVER = 2628000;
+
+  /**
+   * The cookies that have been set.
+   *
+   * @var array
+   */
+  public static $jar = array();
+
+  /**
+   * Determine if a cookie exists.
+   *
+   * @param  string $name
+   *
+   * @return bool
+   */
+  public static function has($name)
+  {
+    return (static::get($name) !== null);
+  }
+
+  /**
+   * Get the value of a cookie.
+   *
+   * @param      $name
+   * @param null $default
+   *
+   * @return null|string
+   */
+  public static function get($name, $default = null)
+  {
+    if (isset(static::$jar[$name])) {
+      return static::parse(static::$jar[$name]['value']);
+    }
+
+    $cookie = Request::$cookieData;
+
+    if (!is_null($value = $cookie->get($name))) {
+      return static::parse($value);
+    }
+
+    return $default;
+  }
+
+  /**
+   * Set the value of a cookie.
+   *
+   * @param        $name
+   * @param        $value
+   * @param int    $expiration
+   * @param string $path
+   * @param null   $domain
+   * @param bool   $secure
+   *
+   * @return bool
+   * @throws \RuntimeException
+   */
+  public static function put($name, $value, $expiration = 0, $path = '/', $domain = null, $secure = false)
+  {
+    if ($expiration !== 0) {
+      $expiration = time() + ($expiration * 60);
+    }
+
+    $value = static::hash($value) . '+' . $value;
+
+    // If we are attempting to send a secure cookie over the insecure HTTP.
+    $conf = Registry::get('conf');
+
+    if ($secure === true and $conf['ssl'] === false) {
+      throw new \RuntimeException("Attempting to set secure cookie over HTTP!");
+    }
+
+    static::$jar[$name] = compact('name', 'value', 'expiration', 'path', 'domain', 'secure');
+
+    return true;
+  }
+
+  /**
+   * Set a "permanent" cookie. The cookie will last for one year.
+   *
+   * @param        $name
+   * @param        $value
+   * @param string $path
+   * @param null   $domain
+   * @param bool   $secure
+   *
+   * @return bool
+   */
+  public static function forever($name, $value, $path = '/', $domain = null, $secure = false)
+  {
+    return static::put($name, $value, static::FOREVER, $path, $domain, $secure);
+  }
+
+  /**
+   * Delete a cookie.
+   *
+   * @param        string $name
+   * @param string $path
+   * @param null   $domain
+   * @param bool   $secure
+   *
+   * @return bool
+   */
+  public static function forget($name, $path = '/', $domain = null, $secure = false)
+  {
+    return static::put($name, null, -2000, $path, $domain, $secure);
+  }
+
+  /**
+   * Hash the given cookie value.
+   *
+   * @param string $value
+   *
+   * @return string
+   */
+  public static function hash($value)
+  {
+    $conf = Registry::get('conf');
+
+    return hash_hmac('sha1', $value, $conf['app']['key']);
+  }
+
+  /**
+   * Parse a hash fingerprinted cookie value.
+   *
+   * @param string $value
+   *
+   * @return string
+   */
+  protected static function parse($value)
+  {
+    $segments = explode('+', $value);
+
+    // check if the cookie is invalid.
+    if (!(count($segments) >= 2)) {
+      return null;
+    }
+
+    $value = implode('+', array_slice($segments, 1));
+
+    // check the SHA-1 hash from the cookie.
+    if ($segments[0] == static::hash($value)) {
+      return $value;
+    }
+
+    return null;
+  }
+
+  /**
+   * Send along with the rest of the HTTP headers.
+   */
+  public static function send()
+  {
+    foreach (static::$jar as $cookie) {
+      setcookie($cookie['name'], $cookie['value'], $cookie['expiration'], $cookie['path'], $cookie['domain'], $cookie['secure'], true);
+    }
+  }
+}

+ 71 - 0
php-pimf/pimf-framework/core/Pimf/DataMapper/Base.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * DataMapper
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\DataMapper;
+
+use Pimf\Util\IdentityMap;
+
+/**
+ * For mapping the domain models to the persistence layer.
+ *
+ * Defines the general behaviour for the data-mappers - you have to extend it.
+ *
+ * You have to use it if you want to persist data.
+ *
+ * @package DataMapper
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ * @method insert($entity)
+ * @method update($entity)
+ * @method delete($entity)
+ * @method find($key)
+ */
+abstract class Base
+{
+  /**
+   * @var \PDO The database resource.
+   */
+  protected $pdo;
+
+  /**
+   * @var \Pimf\Util\IdentityMap
+   */
+  protected $identityMap;
+
+  /**
+   * @param \PDO $pdo
+   */
+  public function __construct(\PDO $pdo)
+  {
+    $this->pdo         = $pdo;
+    $this->identityMap = new IdentityMap();
+  }
+
+  public function __destruct()
+  {
+    unset($this->identityMap, $this->pdo);
+  }
+
+  /**
+   * Makes a given model-property accessible.
+   *
+   * @param object $model
+   * @param int    $value
+   * @param string $property
+   *
+   * @return mixed
+   */
+  public function reflect($model, $value, $property = 'id')
+  {
+    $attribute = new \ReflectionProperty($model, $property);
+    $attribute->setAccessible(true);
+    $attribute->setValue($model, $value);
+
+    return $model;
+  }
+}

+ 82 - 0
php-pimf/pimf-framework/core/Pimf/Database.php

@@ -0,0 +1,82 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Database extends \PDO
+{
+  /**
+   * The current transaction level.
+   *
+   * @var int
+   */
+  protected $transLevel = 0;
+
+  /**
+   * Check database drivers that support savepoints.
+   *
+   * @return bool
+   */
+  public function nestable()
+  {
+    return in_array(
+      $this->getAttribute(\PDO::ATTR_DRIVER_NAME), array("pgsql", "mysql")
+    );
+  }
+
+  /**
+   * @return bool|void
+   */
+  public function beginTransaction()
+  {
+    if ($this->transLevel == 0 || !$this->nestable()) {
+      parent::beginTransaction();
+    } else {
+      $this->exec("SAVEPOINT LEVEL{$this->transLevel}");
+    }
+
+    $this->transLevel++;
+  }
+
+  /**
+   * @return bool|void
+   */
+  public function commit()
+  {
+    $this->transLevel--;
+
+    if ($this->transLevel == 0 || !$this->nestable()) {
+      parent::commit();
+    } else {
+      $this->exec("RELEASE SAVEPOINT LEVEL{$this->transLevel}");
+    }
+  }
+
+  /**
+   * @return bool|void
+   * @throws \PDOException
+   */
+  public function rollBack()
+  {
+    if ($this->transLevel == 0) {
+      throw new \PDOException('trying to rollback without a transaction-start', 25000);
+    }
+
+    $this->transLevel--;
+
+    if ($this->transLevel == 0 || !$this->nestable()) {
+      parent::rollBack();
+    } else {
+      $this->exec("ROLLBACK TO SAVEPOINT LEVEL{$this->transLevel}");
+    }
+  }
+}

+ 103 - 0
php-pimf/pimf-framework/core/Pimf/EntityManager.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+use Pimf\DataMapper\Base;
+
+/**
+ * Based on PDO it is a general manager for data persistence and object relational mapping.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ */
+class EntityManager extends Base
+{
+  /**
+   * @var string The namespace name of data-mappers repository.
+   */
+  protected $prefix;
+
+  /**
+   * @param \PDO   $pdo
+   * @param string $prefix The data-mappers repository name.
+   */
+  public function __construct(\PDO $pdo, $prefix = '\Pimf')
+  {
+    parent::__construct($pdo);
+    $this->prefix = $prefix . '\DataMapper\\';
+  }
+
+  /**
+   * @param string $entity The name of the data-mapper class.
+   *
+   * @return Base
+   * @throws \BadMethodCallException If no entity fount at the repository.
+   */
+  public function load($entity)
+  {
+    $entity = $this->prefix . ucfirst($entity);
+
+    if (true === $this->identityMap->hasId($entity)) {
+      return $this->identityMap->getObject($entity);
+    }
+
+    if (!class_exists($entity)) {
+      throw new \BadMethodCallException('entity "' . $entity . '" not found at the data-mapper repository');
+    }
+
+    $model = new $entity($this->pdo);
+
+    $this->identityMap->set($entity, $model);
+
+    return $this->identityMap->getObject($entity);
+  }
+
+  /**
+   * @return bool
+   */
+  public function beginTransaction()
+  {
+    return $this->pdo->beginTransaction();
+  }
+
+  /**
+   * @return bool
+   */
+  public function commitTransaction()
+  {
+    return $this->pdo->commit();
+  }
+
+  /**
+   * @return bool
+   */
+  public function rollbackTransaction()
+  {
+    return $this->pdo->rollBack();
+  }
+
+  /**
+   * @param string $entity
+   *
+   * @return Base
+   */
+  public function __get($entity)
+  {
+    return $this->load($entity);
+  }
+
+  /**
+   * @return Database
+   */
+  public function getPDO()
+  {
+    return $this->pdo;
+  }
+}

+ 231 - 0
php-pimf/pimf-framework/core/Pimf/Environment.php

@@ -0,0 +1,231 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+namespace Pimf;
+
+/**
+ * Server and execution environment information.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ * @property string X_REQUESTED_WITH       It is sent by the Ajax functions of most major Frameworks
+ * @property string HTTP                   Is the application running under HTTP protocol?
+ * @property string HTTPS                  Is the application running under HTTPS protocol?
+ * @property string SERVER_PROTOCOL        Name and revision of the information protocol via which the page was requested; i.e. 'HTTP/1.0';
+ * @property string CONTENT_LENGTH         The Content-Length
+ * @property string HOST                   The name of the server host under which the current script is executing.
+ * @property string SERVER_NAME            The name of the server host under which the current script is executing.
+ * @property string SERVER_PORT            Get the port
+ * @property string PHP_SELF               Filename of the currently executing script.
+ * @property string SCRIPT_NAME            Get Script Name (physical path)
+ * @property string PATH_INFO              Get Path Info (virtual path)
+ * @property string X_FORWARDED_FOR        Do on your machine is behind the proxy than us it instead of REMOTE_ADDR
+ * @property string CLIENT_IP              Get the client ip address
+ * @property string REMOTE_ADDR            The IP address from which the user is viewing the current page.
+ * @property string HTTP_REFERER           Get Referer - it cannot really be trusted.
+ * @property string USER_AGENT             Contents of the User-Agent from the current request, if there is one.
+ * @property string HTTP_USER_AGENT        Contents of the User-Agent: header from the current request, if there is one.
+ * @property string REQUEST_URI            The URI which was given in order to access this page; for instance, '/index.html'.
+ * @property string REQUEST_METHOD         Which request method was used to access the page; i.e. 'GET', 'HEAD', 'POST', 'PUT'.
+ * @property string HTTP_IF_MODIFIED_SINCE Get request header from Apache even on PHP running as a CGI
+ * @property string HTTP_IF_NONE_MATCH     Get request header from Apache even on PHP running as a CGI
+ */
+class Environment
+{
+  /**
+   * @var Param
+   */
+  private $envData;
+
+  /**
+   * @param array $envData Like $_SERVER
+   */
+  public function __construct(array $envData)
+  {
+    $this->envData = new Param($envData);
+  }
+
+  /**
+   * @return Param
+   */
+  public function getData()
+  {
+    return $this->envData;
+  }
+
+  /**
+   * @param $key
+   *
+   * @return string
+   */
+  public function __get($key)
+  {
+    return $this->envData->get($key);
+  }
+
+  /**
+   * Is this an AJAX request?
+   *
+   * @return bool
+   */
+  public function isAjax()
+  {
+    return $this->X_REQUESTED_WITH === 'XMLHttpRequest';
+  }
+
+  /**
+   * Is the application running under HTTP protocol?
+   *
+   * @return bool
+   */
+  public function isHttp()
+  {
+    return (bool)$this->HTTP;
+  }
+
+  /**
+   * Is the application running under HTTPS protocol?
+   *
+   * @return bool
+   */
+  public function isHttps()
+  {
+    return $this->HTTPS === 'on';
+  }
+
+  /**
+   * Get Host
+   *
+   * @return string
+   */
+  public function getHost()
+  {
+    if ($this->HOST) {
+
+      if (strpos($this->HOST, ':') !== false) {
+        $hostParts = explode(':', $this->HOST);
+
+        return $hostParts[0];
+      }
+
+      return $this->HOST;
+    }
+
+    return $this->SERVER_NAME;
+  }
+
+  /**
+   * Get Host with Port
+   *
+   * @return string
+   */
+  public function getHostWithPort()
+  {
+    return '' . $this->getHost() . ':' . $this->SERVER_PORT;
+  }
+
+  /**
+   * Physical path + virtual path
+   *
+   * @return string
+   */
+  public function getPath()
+  {
+    return $this->SCRIPT_NAME . $this->PATH_INFO;
+  }
+
+  /**
+   * Get remote IP
+   *
+   * @return string
+   */
+  public function getIp()
+  {
+    if ($this->X_FORWARDED_FOR) {
+      return $this->X_FORWARDED_FOR;
+    }
+
+    if ($this->CLIENT_IP) {
+      return $this->CLIENT_IP;
+    }
+
+    if ($this->SERVER_NAME) {
+      return gethostbyname($this->SERVER_NAME);
+    }
+
+    return $this->REMOTE_ADDR;
+  }
+
+  /**
+   * Get User Agent
+   *
+   * @return string|null
+   */
+  public function getUserAgent()
+  {
+    if ($this->USER_AGENT) {
+      return $this->USER_AGENT;
+    }
+
+    if ($this->HTTP_USER_AGENT) {
+      return $this->HTTP_USER_AGENT;
+    }
+
+    return null;
+  }
+
+  /**
+   * Gives you the current page URL
+   *
+   * @return string
+   */
+  public function getUrl()
+  {
+    $protocol = strpos(strtolower($this->PATH_INFO), 'https') === false ? 'http' : 'https';
+
+    return $protocol . '://' . $this->getHost();
+  }
+
+  /**
+   * Try to get a request header.
+   *
+   * @param string $header
+   *
+   * @return array
+   */
+  public function getRequestHeader($header)
+  {
+    $header = str_replace('-', '_', strtoupper($header));
+    $value  = $this->{'HTTP_' . $header};
+
+    if (!$value) {
+      $headers = $this->getRequestHeaders();
+      $value   = !empty($headers[$header]) ? $headers[$header] : null;
+    }
+
+    return $value;
+  }
+
+  /**
+   * Try to determine all request headers
+   *
+   * @return array
+   */
+  public function getRequestHeaders()
+  {
+    $headers = array();
+
+    foreach ($this->envData->getAll() as $key => $value) {
+      if ('HTTP_' === substr($key, 0, 5)) {
+        $headers[substr($key, 5)] = $value;
+      }
+    }
+
+    return $headers;
+  }
+}

+ 134 - 0
php-pimf/pimf-framework/core/Pimf/Error.php

@@ -0,0 +1,134 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+use Pimf\Util\Header;
+
+/**
+ * Defines the default exception handler if an exception is not caught within a try/catch block.
+ * Execution will stop after the exception_handler is called.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Error
+{
+  /**
+   * Handle an exception and display the exception report.
+   *
+   * @param \Exception $exception
+   * @param boolean    $exit
+   */
+  public static function exception(\Exception $exception, $exit = true)
+  {
+    static::log($exception);
+
+    ob_get_length() > 0 and ob_get_level() and ob_end_clean();
+
+    $conf = Registry::get('conf');
+
+    if (isset($conf['error']['debug_info']) && $conf['error']['debug_info'] === true) {
+      echo static::format($exception);
+      if ($exit) {
+        exit;
+      }
+    }
+
+    Header::clear();
+
+    if ($exception instanceof \Pimf\Controller\Exception
+      || $exception instanceof \Pimf\Resolver\Exception
+    ) {
+      Event::first('404', array($exception));
+      Header::sendNotFound(null, $exit);
+    } else {
+      Event::first('500', array($exception));
+      Header::sendInternalServerError(null, $exit);
+    }
+  }
+
+  /**
+   * If detailed errors are enabled, just format the exception into
+   * a simple error message and display it.
+   *
+   * @param \Exception $exception
+   *
+   * @return string
+   */
+  public static function format(\Exception $exception)
+  {
+    if (Sapi::isCli()) {
+      return
+        "+++ Untreated Exception +++" . PHP_EOL . "Message: " . $exception->getMessage() . PHP_EOL . "Location: " . $exception->getFile()
+        . " on line " . $exception->getLine() . PHP_EOL . "Stack Trace: " . PHP_EOL . $exception->getTraceAsString() . PHP_EOL;
+    }
+
+    return "<html><h2>Untreated Exception</h2>
+      <h3>Message:</h3>
+      <pre>" . $exception->getMessage() . "</pre>
+      <h3>Location:</h3>
+      <pre>" . $exception->getFile() . " on line " . $exception->getLine() . "</pre>
+      <h3>Stack Trace:</h3>
+      <pre>" . $exception->getTraceAsString() . "</pre></html>";
+  }
+
+  /**
+   * Handle a native PHP error as an ErrorException.
+   *
+   * @param int    $code
+   * @param string $error
+   * @param string $file
+   * @param int    $line
+   */
+  public static function native($code, $error, $file, $line)
+  {
+    if (error_reporting() === 0) {
+      return;
+    }
+
+    // create an ErrorException for the PHP error
+    $exception = new \ErrorException($error, $code, 0, $file, $line);
+
+    $conf = Registry::get('conf');
+
+    if (in_array($code, (array)$conf['error']['ignore_levels'])) {
+      return static::log($exception);
+    }
+
+    // display the ErrorException
+    static::exception($exception);
+  }
+
+  /**
+   * Handle the PHP shutdown event.
+   */
+  public static function shutdown()
+  {
+    // if a fatal error occurred
+    $error = error_get_last();
+
+    if (!is_null($error)) {
+      static::exception(new \ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
+    }
+  }
+
+  /**
+   * Log an exception.
+   *
+   * @param \Exception $exception
+   */
+  public static function log(\Exception $exception)
+  {
+    $conf = Registry::get('conf');
+
+    if (isset($conf['error']['log']) && $conf['error']['log'] === true) {
+      Registry::get('logger')->error($exception->getMessage() . ' ' . $exception->getTraceAsString());
+    }
+  }
+}

+ 176 - 0
php-pimf/pimf-framework/core/Pimf/Event.php

@@ -0,0 +1,176 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * Provides a great way to build de-coupled applications and allows plug-ins to tap
+ * into the core of your application without modifying the code.
+ *
+ * Register a callback for a given event.
+ *
+ * <code>
+ *    // register a callback for the "start" event
+ *    Efs_Event::listen('start', function () {return 'Started!';});
+ *
+ *    // register an object instance callback for the given event
+ *    Efs_Event::listen('event', array($object, 'method'));
+ * </code>
+ *
+ * Fire an event and return the first response.
+ *
+ * <code>
+ *    // fire the "start" event
+ *    $response = Efs_Event::first('start');
+ *
+ *    // fire the "start" event passing an array of parameters
+ *    $response = Efs_Event::first('start', array('Pimf', 'Framework'));
+ * </code>
+ *
+ * Fire an event so that all listeners are called.
+ *
+ * <code>
+ *    // fire the "start" event
+ *    $responses = Efs_Event::fire('start');
+ *
+ *    // fire the "start" event passing an array of parameters
+ *    $responses = Efs_Event::fire('start', array('Pimf', 'Framework'));
+ *
+ *    // fire multiple events with the same parameters
+ *    $responses = Efs_Event::fire(array('start', 'loading'), $parameters);
+ * </code>
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Event
+{
+  /**
+   * All registered events.
+   *
+   * @var array
+   */
+  protected static $events = array();
+
+  /**
+   * Determine if an event has any registered listeners.
+   *
+   * @param string $event
+   *
+   * @return bool
+   */
+  public static function listeners($event)
+  {
+    return isset(static::$events[$event]);
+  }
+
+  /**
+   * Register a callback for a given event.
+   *
+   * @param string $event
+   * @param mixed  $callback
+   *
+   * @return void
+   */
+  public static function listen($event, $callback)
+  {
+    static::$events[$event][] = $callback;
+  }
+
+  /**
+   * Override all callbacks for a given event with a new callback.
+   *
+   * @param string $event
+   * @param mixed  $callback
+   *
+   * @return void
+   */
+  public static function override($event, $callback)
+  {
+    static::clear($event);
+    static::listen($event, $callback);
+  }
+
+  /**
+   * Clear all event listeners for a given event.
+   *
+   * @param string $event
+   *
+   * @return void
+   */
+  public static function clear($event)
+  {
+    unset(static::$events[$event]);
+  }
+
+  /**
+   * Fire an event and return the first response.
+   *
+   * @param string       $event
+   * @param \Exception[] $parameters
+   *
+   * @return mixed
+   */
+  public static function first($event, $parameters = array())
+  {
+    $responses = static::fire($event, $parameters);
+
+    return reset($responses);
+  }
+
+  /**
+   * Fire an event and return the first response.
+   * Execution will be halted after the first valid response is found.
+   *
+   * @param string $event
+   * @param array  $parameters
+   *
+   * @return mixed
+   */
+  public static function until($event, $parameters = array())
+  {
+    return static::fire($event, $parameters, true);
+  }
+
+  /**
+   * Fire an event so that all listeners are called.
+   *
+   * @param string $events
+   * @param array  $parameters
+   * @param bool   $halt
+   *
+   * @return array|null
+   */
+  public static function fire($events, $parameters = array(), $halt = false)
+  {
+    $responses  = array();
+    $parameters = (array)$parameters;
+
+    // If the event has listeners, iterate through them and call each listener,
+    // passing in the parameters.
+    foreach ((array)$events as $event) {
+
+      if (static::listeners($event)) {
+
+        foreach (static::$events[$event] as $callback) {
+          $response = call_user_func_array($callback, $parameters);
+
+          // If the event is set to halt,
+          // return the first response that is not null.
+          if ($halt and !is_null($response)) {
+            return $response;
+          }
+
+          $responses[] = $response;
+        }
+      }
+    }
+
+    return $halt ? null : $responses;
+  }
+}

+ 234 - 0
php-pimf/pimf-framework/core/Pimf/Logger.php

@@ -0,0 +1,234 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * Logger with common logging options into a file.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Logger
+{
+  /**
+   * @var resource
+   */
+  private $handle;
+
+  /**
+   * @var resource
+   */
+  private $warnHandle;
+
+  /**
+   * @var resource
+   */
+  private $errorHandle;
+
+  /**
+   * @var string
+   */
+  private $storageDir;
+
+  /**
+   * @var bool
+   */
+  private $separator;
+
+  /**
+   * @param string $localeStorageDir Use better the local TMP dir or dir with mod 777.
+   * @param bool   $trailingSeparator
+   */
+  public function __construct($localeStorageDir, $trailingSeparator = true)
+  {
+    $this->storageDir = (string)$localeStorageDir;
+    $this->separator  = (bool)$trailingSeparator;
+  }
+
+  /**
+   * @throws \RuntimeException If something went wrong on creating the log dir and file.
+   */
+  public function init()
+  {
+    if (is_resource($this->errorHandle)
+      && is_resource($this->handle)
+      && is_resource($this->warnHandle)
+    ) {
+      return;
+    }
+
+    if (!is_dir($this->storageDir)) {
+      mkdir($this->storageDir, 0777);
+    }
+
+    if (true === $this->separator) {
+      $this->storageDir = rtrim(realpath($this->storageDir), '\\/') . DS;
+    }
+
+    $this->handle      = fopen($this->storageDir . "pimf-logs.txt", "at+");
+    $this->warnHandle  = fopen($this->storageDir . "pimf-warnings.txt", "at+");
+    $this->errorHandle = fopen($this->storageDir . "pimf-errors.txt", "at+");
+
+    if (!$this->errorHandle || !$this->handle || !$this->warnHandle) {
+      throw new \RuntimeException("failed to obtain a handle to logger file");
+    }
+  }
+
+  /**
+   * @param string $msg
+   *
+   * @return Logger
+   */
+  public function debug($msg)
+  {
+    if ($this->iniGetBool('display_errors') === true) {
+      $this->write((string)$msg, 'DEBUG');
+    }
+
+    return $this;
+  }
+
+  /**
+   * @param string $msg
+   *
+   * @return Logger
+   */
+  public function warn($msg)
+  {
+    if ($this->iniGetBool('display_errors') === true) {
+      $this->write((string)$msg, 'WARNING');
+    }
+
+    return $this;
+  }
+
+  /**
+   * @param string $msg
+   *
+   * @return Logger
+   */
+  public function error($msg)
+  {
+    $this->write((string)$msg, 'ERROR');
+
+    return $this;
+  }
+
+  /**
+   * @param string $msg
+   *
+   * @return Logger
+   */
+  public function info($msg)
+  {
+    if ($this->iniGetBool('display_errors') === true) {
+      $this->write((string)$msg, 'INFO');
+    }
+
+    return $this;
+  }
+
+  /**
+   * @param        string $msg
+   * @param string $severity
+   */
+  protected function write($msg, $severity = 'DEBUG')
+  {
+    $msg = $this->format($msg, $severity);
+
+    // if severity is WARNING then write to warning file
+    if ($severity == 'WARNING') {
+      fwrite($this->warnHandle, $msg);
+    } // if severity is ERROR then write to error file
+    else if ($severity == 'ERROR') {
+      fwrite($this->errorHandle, $msg);
+    } else {
+      fwrite($this->handle, $msg);
+    }
+  }
+
+  public function __destruct()
+  {
+    if (is_resource($this->handle)
+      && is_resource($this->warnHandle)
+      && is_resource($this->errorHandle)
+    ) {
+
+      if (fclose($this->handle) === false) {
+        // Failure to close the log file
+        $this->error('Logger failed to close the handle to the log file');
+      }
+
+      fclose($this->warnHandle);
+      fclose($this->errorHandle);
+    }
+  }
+
+  /**
+   * Formats the error message in representable manner.
+   *
+   * @param string $message
+   * @param string $severity
+   *
+   * @return string
+   */
+  private function format($message, $severity)
+  {
+    $registry = new Registry();
+    $remoteIP = $registry->env->getIp();
+    $script   = $registry->env->PHP_SELF;
+
+    $msg = date("m-d-Y") . " " . date("G:i:s") . " " . $remoteIP;
+
+    $IPLength       = strlen($remoteIP);
+    $numWhitespaces = 15 - $IPLength;
+
+    for ($i = 0; $i < $numWhitespaces; $i++) {
+      $msg .= " ";
+    }
+
+    $msg .= " " . $severity . ": ";
+
+    // get the file name
+    $lastSlashIndex = strrpos($script, "/");
+    $fileName       = $script;
+
+    if ($lastSlashIndex !== false) {
+      $fileName = substr($script, $lastSlashIndex + 1);
+    }
+
+    $msg .= $fileName . "\t";
+    $msg .= $severity;
+    $msg .= ": " . $message . "\r\n";
+
+    return $msg;
+  }
+
+  /**
+   * @param string $varname
+   *
+   * @return bool
+   */
+  protected function iniGetBool($varname)
+  {
+    $varvalue = ini_get($varname);
+
+    switch (strtolower($varvalue)) {
+      case 'on':
+      case 'yes':
+      case 'true':
+        return 'assert.active' !== $varname;
+      case 'stdout':
+      case 'stderr':
+        return 'display_errors' === $varname;
+      default:
+        return (bool)(int)$varvalue;
+    }
+  }
+}

+ 110 - 0
php-pimf/pimf-framework/core/Pimf/Memcached.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * For use please add the following code to the end of the config.core.php file:
+ *
+ * <code>
+ *
+ * 'cache' => array(
+ *
+ *    'storage' => 'memcached',
+ *       'servers' => array(
+ *           array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100),
+ *        ),
+ *      ),
+ *  ),
+ *
+ * </code>
+ *
+ * Memcached usage:
+ *
+ * <code>
+ *    // Get the Memcache connection and get an item from the cache
+ *    $name = Memcached::connection()->get('name');
+ *
+ *    // Get the Memcache connection and place an item in the cache
+ *    Memcached::connection()->set('name', 'Robin');
+ *
+ *    // Get an item from the Memcache instance
+ *    $name = Memcached::get('name');
+ *
+ *    // Store data on the Memcache server
+ *    Memcached::set('name', 'Robin');
+ * </code>
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ * @method get($key)
+ * @method put($key, $value, $expiration)
+ * @method forget($key);
+ */
+class Memcached
+{
+  /**
+   * @var \Memcached
+   */
+  protected static $connection;
+
+  /**
+   * @return \Memcached
+   */
+  public static function connection()
+  {
+    if (static::$connection === null) {
+      $conf               = Registry::get('conf');
+      static::$connection = static::connect(
+        $conf['cache']['servers']
+      );
+    }
+
+    return static::$connection;
+  }
+
+  /**
+   * Create a new Memcached connection instance.
+   *
+   * @param array $servers
+   * @param null  $memcache
+   *
+   * @return \Memcached|null
+   * @throws \RuntimeException
+   */
+  protected static function connect(array $servers, $memcache = null)
+  {
+    if (!$memcache) {
+      $memcache = new \Memcached();
+    }
+
+    foreach ($servers as $server) {
+      $memcache->addServer($server['host'], $server['port'], $server['weight']);
+    }
+
+    if ($memcache->getVersion() === false) {
+      throw new \RuntimeException('could not establish memcached connection!');
+    }
+
+    return $memcache;
+  }
+
+  /**
+   * Dynamically pass all other method calls to the Memcache instance.
+   *
+   * @param $method
+   * @param $parameters
+   *
+   * @return mixed
+   */
+  public static function __callStatic($method, $parameters)
+  {
+    return call_user_func_array(array(static::connection(), $method), $parameters);
+  }
+}

+ 55 - 0
php-pimf/pimf-framework/core/Pimf/Model/AsArray.php

@@ -0,0 +1,55 @@
+<?php
+/**
+ * Model
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Model;
+
+use Pimf\Contracts\Arrayable;
+
+/**
+ * Returns only protected and public properties of the given model-object. You have to extend it.
+ *
+ * Normally you will use ArrayObject and than method getArrayCopy() to turn Classes to Array, but
+ * with AsArray you have the opportunity to easily intercept the setting of the values at the array.
+ *
+ * Sure if you need it - otherwise please prefers using ArrayObject - is much faster!
+ *
+ * @package Model
+ * @author  Gjero Krsteski <[email protected]>
+ */
+abstract class AsArray implements Arrayable
+{
+  /**
+   * Returns only protected and public properties of the given model-object.
+   * For another properties output format, please override this method.
+   *
+   * @return array A list of properties.
+   */
+  public function toArray()
+  {
+    return $this->map(get_class_vars(get_class($this)));
+  }
+
+  /**
+   * Maps the properties to array with actual values.
+   * For another properties-mapping, please override this method.
+   *
+   * @param array $properties
+   *
+   * @return array
+   */
+  protected function map(array $properties)
+  {
+    $map = array();
+
+    foreach ($properties as $name => $default) {
+      $map[$name] = (true === empty($this->$name)) ? $default : $this->$name;
+    }
+
+    return $map;
+  }
+}

+ 79 - 0
php-pimf/pimf-framework/core/Pimf/Param.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Param
+{
+  /**
+   * @var \ArrayObject|null
+   */
+  protected $data = null;
+
+  /**
+   * @param array $data
+   */
+  public function __construct(array $data = array())
+  {
+    $this->data = new \ArrayObject($data, \ArrayObject::STD_PROP_LIST + \ArrayObject::ARRAY_AS_PROPS);
+  }
+
+  /**
+   * @return array
+   */
+  public function getAll()
+  {
+    return (array)$this->data->getArrayCopy();
+  }
+
+  /**
+   * @param string $index
+   * @param null|string $defaultValue
+   * @param bool   $filtered If you trust foreign input introduced to your PHP code - set to FALSE!
+   *
+   * @return string
+   */
+  public function get($index, $defaultValue = null, $filtered = true)
+  {
+    if ($this->data->offsetExists($index)) {
+
+      if ($filtered === true) {
+        // pretty high-level filtering here...
+        return self::filter($this->data->offsetGet($index));
+      }
+
+      return $this->data->offsetGet($index);
+    }
+
+    return $defaultValue;
+  }
+
+  /**
+   * Never ever (ever) trust foreign input introduced to your PHP code!
+   *
+   * @param array|string $rawData
+   *
+   * @return array|bool|string
+   */
+  public static function filter($rawData)
+  {
+    return is_array($rawData)
+
+      ? array_map(
+        function ($value) {
+          return \Pimf\Util\String\Clean::xss($value);
+        }, $rawData
+      )
+
+      : \Pimf\Util\String\Clean::xss($rawData);
+  }
+}

+ 54 - 0
php-pimf/pimf-framework/core/Pimf/Pdo/Connector.php

@@ -0,0 +1,54 @@
+<?php
+/**
+ * Database
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Pdo;
+
+/**
+ * Abstract class for connections and connection management.
+ *
+ * @package Database
+ * @author  Gjero Krsteski <[email protected]>
+ */
+abstract class Connector
+{
+  /**
+   * The PDO connection options.
+   *
+   * @var array
+   */
+  protected $options = array(
+      \PDO::ATTR_ERRMODE           => \PDO::ERRMODE_EXCEPTION,
+      \PDO::ATTR_ORACLE_NULLS      => \PDO::NULL_NATURAL,
+      \PDO::ATTR_STRINGIFY_FETCHES => false,
+      \PDO::ATTR_EMULATE_PREPARES  => false,
+    );
+
+  /**
+   * Establish a PDO database connection.
+   *
+   * @param array $config
+   *
+   * @return \PDO
+   */
+  abstract public function connect(array $config);
+
+  /**
+   * Get the PDO connection options for the configuration.
+   * Developer specified options will override the default connection options.
+   *
+   * @param array $config
+   *
+   * @return array
+   */
+  protected function options($config)
+  {
+    $options = (isset($config['options'])) ? $config['options'] : array();
+
+    return $this->options + (array)$options;
+  }
+}

+ 49 - 0
php-pimf/pimf-framework/core/Pimf/Pdo/Factory.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * Database
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Pdo;
+
+/**
+ * Creates a PDO connection from the farm of connectors.
+ *
+ * @package Database
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Factory
+{
+  /**
+   * @param array $config
+   *
+   * @return \Pimf\Database
+   * @throws \RuntimeException If no driver specified or no PDO installed.
+   * @throws \UnexpectedValueException
+   */
+  public static function get(array $config)
+  {
+    if (!isset($config['driver']) or !$config['driver']) {
+      throw new \RuntimeException('no driver specified');
+    }
+
+    $driver = strtolower($config['driver']);
+
+    if (!in_array($driver, array('sqlite', 'mysql', 'sqlserver', 'postgre'), true)) {
+      throw new \UnexpectedValueException('PDO driver "' . $driver . '" not supported by PIMF');
+    }
+
+    if (!extension_loaded('pdo') or !extension_loaded('pdo_' . $driver)) {
+      throw new \RuntimeException('Please navigate to "http://php.net/manual/pdo.installation.php" '
+        . ' to find out how to install "PDO" with "pdo_' . $driver . '" on your system!');
+    }
+
+    $driver = '\Pimf\Pdo\\' . ucfirst($driver);
+
+    $pdo = new $driver();
+
+    return $pdo->connect($config);
+  }
+}

+ 45 - 0
php-pimf/pimf-framework/core/Pimf/Pdo/Mysql.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * Database
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Pdo;
+
+/**
+ * Connection management to MySQL.
+ *
+ * @package Database
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Mysql extends Connector
+{
+  /**
+   * @param array $config
+   *
+   * @return \Pimf\Database
+   */
+  public function connect(array $config)
+  {
+    $dsn = "mysql:host={$config['host']};dbname={$config['database']}";
+
+    if (isset($config['port'])) {
+      $dsn .= ";port={$config['port']}";
+    }
+
+    if (isset($config['unix_socket'])) {
+      $dsn .= ";unix_socket={$config['unix_socket']}";
+    }
+
+    $connection = new \Pimf\Database($dsn, $config['username'], $config['password'], $this->options($config));
+
+    // set to UTF-8 which should be fine for most scenarios.
+    if (isset($config['charset'])) {
+      $connection->prepare("SET NAMES '{$config['charset']}'")->execute();
+    }
+
+    return $connection;
+  }
+}

+ 53 - 0
php-pimf/pimf-framework/core/Pimf/Pdo/Postgre.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * Database
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Pdo;
+
+/**
+ * Connection management to PostgreSQL
+ *
+ * @package Database
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Postgre extends Connector
+{
+  protected $options = array(
+      \PDO::ATTR_CASE              => \PDO::CASE_LOWER,
+      \PDO::ATTR_ERRMODE           => \PDO::ERRMODE_EXCEPTION,
+      \PDO::ATTR_ORACLE_NULLS      => \PDO::NULL_NATURAL,
+      \PDO::ATTR_STRINGIFY_FETCHES => false,
+    );
+
+  /**
+   * @param array $config
+   *
+   * @return \Pimf\Database
+   */
+  public function connect(array $config)
+  {
+    $dsn = "pgsql:host={$config['host']};dbname={$config['database']}";
+
+    if (isset($config['port'])) {
+      $dsn .= ";port={$config['port']}";
+    }
+
+    $connection = new \Pimf\Database($dsn, $config['username'], $config['password'], $this->options($config));
+
+    // set to UTF-8 which should be fine for most scenarios.
+    if (isset($config['charset'])) {
+      $connection->prepare("SET NAMES '{$config['charset']}'")->execute();
+    }
+
+    // If a schema has been specified
+    if (isset($config['schema'])) {
+      $connection->prepare("SET search_path TO '{$config['schema']}'")->execute();
+    }
+
+    return $connection;
+  }
+}

+ 36 - 0
php-pimf/pimf-framework/core/Pimf/Pdo/Sqlite.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Database
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Pdo;
+
+/**
+ * Connection management to SQLite.
+ *
+ * @package Database
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Sqlite extends Connector
+{
+  /**
+   * @param array $config
+   *
+   * @return \Pimf\Database
+   */
+  public function connect(array $config)
+  {
+    $options = $this->options($config);
+
+    // SQLite provides supported for "in-memory" databases, which exist only for
+    // lifetime of the request. These are mainly for tests.
+    if ($config['database'] == ':memory:') {
+      return new \Pimf\Database('sqlite::memory:', null, null, $options);
+    }
+
+    return new \Pimf\Database('sqlite:' . $config['database'], null, null, $options);
+  }
+}

+ 46 - 0
php-pimf/pimf-framework/core/Pimf/Pdo/Sqlserver.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * Database
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Pdo;
+
+/**
+ * Connection management to SQL Server
+ *
+ * @package Database
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Sqlserver extends Connector
+{
+  protected $options = array(
+      \PDO::ATTR_CASE              => \PDO::CASE_LOWER,
+      \PDO::ATTR_ERRMODE           => \PDO::ERRMODE_EXCEPTION,
+      \PDO::ATTR_ORACLE_NULLS      => \PDO::NULL_NATURAL,
+      \PDO::ATTR_STRINGIFY_FETCHES => false,
+    );
+
+  /**
+   * @param array $config
+   *
+   * @return \Pimf\Database
+   */
+  public function connect(array $config)
+  {
+    // This connection string format can also be used to connect
+    // to Azure SQL Server databases.
+    $port = (isset($config['port'])) ? ',' . $config['port'] : '';
+
+    //check for dblib for mac users connecting to mssql
+    if (isset($config['dsn_type']) && !empty($config['dsn_type']) and $config['dsn_type'] == 'dblib') {
+      $dsn = "dblib:host={$config['host']}{$port};dbname={$config['database']}";
+    } else {
+      $dsn = "sqlsrv:Server={$config['host']}{$port};Database={$config['database']}";
+    }
+
+    return new \Pimf\Database($dsn, $config['username'], $config['password'], $this->options($config));
+  }
+}

+ 322 - 0
php-pimf/pimf-framework/core/Pimf/Redis.php

@@ -0,0 +1,322 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * Redis usage
+ *
+ * <code>
+ *    // Get the default Redis database instance
+ *    $redis = Redis::db();
+ *
+ *    // Get a specified Redis database instance
+ *    $reids = Redis::db('redis_2');
+ *
+ *    // Execute the GET command for the "name" key
+ *    $name = Redis::db()->run('get', array('name'));
+ *
+ *    // Execute the LRANGE command for the "list" key
+ *    $list = Redis::db()->run('lrange', array(0, 5));
+ *
+ * </code>
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ * @method expire($key, $seconds)
+ * @method set($key, $value)
+ * @method del($key)
+ * @method forget($key)
+ * @method get($key)
+ * @method select($database_id)
+ * @method put($session_id, $session, $lifetime);
+ */
+class Redis
+{
+  /**
+   * The address for the Redis host.
+   *
+   * @var string
+   */
+  protected $host;
+
+  /**
+   * The port on which Redis can be accessed on the host.
+   *
+   * @var int
+   */
+  protected $port;
+
+  /**
+   * The database number the connection selects on load.
+   *
+   * @var int
+   */
+  protected $database;
+
+  /**
+   * The connection to the Redis database.
+   *
+   * @var resource
+   */
+  protected $connection;
+
+  /**
+   * The active Redis database instances.
+   *
+   * @var array
+   */
+  protected static $databases = array();
+
+  /**
+   * Create a new Redis connection instance.
+   *
+   * @param string $host
+   * @param int    $port
+   * @param int    $database
+   */
+  public function __construct($host, $port, $database = 0)
+  {
+    $this->host = $host;
+    $this->port = $port;
+    $this->database = $database;
+  }
+
+  /**
+   * Get a Redis database connection instance.
+   *
+   * The given name should correspond to a Redis database in the configuration file.
+   *
+   * @param string $name
+   *
+   * @return Redis
+   * @throws \RuntimeException
+   */
+  public static function database($name = 'default')
+  {
+    if (!isset(static::$databases[$name])) {
+      $conf = Registry::get('conf');
+
+      if (!isset($conf['cache']['storage']) || $conf['cache']['storage'] != 'redis') {
+        throw new \RuntimeException("Redis database [$name] is not defined.");
+      }
+
+      static::$databases[$name]
+        = new static($conf['cache']['server']['host'], $conf['cache']['server']['port'], $conf['cache']['server']['database']);
+    }
+
+    return static::$databases[$name];
+  }
+
+  /**
+   * Execute a command against the Redis database.
+   *
+   * @param string $method
+   * @param array  $parameters
+   *
+   * @return mixed
+   */
+  public function run($method, $parameters)
+  {
+    fwrite($this->connect(), $this->command($method, (array)$parameters));
+
+    $response = trim(fgets($this->connection, 512));
+
+    return $this->parse($response);
+  }
+
+  /**
+   * Parse and return the response from the Redis database.
+   *
+   * @param string $response
+   *
+   * @return array|string
+   * @throws \RuntimeException
+   */
+  protected function parse($response)
+  {
+    switch (substr($response, 0, 1)) {
+      case '-':
+        throw new \RuntimeException('Redis error: ' . substr(trim($response), 4));
+
+      case '+':
+      case ':':
+        return $this->inline($response);
+
+      case '$':
+        return $this->bulk($response);
+
+      case '*':
+        return $this->multibulk($response);
+
+      default:
+        throw new \RuntimeException("Unknown Redis response: " . substr($response, 0, 1));
+    }
+  }
+
+  /**
+   * Establish the connection to the Redis database.
+   *
+   * @return resource
+   * @throws \RuntimeException
+   */
+  protected function connect()
+  {
+    if (!is_null($this->connection)) {
+      return $this->connection;
+    }
+
+    $this->connection = @fsockopen($this->host, $this->port, $error, $message);
+
+    if ($this->connection === false) {
+      throw new \RuntimeException("Error making Redis connection: {$error} - {$message}");
+    }
+
+    $this->select($this->database);
+
+    return $this->connection;
+  }
+
+  /**
+   * Build the Redis command based from a given method and parameters.
+   *
+   * Redis protocol states that a command should conform to the following format:
+   *
+   *     *<number of arguments> CR LF
+   *     $<number of bytes of argument 1> CR LF
+   *     <argument data> CR LF
+   *     ...
+   *     $<number of bytes of argument N> CR LF
+   *     <argument data> CR LF
+   *
+   * More information regarding the Redis protocol: http://redis.io/topics/protocol
+   *
+   * @param string $method
+   * @param $parameters
+   *
+   * @return string
+   */
+  protected function command($method, $parameters)
+  {
+    $CRLF = "\r\n";
+
+    $command = '*' . (count($parameters) + 1) . $CRLF . '$' . strlen($method) . $CRLF . strtoupper($method) . $CRLF;
+
+    foreach ($parameters as $parameter) {
+      $command .= '$' . strlen($parameter) . $CRLF . $parameter . $CRLF;
+    }
+
+    return $command;
+  }
+
+  /**
+   * Parse and handle an inline response from the Redis database.
+   *
+   * @param string $response
+   *
+   * @return string
+   */
+  protected function inline($response)
+  {
+    return substr(trim($response), 1);
+  }
+
+  /**
+   * Parse and handle a bulk response from the Redis database.
+   *
+   * @param string $head
+   *
+   * @return string
+   */
+  protected function bulk($head)
+  {
+    if ($head == '$-1') {
+      return null;
+    }
+
+    list($read, $response, $size) = array(0, '', substr($head, 1));
+
+    if ($size > 0) {
+      do {
+
+        // Calculate and read the appropriate bytes off of the Redis response.
+        $block = (($remaining = $size - $read) < 1024) ? $remaining : 1024;
+        $response .= fread($this->connection, $block);
+        $read += $block;
+
+      } while ($read < $size);
+    }
+
+    // The response ends with a trailing CRLF.
+    fread($this->connection, 2);
+
+    return $response;
+  }
+
+  /**
+   * Parse and handle a multi-bulk reply from the Redis database.
+   *
+   * @param string $head
+   *
+   * @return array
+   */
+  protected function multibulk($head)
+  {
+    if (($count = substr($head, 1)) == '-1') {
+      return null;
+    }
+
+    $response = array();
+
+    // Iterate through each bulk response in the multi-bulk and parse it out.
+    for ($i = 0; $i < $count; $i++) {
+      $response[] = $this->parse(trim(fgets($this->connection, 512)));
+    }
+
+    return $response;
+  }
+
+  /**
+   * Dynamically make calls to the Redis database.
+   *
+   * @param string $method
+   * @param array $parameters
+   *
+   * @return mixed
+   */
+  public function __call($method, $parameters)
+  {
+    return $this->run($method, $parameters);
+  }
+
+  /**
+   * Dynamically pass static method calls to the Redis instance.
+   *
+   * @param $method
+   * @param $parameters
+   *
+   * @return mixed
+   */
+  public static function __callStatic($method, $parameters)
+  {
+    return static::database()->run($method, $parameters);
+  }
+
+  /**
+   * Close the connection to the Redis database.
+   *
+   * @return void
+   */
+  public function __destruct()
+  {
+    if ($this->connection) {
+      fclose($this->connection);
+    }
+  }
+}

+ 107 - 0
php-pimf/pimf-framework/core/Pimf/Registry.php

@@ -0,0 +1,107 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * A well-known object that other objects can use to find common objects and services.
+ * Acts also as a dependency injection container.
+ *
+ * <code>
+ *  $registry = new Registry();
+ *  $registry->your_key = "123";
+ *
+ *  // or ..
+ *
+ *  Registry::set('your_key', "123")
+ *  Registry::get('your_key')
+ * </code>
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ * @property EntityManager $em
+ * @property Logger        $logger
+ * @property Environment   $env
+ * @property array         $conf
+ * @property router        $router
+ */
+class Registry
+{
+  /**
+   * The temporary storage for the accumulator.
+   *
+   * @var \ArrayObject
+   */
+  protected static $battery;
+
+  /**
+   * Re-initialises the data.
+   *
+   * @return void
+   */
+  protected static function init()
+  {
+    if (!self::$battery) {
+      self::$battery = new \ArrayObject(array(), \ArrayObject::STD_PROP_LIST);
+    }
+  }
+
+  /**
+   * @param mixed $namespace The namespace or identifier.
+   * @param mixed $value     The value.
+   */
+  public function __set($namespace, $value)
+  {
+    self::set($namespace, $value);
+  }
+
+  /**
+   * @param mixed $namespace The namespace or identifier.
+   * @param mixed $value     The value.
+   *
+   * @throws \LogicException If key should be overwritten.
+   */
+  public static function set($namespace, $value)
+  {
+    self::init();
+
+    if (is_resource($value)) {
+      throw new \LogicException('storing resources in a registry is not permitted!');
+    }
+
+    self::$battery->offsetSet($namespace, $value);
+  }
+
+  /**
+   * @param mixed $namespace The namespace or identifier.
+   *
+   * @return mixed
+   */
+  public function __get($namespace)
+  {
+    return self::get($namespace);
+  }
+
+  /**
+   * @param string|integer $namespace The namespace or identifier.
+   * @param mixed          $defaultValue
+   *
+   * @return mixed|null
+   */
+  public static function get($namespace, $defaultValue = null)
+  {
+    self::init();
+
+    if (self::$battery->offsetExists($namespace)) {
+      return self::$battery->offsetGet($namespace);
+    }
+
+    return $defaultValue;
+  }
+}

+ 133 - 0
php-pimf/pimf-framework/core/Pimf/Request.php

@@ -0,0 +1,133 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * Request Manager - for controlled access to the global state of the world.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Request
+{
+  /**
+   * @var Param
+   */
+  public static $postData;
+
+  /**
+   * @var Param
+   */
+  public static $getData;
+
+  /**
+   * @var Param
+   */
+  public static $cookieData;
+
+  /**
+   * @var Param
+   */
+  public static $cliData;
+
+  /**
+   * @param array $getData
+   * @param array $postData
+   * @param array $cookieData
+   * @param array $cliData
+   */
+  public function __construct(array $getData, array $postData = array(), array $cookieData = array(), array $cliData = array())
+  {
+    static::$getData    = new Param((array)self::stripSlashesIfMagicQuotes($getData));
+    static::$postData   = new Param((array)self::stripSlashesIfMagicQuotes($postData));
+    static::$cookieData = new Param($cookieData);
+    static::$cliData    = new Param((array)self::stripSlashesIfMagicQuotes($cliData));
+  }
+
+  /**
+   * HTTP GET variables.
+   *
+   * @return Param
+   */
+  public function fromGet()
+  {
+    return static::$getData;
+  }
+
+  /**
+   * CLI arguments passed to script.
+   *
+   * @return Param
+   */
+  public function fromCli()
+  {
+    return static::$cliData;
+  }
+
+  /**
+   * HTTP POST variables.
+   *
+   * @return Param
+   */
+  public function fromPost()
+  {
+    return static::$postData;
+  }
+
+  /**
+   * HTTP Cookies.
+   *
+   * @return Param
+   */
+  public function fromCookie()
+  {
+    return static::$cookieData;
+  }
+
+  /**
+   * Strip slashes from string or array
+   *
+   * @param      $rawData
+   * @param null $overrideStripSlashes
+   *
+   * @return array|string
+   */
+  public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes = null)
+  {
+    $hasMagicQuotes = function_exists('get_magic_quotes_gpc') ? get_magic_quotes_gpc() : false;
+
+    $strip = !$overrideStripSlashes ? $hasMagicQuotes : $overrideStripSlashes;
+
+    if ($strip) {
+      return self::stripSlashes($rawData);
+    }
+
+    return $rawData;
+  }
+
+  /**
+   * Strip slashes from string or array
+   *
+   * @param $rawData
+   *
+   * @return array|string
+   */
+  public static function stripSlashes($rawData)
+  {
+    return is_array($rawData)
+
+      ? array_map(
+        function ($value) {
+          return \Pimf\Request::stripSlashes($value);
+        }, $rawData
+      )
+
+      : stripslashes($rawData);
+  }
+}

+ 106 - 0
php-pimf/pimf-framework/core/Pimf/Resolver.php

@@ -0,0 +1,106 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+use Pimf\Resolver\Exception as Bomb;
+use Pimf\Util\String as Str;
+
+/**
+ * Resolves the user requests to controller and action.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Resolver
+{
+  /**
+   * @var string
+   */
+  protected $controllerPath;
+
+  /**
+   * @var string
+   */
+  protected $controllerClass;
+
+  /**
+   * @var string
+   */
+  protected $repositoryPath;
+
+  /**
+   * @var Request
+   */
+  protected $request;
+
+  /**
+   * @param Request $request
+   * @param string  $repositoryPath
+   * @param string  $prefix
+   *
+   * @throws Resolver\Exception
+   */
+  public function __construct(Request $request, $repositoryPath = '/Controller', $prefix = 'Pimf\\')
+  {
+    $conf = Registry::get('conf');
+
+    $controllerName = $request->fromGet()->get('controller');
+
+    if ($conf['app']['routeable'] === true) {
+
+      $target = Registry::get('router')->find();
+
+      if ($target instanceof \Pimf\Route\Target) {
+        $controllerName = $target->getController();
+      }
+    }
+
+    if (Sapi::isCli() && $conf['environment'] == 'production') {
+      $controllerName = $request->fromCli()->get('controller');
+    }
+
+    if (!$controllerName) {
+      $controllerName = $conf['app']['default_controller'];
+    }
+
+    $this->repositoryPath  = $repositoryPath;
+    $this->request         = $request;
+    $this->controllerClass = $prefix . 'Controller\\';
+
+    $basepath   = $this->repositoryPath . '/';
+    $controller = ucfirst($controllerName);
+
+    if (Str::isEvilPath($basepath . $controller)) {
+      throw new Bomb('directory traversal attack is not funny!');
+    }
+
+    $this->controllerPath = $basepath . $controller . '.php';
+
+    if (!file_exists($this->controllerPath)) {
+      throw new Bomb('no controller found at the repository path; ' . $this->controllerPath);
+    }
+  }
+
+  /**
+   * @return \Pimf\Controller\Base
+   * @throws \Exception If no controller specified or no controller found at the repository.
+   */
+  public function process()
+  {
+    $path       = str_replace($this->repositoryPath, '', $this->controllerPath);
+    $name       = str_replace('/', $this->controllerClass, $path);
+    $controller = str_replace('.php', '', $name);
+
+    if (!class_exists($controller)) {
+      throw new Bomb('can not load class "' . $controller . '" from the repository');
+    }
+
+    return new $controller($this->request, new Response(Registry::get('env')->REQUEST_METHOD));
+  }
+}

+ 20 - 0
php-pimf/pimf-framework/core/Pimf/Resolver/Exception.php

@@ -0,0 +1,20 @@
+<?php
+/**
+ * Resolver
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Resolver;
+
+/**
+ * @package Resolver
+ * @author  Gjero Krsteski <[email protected]>
+ *
+ * @codeCoverageIgnore
+ */
+class Exception extends \RuntimeException
+{
+
+}

+ 263 - 0
php-pimf/pimf-framework/core/Pimf/Response.php

@@ -0,0 +1,263 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+namespace Pimf;
+
+use \Pimf\Util\Header, Pimf\Util\Json as UtilJson;
+
+/**
+ * Provides a simple interface around the HTTP an HTTPCache-friendly response generating.
+ * Use this class to build and the current HTTP response before it is returned to the client.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Response
+{
+  /**
+   * The request method send by the client-browser.
+   *
+   * @var string
+   */
+  protected $method = null;
+
+  /**
+   * If the response attempts to send any cached headers.
+   *
+   * @var bool
+   */
+  protected static $cached = false;
+
+  /**
+   * Type of the data will be send to the client-browser.
+   *
+   * @var string
+   */
+  protected static $typed = null;
+
+  /**
+   * @param string $requestMethod
+   *
+   * @throws \RuntimeException
+   */
+  public function __construct($requestMethod)
+  {
+    $this->method = '' . strtoupper($requestMethod);
+
+    // it is PIMF framework restriction
+    if (!in_array($this->method, array('POST', 'GET', null))) {
+      throw new \RuntimeException('unsupported request-method given');
+    }
+
+    Header::clear();
+  }
+
+  /**
+   * @return string
+   */
+  public function getMethod()
+  {
+    return $this->method;
+  }
+
+  public function asJSON()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asJSON();
+
+    return $this;
+  }
+
+  public function asHTML()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asTextHTML();
+
+    return $this;
+  }
+
+  public function asPDF()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asPDF();
+
+    return $this;
+  }
+
+  public function asCSV()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asCSV();
+
+    return $this;
+  }
+
+  public function asTEXT()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asTextPlain();
+
+    return $this;
+  }
+
+  public function asZIP()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asZIP();
+
+    return $this;
+  }
+
+  public function asXZIP()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asXZIP();
+
+    return $this;
+  }
+
+  public function asMSWord()
+  {
+    $this->preventMultipleTypes();
+    self::$typed = __FUNCTION__;
+    Header::asMSWord();
+
+    return $this;
+  }
+
+  /**
+   * Sends a download dialog to the browser.
+   *
+   * @param string $stream Can be a file-path or a string.
+   * @param string $name   Name of the stream/file should be shown.
+   * @param boolean $exit Optional for testing
+   */
+  public function sendStream($stream, $name, $exit = true)
+  {
+    Header::clear();
+    Header::sendDownloadDialog($stream, $name, $exit);
+  }
+
+  /**
+   * @param mixed $data
+   * @param bool  $exit
+   */
+  public function send($data, $exit = true)
+  {
+    $body = $data;
+
+    if (self::$typed === 'asJSON') {
+      $body = UtilJson::encode($data);
+    } elseif ($data instanceof \Pimf\View) {
+      $body = $data->render();
+    }
+
+    echo '' . $body;
+    if ($exit) {
+      exit(0);
+    }
+  }
+
+
+  /**
+   * If instead you have a page that has personalization on it
+   * (say, for example, the splash page contains local news as well),
+   * you can set a copy to be cached only by the browser.
+   *
+   * @param int $seconds Interval in seconds
+   *
+   * @return $this
+   */
+  public function cacheBrowser($seconds)
+  {
+    self::preventMultipleCaching();
+    self::$cached = true;
+    Header::cacheBrowser($seconds);
+
+    return $this;
+  }
+
+  /**
+   * If you want to try as hard as possible to keep a page from being cached anywhere.
+   *
+   * @return $this
+   */
+  public function cacheNone()
+  {
+    self::preventMultipleCaching();
+    self::$cached = true;
+    Header::cacheNone();
+
+    return $this;
+  }
+
+  /**
+   * If you want to allow a page to be cached by shared proxies for one minute.
+   *
+   * @param int $seconds Interval in seconds
+   *
+   * @return $this
+   */
+  public function cacheNoValidate($seconds = 60)
+  {
+    self::preventMultipleCaching();
+    self::$cached = true;
+    Header::cacheNoValidate($seconds);
+
+    return $this;
+  }
+
+  /**
+   * Handles setting pages that are always to be revalidated for freshness by any cache.
+   *
+   * @param int $last_modified Timestamp in seconds
+   *
+   * @return $this
+   */
+  public function exitIfNotModifiedSince($last_modified)
+  {
+    self::preventMultipleCaching();
+    self::$cached = true;
+    Header::exitIfNotModifiedSince($last_modified);
+
+    return $this;
+  }
+
+  /**
+   * @throws \RuntimeException
+   */
+  private function preventMultipleTypes()
+  {
+    if (!is_empty(self::$typed)) {
+      Header::clear();
+      throw new \RuntimeException('only one HTTP content-type can be sent!');
+    }
+  }
+
+  /**
+   * @throws \RuntimeException
+   */
+  private function preventMultipleCaching()
+  {
+    if ($this->method != 'GET') {
+      Header::clear();
+      throw new \RuntimeException('HTTP cache headers can only take effect if request was sent via GET method!');
+    }
+
+    if (self::$cached === true) {
+      Header::clear();
+      throw new \RuntimeException('only one HTTP cache-control can be sent!');
+    }
+  }
+}

+ 174 - 0
php-pimf/pimf-framework/core/Pimf/Route.php

@@ -0,0 +1,174 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf;
+
+/**
+ * Route
+ *
+ * This class is a relationship of HTTP method(s), an HTTP URI to create
+ * a Pimf application route. The Pimf application will determine
+ * the one Route object to dispatch for the current HTTP request.
+ *
+ * Each route object will have a URI pattern. This pattern must match the
+ * current HTTP request's URI for the route object to be dispatched by
+ * the Pimf application. The route pattern may contain parameters, segments
+ * prefixed with a colon (:). For example:
+ *
+ *     /controller/:action/:id
+ *
+ * When the route is dispatched, it's parameters array will be populated
+ * with the values of the corresponding HTTP request URI segments.
+ *
+ * @package Pimf
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Route
+{
+  /**
+   * @var bool
+   */
+  private $matched = true;
+
+  /**
+   * @var array
+   */
+  private $params = array();
+
+  /**
+   * The route pattern (e.g. "/controller/:action/:id")
+   *
+   * @var string
+   */
+  private $rule;
+
+  /**
+   * Array of URL parameter names
+   *
+   * @var array
+   */
+  protected $names = array();
+
+  /**
+   * Array of URL parameter names with + at the end
+   *
+   * @var array
+   */
+  protected $namesPath = array();
+
+  /**
+   * Conditions for this route's URL parameters
+   *
+   * @var array
+   */
+  private $conditions;
+
+  /**
+   * @param string $rule
+   * @param array  $target
+   * @param array  $conditions
+   */
+  public function __construct($rule, array $target = array(), array $conditions = array())
+  {
+    $this->rule       = $rule;
+    $this->conditions = $conditions;
+
+    //convert URL params into regex patterns, construct a regex for this route, init params
+    $regex = preg_replace_callback(
+      '#:([\w]+)\+?#', array($this, 'computeUrlRegex'), str_replace(')', ')?', (string)$rule)
+    );
+
+    if (substr($rule, -1) === '/') {
+      $regex .= '?';
+    }
+
+    //cache URL params' names and values if this route matches the current HTTP request
+    $params = array();
+    if (!preg_match('#^' . $regex . '$#', self::computeUri(), $params)) {
+      $this->matched = false;
+
+      return;
+    }
+
+    foreach ($this->names as $name) {
+      if (isset($params[$name])) {
+        if (isset($this->namesPath[$name])) {
+          $this->params[$name] = explode('/', urldecode($params[$name]));
+        } else {
+          $this->params[$name] = urldecode($params[$name]);
+        }
+      }
+    }
+
+    foreach ($target as $key => $value) {
+      $this->params[$key] = $value;
+    }
+  }
+
+  /**
+   * @param array $matches
+   *
+   * @return string
+   */
+  private function computeUrlRegex(array $matches)
+  {
+    $this->names[] = $matches[1];
+
+    if (isset($this->conditions[$matches[1]])) {
+      return '(?P<' . $matches[1] . '>' . $this->conditions[$matches[1]] . ')';
+    }
+
+    if (substr($matches[0], -1) === '+') {
+
+      $this->namesPath[$matches[1]] = 1;
+
+      return '(?P<' . $matches[1] . '>.+)';
+    }
+
+    return '(?P<' . $matches[1] . '>[^/]+)';
+  }
+
+  /**
+   * @return string
+   */
+  private function computeUri()
+  {
+    $uri = Registry::get('env')->REQUEST_URI;
+    $pos = strpos($uri, '?');
+
+    if ($pos !== false) {
+      $uri = substr($uri, 0, $pos);
+    }
+
+    return $uri;
+  }
+
+  /**
+   * @return boolean
+   */
+  public function matches()
+  {
+    return $this->matched;
+  }
+
+  /**
+   * @return array
+   */
+  public function getParams()
+  {
+    return $this->params;
+  }
+
+  /**
+   * @return string
+   */
+  public function getRule()
+  {
+    return $this->rule;
+  }
+}

+ 92 - 0
php-pimf/pimf-framework/core/Pimf/Route/Target.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * Pimf
+ *
+ * @copyright Copyright (c)  Gjero Krsteski (http://krsteski.de)
+ * @license   http://krsteski.de/new-bsd-license New BSD License
+ */
+
+namespace Pimf\Route;
+
+/**
+ * Target
+ *
+ * The route-target class defines which prefixes get imported
+ * and exported on the Pimf request-resolver and controller.
+ *
+ * @package Route
+ * @author  Gjero Krsteski <[email protected]>
+ */
+class Target
+{
+  /**
+   * Controller name as it appears in url
+   *
+   * @var string
+   */
+  protected $controller;
+
+  /**
+   * Controller action name as it appears in url
+   *
+   * @var string
+   */
+  protected $action = 'index';
+
+  /**
+   * List of additional params at teh URL.
+   *
+   * @var array
+   */
+  protected $params = array();
+
+  /**
+   * @param string $controller
+   */
+  public function __construct($controller)
+  {
+    $this->controller = $controller;
+  }
+
+  /**
+   * @param mixed $action
+   */
+  public function setAction($action)
+  {
+    $this->action = $action;
+  }
+
+  /**
+   * @param array $params
+   */
+  public function setParams(array $params)
+  {
+    $this->params = $params;
+  }
+
+  /**
+   * @return string
+   */
+  public function getAction()
+  {
+    return $this->action;
+  }
+
+  /**
+   * Controller-name as it appears in url.
+   *
+   * @return string
+   */
+  public function getController()
+  {
+    return $this->controller;
+  }
+
+  /**
+   * @return array
+   */
+  public function getParams()
+  {
+    return $this->params;
+  }
+}

Some files were not shown because too many files changed in this diff