├── skel ├── cache │ └── .htaccess ├── config │ └── .htaccess ├── theme │ └── vanilla │ │ └── example.twig ├── _htaccess └── index.php ├── doc ├── Commands.mdown ├── Fortissimo-UML-state.gif ├── TODO.txt ├── cli.php ├── Overview.txt ├── QUICKSTART.mdown ├── commands.php ├── README.mdown └── doc.php ├── .gitignore ├── test ├── Tests │ └── Fortissimo │ │ ├── Stubs │ │ └── LoaderStub.php │ │ ├── TestRunner.php │ │ ├── FatalErrorLogger.php │ │ ├── Command │ │ ├── ShowPHPInfoTest.php │ │ ├── EchoTextTest.php │ │ ├── AddToContextTest.php │ │ ├── DumpContextTest.php │ │ ├── IncrementTest.php │ │ ├── IntoArrayTest.php │ │ ├── RunRunnerTest.php │ │ ├── EachTest.php │ │ ├── ParseOptionsTest.php │ │ ├── AddINITest.php │ │ ├── UntilTest.php │ │ └── AddPathToContextTest.php │ │ ├── ExternalCacheHeadersTest.php │ │ ├── RequestMapperTest.php │ │ ├── CLIRunnerTest.php │ │ ├── TestCase.php │ │ ├── FortissimoExceptionsTest.php │ │ ├── FortissimoConfigTest.php │ │ ├── FortissimoExecutionContextTest.php │ │ ├── BaseFortissimoCommandTest.php │ │ └── ConfigTest.php └── test.ini ├── composer.json ├── src └── Fortissimo │ ├── ConfigurationException.php │ ├── RequestNotFoundException.php │ ├── Runtime │ ├── Exception.php │ ├── CLIRunner.php │ ├── WebRunner.php │ └── Runner.php │ ├── Exception.php │ ├── Command │ ├── Flow │ │ ├── Iterator.php │ │ ├── FoldRight.php │ │ ├── Forward.php │ │ ├── Until.php │ │ ├── Wrapper.php │ │ ├── Each.php │ │ └── FoldLeft.php │ ├── Theme │ │ ├── TestTheming.php │ │ ├── RenderTheme.php │ │ ├── InitializeTheme.php │ │ └── BaseThemePackage.php │ ├── Util │ │ ├── Increment.php │ │ ├── Head.php │ │ └── ShowPHPInfo.php │ ├── EchoText.php │ ├── Context │ │ ├── AddToContext.php │ │ ├── IntoArray.php │ │ ├── DumpContext.php │ │ ├── AddINI.php │ │ └── AddPathToContext.php │ ├── CLI │ │ ├── ShowHelp.php │ │ ├── RunRunner.php │ │ └── ReadLine.php │ ├── BaseParameter.php │ └── BaseParameterCollection.php │ ├── Explainable.php │ ├── ErrorException.php │ ├── Interrupt.php │ ├── InterruptException.php │ ├── Logger │ ├── SimpleArrayInjectionLogger.php │ ├── SimpleOutputInjectionLogger.php │ ├── OutputInjectionLogger.php │ ├── ArrayInjectionLogger.php │ ├── MongoCappedLogger.php │ ├── Manager.php │ ├── Syslogger.php │ └── Base.php │ ├── Observable.php │ ├── ForwardRequest.php │ ├── Cache │ ├── RequestCache.php │ ├── Base.php │ ├── Memcache.php │ └── Manager.php │ ├── Datasource │ ├── PDO.php │ ├── MongoDB.php │ └── Base.php │ ├── Request.php │ ├── Cacheable.php │ ├── Command.php │ ├── Autoloader.php │ └── RequestMapper.php ├── registry.php ├── Makefile ├── LICENSE.txt ├── TODO.mdown ├── README.mdown └── fort /skel/cache/.htaccess: -------------------------------------------------------------------------------- 1 | Order Allow,Deny -------------------------------------------------------------------------------- /skel/config/.htaccess: -------------------------------------------------------------------------------- 1 | Order Allow,Deny -------------------------------------------------------------------------------- /doc/Commands.mdown: -------------------------------------------------------------------------------- 1 | # How to Write Fortissimo Commands 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Fortissimo.tmproj 2 | test/reports/html/* 3 | composer.lock 4 | vendor/ 5 | -------------------------------------------------------------------------------- /doc/Fortissimo-UML-state.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masterminds/Fortissimo/HEAD/doc/Fortissimo-UML-state.gif -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Stubs/LoaderStub.php: -------------------------------------------------------------------------------- 1 | TRUE)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /doc/TODO.txt: -------------------------------------------------------------------------------- 1 | To Do 2 | ===== 3 | 4 | * Complete command caching mechanism 5 | - Issue: What are we going to cache for commands? 6 | * Data source abstraction layer 7 | * Bootstrap interception? 8 | * Error handling for cached requests. 9 | * Should the logger be a Singleton (or Multiton)? 10 | -------------------------------------------------------------------------------- /registry.php: -------------------------------------------------------------------------------- 1 | route('@test'); 14 | -------------------------------------------------------------------------------- /src/Fortissimo/Runtime/Exception.php: -------------------------------------------------------------------------------- 1 | description('Create an iterator.') 10 | ->usesParam('array', 'The array to convert to an iterator.')->whichIsRequired() 11 | ->andReturns('An Iterable.') 12 | ; 13 | } 14 | 15 | public function doCommand() { 16 | $array = $this->param('array'); 17 | return new \ArrayIterator($array); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/FatalErrorLogger.php: -------------------------------------------------------------------------------- 1 | params['ignore'])) { 9 | $this->ignore = $this->params['ignore']; 10 | } 11 | } 12 | 13 | public function log($msg, $severity, $details) { 14 | 15 | if (!in_array($severity, $this->ignore)) { 16 | throw new \Exception($msg . ' ' . $details); 17 | } 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /skel/theme/vanilla/example.twig: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 11 | {{title}} 12 | 13 | 14 | 15 | 16 |

{{title}}

17 |

{{welcome}}

18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Flow/FoldRight.php: -------------------------------------------------------------------------------- 1 | registry(); 13 | $reg->route('test')->does('\Fortissimo\Command\Util\ShowPHPInfo', 'info'); 14 | 15 | $runner = $this->runner($reg); 16 | 17 | ob_flush(); 18 | ob_start(); 19 | $cxt = $runner->run('test'); 20 | $res = ob_get_clean(); 21 | 22 | $this->assertRegExp('/PHP License/', $res); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/EchoTextTest.php: -------------------------------------------------------------------------------- 1 | registry(__CLASS__); 13 | $reg->route('default')->does('\Fortissimo\Command\EchoText', 'echo')->using('text', 'Echo'); 14 | 15 | $runner = $this->runner($reg); 16 | 17 | ob_start(); 18 | $runner->run('default'); 19 | $c = ob_get_contents(); 20 | ob_end_clean(); 21 | $c = trim($c); 22 | $this->assertEquals('Echo', $c); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/AddToContextTest.php: -------------------------------------------------------------------------------- 1 | registry('test'); 12 | $reg->route('test') 13 | ->does('\Fortissimo\Command\Context\AddToContext', 'add') 14 | ->using('test1', 'foo') 15 | ->using('test2', 'bar') 16 | ; 17 | 18 | $runner = $this->runner($reg); 19 | 20 | $cxt = $runner->run('test'); 21 | 22 | $this->assertEquals('foo', $cxt->get('test1')); 23 | $this->assertEquals('bar', $cxt->get('test2')); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Fortissimo/ErrorException.php: -------------------------------------------------------------------------------- 1 | 'test.php'); 13 | } 14 | 15 | public function functions() { 16 | return array( 17 | 'link' => array($this, 'doLink'), 18 | ); 19 | } 20 | 21 | /** 22 | * Make a link. 23 | * 24 | * Takes two arguments: 25 | * - url: The URL to link to 26 | * - text: The text of the link. 27 | */ 28 | public function doLink(&$v) { 29 | return '' . $v['text'] . ''; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Fortissimo/Interrupt.php: -------------------------------------------------------------------------------- 1 | registry('test'); 12 | $reg->route('cache') 13 | ->does('\Fortissimo\Command\HTTP\ExternalCacheHeaders', 'headers') 14 | ->using('ttl', 1234) 15 | ->route('nocache') 16 | ->does('\Fortissimo\Command\HTTP\ExternalCacheHeaders', 'headers') 17 | ->using('no_cache', TRUE) 18 | ; 19 | 20 | $runner = $this->runner($reg); 21 | /* FIXME: Need to work around the unit testing framework here -- header() doesn't work. 22 | $cxt = $runner->run('cache'); 23 | $headers = headers_list(); 24 | 25 | $this->assertGreaterThan(2, count($headers)); 26 | */ 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Theme/RenderTheme.php: -------------------------------------------------------------------------------- 1 | description('Render the given variables through the given theme.') 19 | ->usesParam('variables', 'The theme variables') 20 | ->whichIsRequired() 21 | ->usesParam('theme', 'The theme target') 22 | ->whichIsRequired() 23 | ->andReturns('A themed string.') 24 | ; 25 | } 26 | 27 | public function doCommand() { 28 | $target = $this->param('theme'); 29 | $variables = $this->param('variables'); 30 | return Theme::render($target, $variables); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /skel/_htaccess: -------------------------------------------------------------------------------- 1 | # Configuration directives for Apache 2. 2 | 3 | Order allow,deny 4 | 5 | 6 | Options -Indexes 7 | Options +FollowSymLinks 8 | DirectoryIndex index.php 9 | 10 | 11 | php_value magic_quotes_gpc 0 12 | php_value register_globals 0 13 | php_value mbstring.http_input pass 14 | php_value mbstring.http_output pass 15 | php_value mbstring.encoding_translation 0 16 | 17 | 18 | # Rewrite URLs of the form 'x' to the form 'index.php?ff=x'. 19 | RewriteEngine on 20 | RewriteCond %{REQUEST_FILENAME} !-f 21 | RewriteCond %{REQUEST_FILENAME} !-d 22 | RewriteCond %{REQUEST_URI} !=/favicon.ico 23 | RewriteRule ^(.*)$ index.php?ff=$1 [L,QSA] 24 | 25 | 26 | # Based on the .htaccess file from Drupal 6. -------------------------------------------------------------------------------- /src/Fortissimo/InterruptException.php: -------------------------------------------------------------------------------- 1 | registry('test'); 13 | 14 | $reg->route('default')->does('\Fortissimo\Command\Context\DumpContext', 'dump'); 15 | 16 | $reg->route('test2') 17 | ->does('\Fortissimo\Command\Context\DumpContext', 'dump') 18 | ->using('item', 'test') 19 | ; 20 | 21 | $runner = $this->runner($reg); 22 | 23 | ob_flush(); 24 | ob_start(); 25 | $cxt = $runner->run('default'); 26 | $out = ob_get_clean(); 27 | 28 | $this->assertRegExp('/ExecutionContext/',$out); 29 | 30 | 31 | ob_flush(); 32 | ob_start(); 33 | $cxt = $runner->run('test2'); 34 | $out = ob_get_clean(); 35 | 36 | $this->assertRegExp('/bool\(true\)/',$out); 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Util/Increment.php: -------------------------------------------------------------------------------- 1 | description('Increment a number') 19 | ->usesParam('startWith', 'The starting value') 20 | ->whichHasDefault(0) 21 | ->usesParam('incrementBy', 'The integer number that should be added to the base value each time.') 22 | ->whichHasDefault(1) 23 | ->andReturns('The incremented value.'); 24 | } 25 | public function doCommand() { 26 | $startWith= $this->param('startWith', 0); 27 | $incrementBy = $this->param('incrementBy', 1); 28 | fprintf(STDOUT, "Start With %d and increment by %d\n", $startWith, $incrementBy); 29 | return $startWith + $incrementBy; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Flow/Forward.php: -------------------------------------------------------------------------------- 1 | description('Forward to another route.') 17 | ->usesParam('route', 'The name of the route.')->whichIsRequired() 18 | ->usesParam('allowInternal', 'Allow this to forward to an @-request.') 19 | ->whichHasDefault(FALSE) 20 | ->withFilter('boolean') 21 | ->andReturns('Nothing, but it will stop processing of the present route and begin a new route.') 22 | ; 23 | } 24 | public function doCommand() { 25 | $route = $this->param('route'); 26 | $internal = $this->param('allowInternal'); 27 | throw new \Fortissimo\ForwardRequest($route, $this->context, $internal); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Util/Head.php: -------------------------------------------------------------------------------- 1 | description('Put the first (head) value in the list and move the pointer to the next. This does not modify the list.') 21 | ->usesParam('list', 'An iterable or an array.') 22 | ->andReturns('The first item in the iterable.'); 23 | } 24 | public function doCommand() { 25 | $list =& $this->param('list', array()); 26 | // XXX: Should this use a foreach? Not sure if a plain Iterable can 27 | // do current(). 28 | $ret = current($list); 29 | next($list); 30 | return $ret; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/RequestMapperTest.php: -------------------------------------------------------------------------------- 1 | mapper()->basePath(); 30 | 31 | $this->assertEquals('/foo', $res); 32 | } 33 | public function testLocalPath() { 34 | $res = $this->mapper()->localPath(); 35 | 36 | $this->assertEquals('/bar', $res); 37 | } 38 | public function testBaseURL() { 39 | $res = $this->mapper()->baseURL(); 40 | 41 | $this->assertEquals('https://www.example.com:8080/foo', $res); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Fortissimo 2 | Matt Butcher 3 | Copyright (C) 2012 Matt Butcher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /TODO.mdown: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ** The runner should not need to scan the config. ** 4 | 5 | ## Bundles 6 | 7 | - Code to scan specific directories for config.php files (dev mode) 8 | - Code to generate a compiled set of config.php files (prod mode) 9 | 10 | ## Fortissimo and Runners 11 | - Runners should handle Explain mode -- maybe an ExplainRunner? 12 | - Extend fetchParameterFromSource to search datasources. 13 | 14 | ## Event Handlers 15 | 16 | - Test event handlers 17 | 18 | ## Commands 19 | 20 | - Theme commands need updating. 21 | - Base needs better explain support. 22 | - Add Context\AddJSON command. 23 | 24 | ## Caches 25 | 26 | - Cache implementations need testing 27 | 28 | ## Mappers 29 | 30 | - Need a CLI mapper 31 | - Determine whether mappers should be handled in runner, context 32 | or fortissimo. 33 | 34 | ## Datasources 35 | 36 | - Test PDO datasource 37 | - Move MongoDB to separate project 38 | 39 | ## Fort 40 | 41 | - Default registry.php to build projects 42 | - Refactor fort to run off of registry 43 | 44 | ## Tests 45 | 46 | - Clean up tests!!! 47 | - Remove unused fixtures and stubs 48 | 49 | # Misc 50 | 51 | - Composer needs to treat fort as a bin 52 | -------------------------------------------------------------------------------- /src/Fortissimo/Logger/SimpleArrayInjectionLogger.php: -------------------------------------------------------------------------------- 1 | %s %s'; 19 | switch ($category) { 20 | case 'Fatal Error': 21 | $msg = 'An unrecoverable error occurred. Your request could not be completed.'; 22 | case 'Recoverable Error': 23 | $msg = 'An error occurred. Some data may be lost or incomplete.'; 24 | default: 25 | $msg = 'An unexpected error occurred. Some data may be lost or incomplete.'; 26 | } 27 | $this->logItems[] = sprintf($filter, $severity, 'Error', $msg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/EchoText.php: -------------------------------------------------------------------------------- 1 | description('Echo the contents of the "text" parameter to standard output.') 18 | ->usesParam('type', 'The MIME type of the message, e.g. text/plain, text/html, application/javascript') 19 | ->withFilter('string') 20 | ->usesParam('headers', 'Other HTTP headers to set. This should be an indexed array of header strings, e.g. array("Location: http://example.com").') 21 | ->usesParam('text', 'The text to echo.') 22 | //->withFilter('string') 23 | ; 24 | } 25 | 26 | public function doCommand() { 27 | $type = $this->param('type', NULL); 28 | $headers = $this->param('headers', array()); 29 | 30 | if (!empty($type)) { 31 | header('Content-Type: ' . $type); 32 | } 33 | 34 | foreach ($headers as $header) { 35 | header($header); 36 | } 37 | 38 | print $this->parameters['text']; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Context/AddToContext.php: -------------------------------------------------------------------------------- 1 | name = $name; 20 | $this->caching = $caching; 21 | } 22 | 23 | public function isCacheable () { 24 | return TRUE; 25 | } 26 | 27 | public function execute($params, \Fortissimo\ExecutionContext $cxt) { 28 | foreach ($params as $name => $value) { 29 | $cxt->add($name, $value); 30 | } 31 | } 32 | 33 | public function explain() { 34 | $klass = new \ReflectionClass($this); 35 | $desc = 'Add all parameters to the context.'; 36 | $cmdFilter = "CMD: %s (%s): %s\n\tRETURNS: Nothing" . PHP_EOL . PHP_EOL; 37 | return sprintf($cmdFilter, $this->name, $klass->name, $desc); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/CLI/ShowHelp.php: -------------------------------------------------------------------------------- 1 | description('Generate help text listing all of the commands.') 14 | ->andReturns('Nothing.') 15 | ; 16 | } 17 | 18 | public function doCommand() { 19 | global $argv; 20 | $ff = $this->context->fortissimo(); 21 | $config = $ff->getRequestPaths(); 22 | //var_dump($config['requests']); 23 | 24 | 25 | $buffer = array(); 26 | $longest = 4; 27 | foreach ($config['requests'] as $name => $params) { 28 | $buffer[] = array($name, $config['help']['requests'][$name]); 29 | $width = strlen($name); 30 | if ($width > $longest) { 31 | $longest = $width; 32 | } 33 | 34 | } 35 | 36 | printf("\n %s COMMAND [--help | --OPTIONS [..]] [ARGS]\n\n Available commands:\n\n", $argv[0]); 37 | $filter = "\t%-" . ($longest + 2) .'s%s' . PHP_EOL; 38 | foreach ($buffer as $line) { 39 | printf($filter, $line[0], $line[1]); 40 | 41 | } 42 | print PHP_EOL; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /doc/cli.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleRequest($argv[1]); -------------------------------------------------------------------------------- /src/Fortissimo/Logger/SimpleOutputInjectionLogger.php: -------------------------------------------------------------------------------- 1 | %s %s'; 23 | switch ($category) { 24 | case 'Fatal Error': 25 | $msg = 'An unrecoverable error occurred. Your request could not be completed.'; 26 | case 'Recoverable Error': 27 | $msg = 'An error occurred. Some data may be lost or incomplete.'; 28 | default: 29 | $msg = 'An unexpected error occurred. Some data may be lost or incomplete.'; 30 | } 31 | printf($filter, $severity, 'Error', $msg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/IncrementTest.php: -------------------------------------------------------------------------------- 1 | registry(); 13 | 14 | $r->route('test')->does('\Fortissimo\Command\Util\Increment', 'inc'); 15 | $r->route('test2') 16 | ->does('\Fortissimo\Command\Util\Increment', 'inc') 17 | ->using('startWith', 4) 18 | ->using('incrementBy', 5) 19 | ; 20 | $r->route('test3') 21 | ->does('\Fortissimo\Command\Util\Increment', 'inc') 22 | ->using('startWith', 4) 23 | ->using('incrementBy', -5) 24 | ; 25 | $r->route('test4') 26 | ->does('\Fortissimo\Command\Util\Increment', 'inc') 27 | ->using('startWith', 0) 28 | ->using('incrementBy', 0) 29 | ; 30 | 31 | $runner = $this->runner($r); 32 | $cxt = $runner->run('test'); 33 | 34 | $this->assertEquals(1, $cxt->get('inc')); 35 | 36 | 37 | $cxt = $runner->run('test2'); 38 | $this->assertEquals(9, $cxt->get('inc')); 39 | 40 | $cxt = $runner->run('test3'); 41 | $this->assertEquals(-1, $cxt->get('inc')); 42 | 43 | $cxt = $runner->run('test4'); 44 | $this->assertEquals(0, $cxt->get('inc')); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/CLIRunnerTest.php: -------------------------------------------------------------------------------- 1 | registry(__CLASS__); 13 | $registry->route('default') 14 | ->does('\Fortissimo\Command\EchoText', 'echo') 15 | ->using('text', 'TEST') 16 | ; 17 | 18 | 19 | 20 | // Run the commandline runner. 21 | global $argv; 22 | $runner = new CLIRunner($argv, STDOUT, STDIN); 23 | $runner->useRegistry($registry); 24 | 25 | ob_flush(); 26 | ob_start(); 27 | $runner->run('default'); 28 | $out = ob_get_clean(); 29 | 30 | $this->assertEquals('TEST', $out); 31 | 32 | 33 | } 34 | 35 | /** 36 | * @expectedException \Fortissimo\RequestNotFoundException 37 | */ 38 | public function testRequestNotFound() { 39 | // We don't want FOIL logger, so we create 40 | // a registry froms scratch. 41 | $registry = new \Fortissimo\Registry(__CLASS__); 42 | $registry->route('default') 43 | ->does('\Fortissimo\Command\EchoText', 'echo') 44 | ->using('text', 'TEST') 45 | ; 46 | global $argv; 47 | $runner = new CLIRunner($argv, STDOUT, STDIN); 48 | $runner->useRegistry($registry)->run('noSuchRequest'); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Context/IntoArray.php: -------------------------------------------------------------------------------- 1 | description('Put some or all of a context into an array.') 22 | ->usesParam('names', 'The names of context items that should be put into this array if found.') 23 | ->andReturns('An associative array of context names/values.') 24 | ; 25 | } 26 | 27 | public function doCommand() { 28 | $names = $this->param('names', NULL); 29 | 30 | if (is_array($names)) { 31 | $buffer = array(); 32 | foreach ($names as $name) { 33 | if ($this->context->has($name)) { 34 | $buffer[$name] = $this->context->get($name); 35 | } 36 | } 37 | } 38 | else { 39 | $buffer = $this->context->toArray(); 40 | } 41 | 42 | return $buffer; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Fortissimo/Runtime/CLIRunner.php: -------------------------------------------------------------------------------- 1 | args = $args; 45 | $this->input = $in; 46 | $this->output = $out; 47 | } 48 | 49 | 50 | 51 | public function initialContext() { 52 | $cxt = parent::initialContext(); 53 | $cxt->add('output', $this->output); 54 | $cxt->add('input', $this->input); 55 | return $cxt; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/TestCase.php: -------------------------------------------------------------------------------- 1 | useRegistry($reg); 37 | } 38 | return $runner; 39 | } 40 | 41 | public function registry($name = 'test') { 42 | $reg = new \Fortissimo\Registry($name); 43 | $reg->logger('\Fortissimo\Tests\FatalErrorLogger', 'testlogger'); 44 | 45 | return $reg; 46 | } 47 | 48 | 49 | /*public static function setUpBeforeClass() { 50 | spl_autoload_register('\Fortissimo\Tests\TestCase::autoloader'); 51 | }*/ 52 | } 53 | spl_autoload_register('\Fortissimo\Tests\TestCase::autoloader'); 54 | -------------------------------------------------------------------------------- /src/Fortissimo/Logger/OutputInjectionLogger.php: -------------------------------------------------------------------------------- 1 | isHTML = isset($this->params['html']) ? filter_var($this->params['html'], FILTER_VALIDATE_BOOLEAN) : FALSE; 25 | $this->filter = empty($this->params['html']) ? '%s %s %s' : '
%s %s
'; 26 | } 27 | public function log($message, $category, $details) { 28 | 29 | if ($this->isHTML) { 30 | $severity = strtr($category, ' ', '-'); 31 | $message = strtr($message, array("\n" => '
')); 32 | $filter = '
%s %s
%s
'; 33 | printf($filter, $severity, $category, $message, $details); 34 | } 35 | else { 36 | printf('%s: %s -- %s', $category, $message, $details); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Context/DumpContext.php: -------------------------------------------------------------------------------- 1 | description('Dumps everything in the context to STDOUT.') 21 | ->usesParam('html', 'Prints the dump in pretty HTML output. Default: False') 22 | ->withFilter('boolean') 23 | ->usesParam('item', 'Dump only this item, not the entire context.') 24 | ->withFilter('string') 25 | ; 26 | } 27 | 28 | /** 29 | * Dump the context to STDOUT. 30 | */ 31 | public function doCommand() { 32 | $pretty = $this->param('html', FALSE); 33 | $item = $this->param('item', NULL); 34 | 35 | if (!empty($item)) { 36 | $format = '
Dumping Context Item "%s"
'; 37 | printf($format, $item); 38 | $dump = $this->context->get($item); 39 | } 40 | else { 41 | $dump = $this->context; 42 | } 43 | 44 | if ($pretty) { 45 | print '
';
46 |       var_dump($dump);
47 |       print '
'; 48 | } 49 | else { 50 | var_dump($dump); 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/IntoArrayTest.php: -------------------------------------------------------------------------------- 1 | registry(); 13 | 14 | $reg->route('test1') 15 | ->does('\Fortissimo\Command\Context\AddToContext') 16 | ->using('foo', 'bar') 17 | ->using('foo2', 'bar2') 18 | ->using('foo3', 'bar3') 19 | ->does('\Fortissimo\Command\Context\IntoArray', 'res') 20 | ; 21 | 22 | $runner = $this->runner($reg); 23 | $cxt = $runner->run('test1'); 24 | 25 | $res = $cxt->get('res'); 26 | 27 | // 'test' plus the three foos. 28 | $this->assertEquals(4, count($res)); 29 | $this->assertEquals('bar', $res['foo']); 30 | 31 | 32 | $reg->route('test2') 33 | ->does('\Fortissimo\Command\Context\AddToContext') 34 | ->using('foo', 'bar') 35 | ->using('foo2', 'bar2') 36 | ->using('foo3', 'bar3') 37 | ->does('\Fortissimo\Command\Context\IntoArray', 'res') 38 | ->using('names', array('foo2', 'foo3', 'NO_SUCH_ARG')) 39 | ; 40 | 41 | $runner = $this->runner($reg); 42 | $cxt = $runner->run('test2'); 43 | 44 | fwrite(STDOUT, print_r($cxt->toArray(), TRUE)); 45 | 46 | $res = $cxt->get('res'); 47 | $this->assertEquals(2, count($res)); 48 | 49 | // 'test' plus the three foos. 50 | $this->assertEquals('bar2', $res['foo2']); 51 | $this->assertFalse(array_key_exists('NO_SUCH_ARG', $res)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/RunRunnerTest.php: -------------------------------------------------------------------------------- 1 | route('test2') 15 | ->does('\Fortissimo\Command\Util\Head', 'head') 16 | ->using('list', array(1, 2, 3)) 17 | ; 18 | 19 | $r = $this->registry(); 20 | $r->route('test') 21 | ->does('\Fortissimo\Command\CLI\RunRunner', 'internal') 22 | ->using('route', 'test2') 23 | ->using('registry', $inner) 24 | ; 25 | $runner = $this->runner($r); 26 | $cxt = $runner->run('test'); 27 | $expects = $cxt->get('head'); 28 | 29 | $this->assertEquals(1, $expects); 30 | 31 | $r->route('dummy') 32 | ->does('\Fortissimo\Command\Context\AddToContext') 33 | ->using('here', TRUE) 34 | ; 35 | $r->route('testNoReg') 36 | ->does('\Fortissimo\Command\CLI\RunRunner', 'internal') 37 | ->using('route', 'dummy') 38 | ; 39 | 40 | $runner = $this->runner($r); 41 | $cxt = $runner->run('testNoReg'); 42 | 43 | $this->assertTrue($cxt->get('here')); 44 | 45 | /* 46 | $r->route('testInclude') 47 | ->does('\Fortissimo\Command\CLI\RunRunner', 'internal') 48 | ->using('registry', 'test/RunRunner_registry.php') 49 | ->using('route', 'foo') 50 | ; 51 | 52 | $runner = $this->runner($r); 53 | $cxt = $runner->run('testInclude'); 54 | 55 | $this->assertEquals(2, $cxt->get('bar')); 56 | */ 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Flow/Until.php: -------------------------------------------------------------------------------- 1 | description('Functional looping.') 28 | ->usesParam('request', 'The request to loop over.')->whichIsRequired() 29 | ->usesParam('condition', 'A callable which will return TRUE when this loop should stop')->whichIsRequired() 30 | ->usesParam('allowInternal', 'Allow internal routes to be called.')->whichHasDefault(FALSE) 31 | ->andReturns('Nothing.') 32 | ; 33 | } 34 | 35 | public function doCommand() { 36 | $request = $this->param('request'); 37 | $cb = $this->param('condition'); 38 | $internal = $this->param('allowInternal', FALSE); 39 | 40 | while (call_user_func($cb, $this->context) !== TRUE) { 41 | $this->context->fortissimo()->handleRequest($request, $this->context, $internal); 42 | } 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /doc/Overview.txt: -------------------------------------------------------------------------------- 1 | 2 | Fortissimo 3 | 4 | The main command for starting a new project is ff.php. 5 | 6 | This command is executed like this: 7 | 8 | ff.php projectName 9 | 10 | It does the following: 11 | 12 | - It creates a new project, named projectName, in the current directory. 13 | - It adds the base directories necessary for that new project. 14 | - It creates customized build.xml and command.xml documents. 15 | * build.xml is used by Phing. It's like a make or rake file. 16 | * command.xml is used by the front controller. 17 | - It installs base classes in the App's library. 18 | - Create default .htaccess file 19 | - Create default index file. 20 | 21 | ## Adding a New Command ## 22 | 23 | To add a new command, simply add a new file in the includes directory. The file 24 | should be named MyClass.cmd.php, where MyClass is the name of the command class. 25 | 26 | The most common way to create a new command is to extend the BaseFortssimoCommand 27 | class. 28 | 29 | Example: 30 | 31 | class FortissimoEcho extends BaseFortissimoCommand { 32 | 33 | public function expects() { 34 | return $this 35 | ->description('Echo the contents of the "text" parameter to standard output.') 36 | ->usesParam('text', 'The text to echo.') 37 | ->withFilter('string'); 38 | } 39 | 40 | public function doCommand() { 41 | print $this->param('text'); 42 | } 43 | } 44 | 45 | The code above implements two methods: 46 | 47 | * expects(): Tells the Fortissimo system what parameters this command expects, and 48 | what it does with those parameters. 49 | * doCommand(): Performs the actual processing of the command. 50 | 51 | That's all there is to creating a new command. From there, just add the command 52 | to some request in the `commands.xml` file, and you should be ready. -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/EachTest.php: -------------------------------------------------------------------------------- 1 | registry(); 12 | $r->route('test') 13 | ->does('\Fortissimo\Command\Flow\Each', 'each') 14 | ->using('list', array(1, 2, 3)) 15 | ->using('command', '\Fortissimo\Command\Util\Increment') 16 | ->using('commandName', 'up') 17 | ->using('startWith')->from('each:value') 18 | ; 19 | 20 | $runner = $this->runner($r); 21 | 22 | $cxt = $runner->run('test'); 23 | 24 | $this->assertTrue($cxt->has('each')); 25 | $this->assertTrue($cxt->has('up')); 26 | 27 | $results = $cxt->get('each', array()); 28 | $this->assertEquals(3, count($results)); 29 | $this->assertEquals(2, $results[0]); 30 | $this->assertEquals(3, $results[1]); 31 | $this->assertEquals(4, $results[2]); 32 | } 33 | public function testDoCommandClosure() { 34 | $test = $this; 35 | $r = $this->registry(); 36 | $r->route('test') 37 | ->does('\Fortissimo\Command\Flow\Each', 'ABCD') 38 | ->using('list', array(3, 5, 7)) 39 | ->using('command', function ($cxt, $name, $params) use ($test) { 40 | // Test to make sure the name is autogenerated. 41 | $test->assertGreaterThan(10, strlen($name)); 42 | $val = $cxt->get('ABCD_value'); 43 | $last = $cxt->get($name, 0) + $val; 44 | return $last; 45 | }) 46 | ; 47 | 48 | $runner = $this->runner($r); 49 | 50 | $cxt = $runner->run('test'); 51 | 52 | $accumulator = $cxt->get('ABCD', array()); 53 | 54 | $this->assertEquals(3, count($accumulator)); 55 | $this->assertEquals(15, $accumulator[2]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /skel/index.php: -------------------------------------------------------------------------------- 1 | 16 | * @license http://opensource.org/licenses/mit.php An MIT-style License (See LICENSE.txt) 17 | * @see Fortissimo 18 | * @see Fortissimo.php 19 | * @copyright Copyright (c) 2010, Matt Butcher. 20 | * @version %UNSTABLE- 21 | */ 22 | if(version_compare(phpversion(), '5.2', '>') === FALSE) { 23 | print 'PHP 5.2 or greater is required.'; 24 | exit; 25 | } 26 | 27 | // Idiotic things you just have to do... 28 | if (get_magic_quotes_gpc()) { 29 | print '"Magic quotes", a deprecated PHP feature, is enabled. Please turn it off.'; 30 | exit; 31 | } 32 | if (get_magic_quotes_runtime()) { 33 | set_magic_quotes_runtime(FALSE); 34 | } 35 | 36 | /** 37 | * Import the main library. 38 | */ 39 | require 'Fortissimo.php'; 40 | 41 | $cmd = filter_input(INPUT_GET, 'ff', FILTER_SANITIZE_STRING); 42 | if (empty($cmd)) { 43 | $cmd = 'default'; 44 | } 45 | 46 | $base = dirname(__FILE__); 47 | 48 | // Allow a preloading script that can alter include paths and 49 | // so on. Do this only to make it possible to load your own 50 | // code in commands.php. Don't do something stupid with this, 51 | // ok? kthxbye 52 | if (is_file($base . '/config/setup.php')) { 53 | require $base . '/config/setup.php'; 54 | } 55 | 56 | $ff = new Fortissimo($base . '/config/commands.php'); 57 | $ff->handleRequest($cmd); -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/ParseOptionsTest.php: -------------------------------------------------------------------------------- 1 | array( 14 | 'help' => 'This is the help for flag 1', 15 | 'value' => TRUE, 16 | ), 17 | '--flag2' => array( 18 | 'help' => 'This is the help for flag 2', 19 | 'value' => FALSE, 20 | ), 21 | '--flag3' => array( 22 | 'help' => 'This is the help for flag 3', 23 | 'value' => TRUE, 24 | ), 25 | ); 26 | } 27 | 28 | public function testDoCommand() { 29 | $r = $this->registry(__CLASS__); 30 | 31 | $spec = $this->spec(); 32 | 33 | $opts = array('command','--flag1', 'test1', '--flag2', '--flag3', 'test3', 'data'); 34 | $r 35 | ->route('test1') 36 | ->does('\Fortissimo\Command\CLI\ParseOptions', 'opts') 37 | ->using('optionSpec', $spec) 38 | ->using('options', $opts) 39 | ->using('help', 'OH HAI') 40 | 41 | ->route('test2') 42 | ->does('\Fortissimo\Command\CLI\ParseOptions', 'opts') 43 | ->using('optionSpec', $spec) 44 | ->using('options', $opts) 45 | ->using('offset', 2) 46 | ; 47 | 48 | 49 | $runner = $this->runner($r); 50 | 51 | $cxt = $runner->run('test1'); 52 | 53 | $this->assertNotEmpty($cxt); 54 | $this->assertEquals('command', $cxt->get('opts-command')); 55 | $this->assertEquals('test1', $cxt->get('flag1')); 56 | $this->assertTrue($cxt->get('flag2')); 57 | $extra = $cxt->get('opts-extra'); 58 | $this->assertEquals('data', $extra[0]); 59 | 60 | $cxt = $runner->run('test2'); 61 | $this->assertEquals('test1', $cxt->get('opts-command')); 62 | $this->assertFalse($cxt->has('flag1')); 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Fortissimo/Observable.php: -------------------------------------------------------------------------------- 1 | array( 31 | * 'function_name' 32 | * function () {}, 33 | * array($object, 'methodName'), 34 | * array('ClassNam', 'staticMethod'). 35 | * ), 36 | * 'another_event => array( 37 | * 'some_other_function', 38 | * ), 39 | * ); 40 | * ?> 41 | * @endcode 42 | * 43 | * @param array $listeners 44 | * An associative array of event names and an array of eventhandlers. 45 | */ 46 | public function setEventHandlers($listeners); 47 | 48 | /** 49 | * Trigger a particular event. 50 | * 51 | * @param string $eventName 52 | * The name of the event. 53 | * @param array $data 54 | * Any data that is to be passed into the event. 55 | * @return 56 | * An optional return value, as determined by the particular event. 57 | */ 58 | public function fireEvent($eventName, $data = NULL); 59 | } 60 | -------------------------------------------------------------------------------- /src/Fortissimo/Logger/ArrayInjectionLogger.php: -------------------------------------------------------------------------------- 1 | whichInvokes('FortissimoArrayInjectionLogger') 19 | * ->withParam('html') 20 | * ->whoseValueIs(TRUE) 21 | * @endcode 22 | * 23 | * Parameters: 24 | * 25 | * - html: If TRUE, the logger generates HTML. If false, it generates plain text. 26 | * - categories: An indexed array or comma-separated list of categories that this logger should log. 27 | * 28 | * @ingroup Fortissimo 29 | */ 30 | class ArrayInjectionLogger extends Base { 31 | protected $logItems = array(); 32 | protected $filter; 33 | 34 | public function init() { 35 | $this->filter = empty($this->params['html']) ? '%s: %s' : '
%s
%s
'; 36 | } 37 | 38 | /** 39 | * Fetch the array of messages. 40 | * 41 | * Returns an array of log items already formatted for display. 42 | * 43 | * @return array 44 | * An indexed array of strings, each of which represents a log message. 45 | */ 46 | public function getMessages() { 47 | return $this->logItems; 48 | } 49 | 50 | /** 51 | * Prints all collected log messages. 52 | */ 53 | public function printMessages() { 54 | print implode('', $this->logItems); 55 | } 56 | 57 | public function log($message, $category, $details) { 58 | $severity = str_replace(' ', '-', $category); 59 | $this->logItems[] = sprintf($this->filter, $severity, $message, $details); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Fortissimo/ForwardRequest.php: -------------------------------------------------------------------------------- 1 | destination = $requestName; 35 | $this->cxt = $cxt; 36 | $this->internal = $allowInternal; 37 | parent::__construct('Request forward.'); 38 | } 39 | 40 | /** 41 | * Get the name of the desired destination request. 42 | * 43 | * @return string 44 | * A request name. 45 | */ 46 | public function destination() { 47 | return $this->destination; 48 | } 49 | 50 | /** 51 | * Retrieve the context. 52 | * 53 | * @return object Fortissimo::ExecutionContext 54 | * The context as it was at the point when the request was interrupted. 55 | */ 56 | public function context() { 57 | return $this->cxt; 58 | } 59 | 60 | public function allowInternal() { 61 | return $this->internal; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/CLI/RunRunner.php: -------------------------------------------------------------------------------- 1 | description('Runs a CLI runner inside of a runner.') 9 | -> usesParam('route', 'The route to run')->whichIsRequired() 10 | -> usesParam('registry', 'A Registry.') 11 | -> usesParam('args', 'An array of arguments, like argv.') 12 | // -> usesParam('runner', 'The classname of the runner to run.')->whichIsRequired() 13 | -> andReturns('Nothing, but the context will be modified.') 14 | ; 15 | 16 | } 17 | public function doCommand() { 18 | 19 | //$runnerName = $this->param('runner'); 20 | $registry = $this->param('registry', NULL); 21 | $route = $this->param('route'); 22 | $args = $this->param('args', array()); 23 | 24 | // We use a special runner for this. 25 | $runner = new _InnerCLIRunner($args); 26 | 27 | $this->initializeRegistry($registry, $runner); 28 | 29 | $runner->setContext($this->context); 30 | 31 | $runner->run($route); 32 | } 33 | 34 | protected function initializeRegistry($reg, $runner) { 35 | if (empty($reg)) { 36 | $registry = $this->context->registry(); 37 | } 38 | /* 39 | elseif (is_string($reg)) { 40 | $registry = new Registry('internal'); 41 | // Some use $register. 42 | $register =& $registry; 43 | require_once $reg; 44 | } 45 | */ 46 | else { 47 | $registry = $reg; 48 | } 49 | 50 | $runner->useRegistry($registry); 51 | } 52 | 53 | } 54 | 55 | /** 56 | * A special-purpose runtime. 57 | * 58 | * This is not for general use. 59 | * 60 | */ 61 | class _InnerCLIRunner extends \Fortissimo\Runtime\CLIRunner { 62 | 63 | protected $_context; 64 | 65 | public function setContext($cxt) { 66 | $this->_context = $cxt; 67 | 68 | } 69 | 70 | public function initialContext() { 71 | return $this->_context; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Context/AddINI.php: -------------------------------------------------------------------------------- 1 | description('Load the conents of an INI file into the context.') 14 | ->usesParam('file', 'The path to the INI file.')->whichIsRequired() 15 | ->usesParam('basedir', 'A base directory that, if set, will be prepended.') 16 | ->usesParam('optional', 'Whether the INI file is optional (TRUE) or required (FALSE)') 17 | ->whichHasDefault(FALSE) 18 | ->withFilter('boolean') 19 | ->usesParam('section', 'The INI section to use. If this is set, only values from this section are added to the context.') 20 | ->usesParam('process_sections', 'Whether or not to process sections. If "section" is set, this is automatically enabled.') 21 | ->whichHasDefault(FALSE) 22 | ->withFilter('boolean') 23 | ->andReturns('Nothing. All ini directives are placed into the context.') 24 | ; 25 | } 26 | 27 | public function doCommand() { 28 | $file = $this->param('file'); 29 | $basedir = $this->param('basedir'); 30 | $section = $this->param('section'); 31 | $process_section = $this->param('process_sections') || strlen($section) > 0; 32 | $optional = $this->param('optional', FALSE); 33 | 34 | if (!empty($basedir)) { 35 | $file = $basedir . '/' . $file; 36 | } 37 | 38 | if ($optional && !is_readable($file)) { 39 | return; 40 | } 41 | $ini = parse_ini_file($file, $process_section); 42 | 43 | // If we only return a section, use this. 44 | if (!empty($section)) { 45 | if (isset($ini[$section])) { 46 | $this->context->addAll($ini[$section]); 47 | } 48 | return; 49 | } 50 | 51 | 52 | // Return the entire INI if we get here. 53 | $this->context->addAll($ini); 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Util/ShowPHPInfo.php: -------------------------------------------------------------------------------- 1 | description('Provides debugging information about PHP.') 18 | ->usesParam('category', 'One of: general, credits, configuration, modules, environment, variables, license, or all.') 19 | ->withFilter('string') 20 | // Can't use as a validator because it may return a legitimate 0. 21 | //->withFilter('callback', array($this, 'getCategoryCode')) 22 | ->whichHasDefault('all') 23 | ->andReturns('Nothing. Prints data straight to output.'); 24 | } 25 | 26 | public function doCommand() { 27 | $categoryName = $this->param('category'); 28 | $category = $this->getCategoryCode($categoryName); 29 | 30 | phpinfo($category); 31 | } 32 | 33 | /** 34 | * Get the category ID, given a string name. 35 | * 36 | * @param string $category 37 | * The name of the category. One of general, credits, configuration, modules, 38 | * environment, variables, license, or all. 39 | * @return int 40 | * The associated code, defaulting to -1 (INFO_ALL). 41 | * @see phpinfo() 42 | */ 43 | protected function getCategoryCode($category) { 44 | switch($category) { 45 | case 'general': 46 | return INFO_GENERAL; 47 | case 'credits': 48 | return INFO_CREDITS; 49 | case 'configuration': 50 | return INFO_CONFIGURATION; 51 | case 'modules': 52 | return INFO_MODULES; 53 | case 'environment': 54 | return INFO_ENVIRONMENT; 55 | case 'variables': 56 | return INFO_VARIABLES; 57 | case 'license': 58 | return INFO_LICENSE; 59 | case 'all': 60 | default: 61 | return INFO_ALL; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/AddINITest.php: -------------------------------------------------------------------------------- 1 | registry(); 13 | 14 | $reg->route('ini')->does('\Fortissimo\Command\Context\AddINI', 'i') 15 | ->using('file', $base . '/../../test.ini') 16 | ; 17 | 18 | $runner = $this->runner($reg); 19 | $cxt = $runner->run('ini'); 20 | 21 | $this->assertEquals('foo', $cxt->get('test.param')); 22 | $this->assertEquals('long text', $cxt->get('test2.param')); 23 | $this->assertEquals('bar', $cxt->get('test3.param')); 24 | 25 | // Test with sections 26 | $reg->route('ini')->does('\Fortissimo\Command\Context\AddINI', 'i') 27 | ->using('file', $base . '/../../test.ini') 28 | ->using('process_sections', TRUE) 29 | ; 30 | 31 | $runner = $this->runner($reg); 32 | $cxt = $runner->run('ini'); 33 | 34 | $this->assertEquals('foo', $cxt->get('test.param')); 35 | $this->assertEquals('long text', $cxt->get('test2.param')); 36 | $this->assertEquals('foo', $cxt->get('test3.param')); 37 | $example = $cxt->get('example'); 38 | $this->assertEquals('bar', $example['test3.param']); 39 | 40 | // Test with one named section 41 | $reg->route('ini')->does('\Fortissimo\Command\Context\AddINI', 'i') 42 | ->using('file', $base . '/../../test.ini') 43 | ->using('section', 'example') 44 | ; 45 | 46 | $runner = $this->runner($reg); 47 | $cxt = $runner->run('ini'); 48 | 49 | $this->assertNull($cxt->get('test.param')); 50 | $this->assertEquals('bar', $cxt->get('test3.param')); 51 | // Test again with Optional. 52 | $reg->route('noini')->does('\Fortissimo\Command\Context\AddINI', 'i') 53 | ->using('file', 'DOES_NOT_EXIST.ini') 54 | ->using('optional', TRUE) 55 | ; 56 | $runner = $this->runner($reg); 57 | $cxt = $runner->run('noini'); 58 | 59 | $this->assertEmpty($cxt->get('noini')); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Fortissimo/Cache/RequestCache.php: -------------------------------------------------------------------------------- 1 | doesCommand('test')->whichInvokes('ExceptionThrowingCommand'); 12 | Config::request('div')->doesCommand('test')->whichInvokes('ErrorThrowingCommand'); 13 | Config::logger('fail')->whichInvokes('FortissimoArrayInjectionLogger'); 14 | } 15 | 16 | public function test404 () { 17 | $ff = new FortissimoHarness(); 18 | $this->assertFalse($ff->hasRequest('NOREQUEST'), 'Request NOREQUEST should not exist'); 19 | 20 | $ff->handleRequest('NOREQUEST'); 21 | $log = $ff->loggerManager()->getLoggerbyName('fail'); 22 | $msgs = $log->getMessages(); 23 | 24 | $this->assertEquals(1, count($msgs)); 25 | } 26 | 27 | /** 28 | * @ expectedException FortissimoException 29 | */ 30 | public function testException () { 31 | $ff = new FortissimoHarness(); 32 | 33 | // Make sure that we are not just hitting the 404 handler. 34 | $this->assertTrue($ff->hasRequest('foo'), 'Command foo exists?'); 35 | 36 | //ob_start(); 37 | $ff->handleRequest('foo'); 38 | //ob_end_clean(); 39 | 40 | $log = $ff->loggerManager()->getLoggerbyName('fail'); 41 | $msgs = $log->getMessages(); 42 | 43 | $this->assertEquals(1, count($msgs)); 44 | } 45 | 46 | public function testErrorToException() { 47 | $ff = new FortissimoHarness(); 48 | $this->assertTrue($ff->hasRequest('div'), 'Command div exists?'); 49 | $ff->handleRequest('div'); 50 | $log = $ff->loggerManager()->getLoggerbyName('fail'); 51 | $msgs = $log->getMessages(); 52 | 53 | $this->assertEquals(1, count($msgs)); 54 | } 55 | } 56 | 57 | class ExceptionThrowingCommand extends \Fortissimo\Command\Base { 58 | 59 | public function expects() { 60 | return $this->description('Throws an exception.'); 61 | } 62 | 63 | public function doCommand() { 64 | throw new Exception('By Design'); 65 | } 66 | } 67 | 68 | class ErrorThrowingCommand extends ExceptionThrowingCommand { 69 | public function doCommand() { 70 | // I <3 Divde-by-zero 71 | 1/0; 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/Fortissimo/Datasource/PDO.php: -------------------------------------------------------------------------------- 1 | datasource('pdo') 17 | * ->whichInvokes('\Fortissimo\Datasource\PDO') 18 | * ->withParam('dsn') 19 | * ->whoseValueIs('mysql:host=localhost;dbname=test') 20 | * ->withParam('user') 21 | * ->whoseValueIs('db_user') 22 | * ->withParam('password') 23 | * ->whoseValueIs('db_pass') 24 | * // Hopefully rarely used: 25 | * ->withParam('driver_options) 26 | * ->whoseValueIs(array(PDO::SOME_CONST => 'some_value')) 27 | * ; 28 | * ?> 29 | * @endcode 30 | * 31 | * To access a PDO database connection, do something like this: 32 | * 33 | * @code 34 | * description('Some command that does something') 40 | * ; 41 | * } 42 | * 43 | * public function doCommand() { 44 | * // Note that 'pdo' is the name we declared in the example above. 45 | * $db = $this->context->datasource('pdo'); 46 | * // Do whatever with DB: 47 | * $res = $db->query('SELECT * FROM foo'); 48 | * } 49 | * } 50 | * ?> 51 | * @endcode 52 | * 53 | * Parameters 54 | * - dsn: The PDO DSN 55 | * - user: The username for the database connection 56 | * - password: The password for the database connection 57 | * - driver_options: An array of driver options, as defined by PDO. 58 | * 59 | * @ingroup Fortissimo 60 | */ 61 | class PDO { 62 | 63 | public function __invoke($params, $name, $manager) { 64 | 65 | if (empty($params['dsn'])) { 66 | throw new \Fortissimo\InterruptException('Missing DSN in ' . __CLASS__); 67 | } 68 | 69 | $dsn = $params['dsn']; 70 | 71 | $user = isset($params['user']) ? $params['user'] : NULL; 72 | $pass = isset($params['password']) ? $params['password'] : NULL; 73 | $options = isset($params['driver_options']) ? $params['driver_options'] : NULL; 74 | 75 | return new \PDO($dsn, $user, $pass, $options); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/CLI/ReadLine.php: -------------------------------------------------------------------------------- 1 | array( 21 | * 'help' => 'Enter your name', 22 | * 'default' => 'anonymous', 23 | * ), 24 | * 'favorite_color' => array( 25 | * 'help' => 'Enter your favorite color, or hit return for no color', 26 | * 'default' => NULL, 27 | * ), 28 | * ); 29 | * @endcode 30 | * @author Matt Butcher 31 | */ 32 | class ReadLine extends \Fortissimo\Command\Base { 33 | 34 | protected $cursor; 35 | 36 | public function expects() { 37 | return $this 38 | ->description('Provides a command-line prompt, and stores answers in the context.') 39 | 40 | ->usesParam('prompts', 'An array of prompts that are then read in from the command line and stored in the context.') 41 | 42 | ->usesParam('promptFormat', 'The format of the command prompt. %s will be replaced with the name value of the prompts') 43 | ->whichHasDefault('%s> ') 44 | 45 | ->andReturns('Nothing. Values are inserted directly into the context.') 46 | ; 47 | } 48 | 49 | public function doCommand() { 50 | $this->cursor = $this->param('promptFormat'); 51 | 52 | $prompts = $this->param('prompts', array()); 53 | 54 | $this->doPromptSequence($prompts); 55 | } 56 | 57 | public function doPromptSequence($prompts) { 58 | foreach ($prompts as $prompt => $desc) { 59 | $help = isset($desc['help']) ? $desc['help'] : ''; 60 | $default = isset($desc['default']) ? $desc['default'] : NULL; // Avoid E_STRICT violation 61 | 62 | $value = $this->doPrompt($prompt, $help, $default); 63 | 64 | $this->context->add($prompt, $value); 65 | } 66 | } 67 | 68 | public function doPrompt($name, $help, $default) { 69 | print $help . PHP_EOL; 70 | if (function_exists('readline')) { 71 | $value = readline(sprintf($this->cursor, $name)); 72 | } 73 | else { 74 | printf($this->cursor, $name); 75 | $value = rtrim(fgets(STDIN)); 76 | } 77 | return empty($value) ? $default : $value; 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Theme/InitializeTheme.php: -------------------------------------------------------------------------------- 1 | description('Initialize the Theme System.') 27 | ->usesParam('path', 'The path to the current theme. This is used for templates.') 28 | // ->withFilter('this', 'checkPath') 29 | ->usesParam('register', 'A list of theme classes to initially register. Classes are expected to be instances of BaseThemePackage.') 30 | ->usesParam('settings', 'An associative array of settings or global variables that the theme system uses.') 31 | ->andReturns('Nothing') 32 | ; 33 | } 34 | 35 | /** 36 | * Filter callback. 37 | * 38 | * @fixme THis needs to have the right return value. 39 | */ 40 | /* 41 | public function checkPath($path) { 42 | if (is_array($path)) { 43 | foreach ($path as $item) { 44 | if (!is_dir($item)) { 45 | $this->context->log(printf("Could not find path %s", $item)); 46 | return FALSE; 47 | } 48 | } 49 | return TRUE; 50 | } 51 | else { 52 | return is_dir($path); 53 | } 54 | } 55 | */ 56 | 57 | public function doCommand() { 58 | $path = $this->param('path', ''); 59 | $settings = $this->param('settings', NULL); 60 | $register = $this->param('register', NULL); 61 | 62 | if (is_null($settings)) { 63 | $settings = array(); 64 | } 65 | 66 | // Since Context is an object, it will be a valid reference 67 | // to the context for the duration of the request. The only exception 68 | // would be if one (manually) switched requests without throwing an 69 | // interrupt. 70 | Theme::initialize($path, $settings, $this->context); 71 | 72 | if (!empty($register)) { 73 | foreach ($register as $klass) { 74 | Theme::register($klass); 75 | } 76 | } 77 | return TRUE; 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /doc/QUICKSTART.mdown: -------------------------------------------------------------------------------- 1 | # Fortissimo Quickstart 2 | 3 | This short document describes how to quickly create a Fortissimo application. 4 | 5 | 6 | 1. Create a new project directory: 7 | 8 | ~~~ 9 | $ mkdir MyApp 10 | $ cd MyApp 11 | ~~~ 12 | 13 | 2. Create a `composer.json` file in the project directory. At minimum, 14 | it should look like this: 15 | 16 | ~~~ 17 | { 18 | "name": "myname/myapp", 19 | "description": "My Application", 20 | "require": { 21 | "technosophos/fortissimo": "2.x", 22 | }, 23 | "autoload": { 24 | "psr-0": { "": "src/" } 25 | } 26 | } 27 | ~~~ 28 | 29 | This creates the structure for your app. 30 | 31 | 3. Start writing code. You probably want an `index.php` file that looks 32 | something like this: 33 | 34 | ~~~ 35 | useRegistry($register); 44 | 45 | // Register routes here... 46 | $register->route('default') 47 | ->does('\Fortissimo\Command\EchoText') 48 | ->using('text', 'Hello World') 49 | ; 50 | 51 | $runner->run($_SERVER['REQUEST_URI']); 52 | ?> 53 | ~~~ 54 | 55 | The above creates a very basic Hello World app. 56 | 57 | 4. Begin writing your own code. 58 | 59 | Building a Fortissimo app generally consists of two things: 60 | 61 | - Building routes (`$register->route()`) 62 | - Creating commands 63 | 64 | Commands are highly re-usable components. A route is nothing other than 65 | a map of a named route to a sequence of commands. 66 | 67 | 68 | ## How a Request is Handled 69 | 70 | In a nutshell, this is how Fortissimo handles a request: 71 | 72 | ~~~ 73 | 74 | browser -> 75 | request-> 76 | command1 77 | | 78 | command2 79 | | 80 | <-command3 81 | <-request 82 | browser 83 | 84 | ~~~ 85 | 86 | In the above flow chart, a browser performs a request (a route), which 87 | then results in each command in the route being run. Finally, one of the 88 | commands will (presumably) return data to the browser. 89 | 90 | ## Writing Routes 91 | 92 | See Routes.mdown 93 | 94 | ## Writing Commands 95 | 96 | See Commands.mdown 97 | 98 | Take a look at the example commands in `src/Fortissimo/Command` inside 99 | of Fortissimo's source code. 100 | 101 | -------------------------------------------------------------------------------- /src/Fortissimo/Request.php: -------------------------------------------------------------------------------- 1 | requestName = $requestName; 22 | $this->commandQueue = $commands; 23 | } 24 | 25 | public function getName() { 26 | return $this->requestName; 27 | } 28 | 29 | /** 30 | * Get the array of commands. 31 | * 32 | * @return array 33 | * An array of commands. 34 | */ 35 | public function getCommands() { 36 | return $this->commandQueue; 37 | } 38 | 39 | /** 40 | * Set the flag indicating whether or not this is caching. 41 | */ 42 | public function setCaching($boolean) { 43 | $this->isCaching = $boolean; 44 | } 45 | 46 | /** 47 | * Set explain mode. 48 | * 49 | * By default a command is NOT in explain mode. 50 | * @param boolean $boolean 51 | * Set to TRUE to turn on explain mode. 52 | */ 53 | public function setExplain($boolean) { 54 | $this->isExplaining = $boolean; 55 | } 56 | 57 | /** 58 | * Determine whether this request is in 'explain' mode. 59 | * 60 | * When a request is explaining, Fortissimo will output detailed 61 | * information about each command, such as what parameters it expects 62 | * and what its purpose is. 63 | * 64 | * @return boolean 65 | * TRUE if this request is in explain mode, false otherwise. 66 | */ 67 | public function isExplaining() { 68 | return $this->isExplaining; 69 | } 70 | 71 | /** 72 | * Determine whether this request can be served from cache. 73 | * 74 | * Request output can sometimes be cached. This flag indicates 75 | * whether the given request can be served from a cache instead 76 | * of requiring the entire request to be executed. 77 | * 78 | * @return boolean 79 | * Returns TRUE if this can be served from cache, or 80 | * FALSE if this should not be served from cache. 81 | * @see Fortissimo::Cache::Request 82 | */ 83 | public function isCaching() { 84 | return $this->isCaching; 85 | } 86 | 87 | /** 88 | * Get an iterator of this object. 89 | * 90 | * @return Iterator 91 | */ 92 | public function getIterator() { 93 | return new \ArrayIterator($this->commandQueue); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Fortissimo/Datasource/MongoDB.php: -------------------------------------------------------------------------------- 1 | params[$pname])) { 55 | $mongoOptions[$pname] = $this->params[$pname]; 56 | } 57 | } 58 | 59 | $mongoInstance = new \Mongo($server); 60 | if (isset($params['instanceName'])) { 61 | $manager->addDatasource(function () use ($mongoInstance) { return $mongoInstance; }, $params['instanceName']); 62 | } 63 | 64 | return $mongoInstance->selectDB($dbName); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/UntilTest.php: -------------------------------------------------------------------------------- 1 | registry(); 13 | $r->route('@inner') 14 | ->does('\Fortissimo\Command\Util\Increment', 'up') 15 | ->using('startWith', 0)->from('cxt:up') 16 | ; 17 | $r->route('outer') 18 | ->does('\Fortissimo\Command\Flow\Until', 'till') 19 | ->using('request', '@inner') 20 | ->using('condition', function ($cxt) { $i = $cxt->get('up', 0); return $i == 3; }) 21 | ->using('allowInternal', TRUE) 22 | ; 23 | 24 | $runner = $this->runner($r); 25 | $res = $runner->run('outer'); 26 | 27 | $this->assertEquals(3, $res->get('up')); 28 | } 29 | 30 | /** 31 | * @expectedException \Fortissimo\RequestNotFoundException 32 | */ 33 | public function testFailsOnInner() { 34 | $r = $this->registry(); //new \Fortissimo\Registry('test'); 35 | $r->route('@inner') 36 | ->does('\Fortissimo\Command\Util\Increment', 'up') 37 | ->using('startWith', 0)->from('cxt:up') 38 | ; 39 | $r->route('outer') 40 | ->does('\Fortissimo\Command\Flow\Until', 'till') 41 | ->using('request', '@inner') 42 | ->using('condition', function ($cxt) { $i = $cxt->get('up', 0); return $i == 3; }) 43 | ; 44 | 45 | $runner = $this->runner($r); 46 | $res = $runner->run('outer'); 47 | } 48 | 49 | public function testNestedRecursion() { 50 | $r = $this->registry(); 51 | $r->route('@inner') 52 | ->does('\Fortissimo\Command\Util\Increment', 'up') 53 | ->using('startWith', 0)->from('cxt:up') 54 | ; 55 | $r->route('outer') 56 | ->does('\Fortissimo\Command\Flow\Until', 'till') 57 | ->using('request', '@inner') 58 | ->using('condition', function ($cxt) { $i = $cxt->get('up', 0); return $i == 3; }) 59 | ->using('allowInternal', TRUE) 60 | ->does('\Fortissimo\Command\Util\Increment', '2up') 61 | ->using('startWith', 0)->from('cxt:2up') 62 | ; 63 | $r->route('outerouter') 64 | ->does('\Fortissimo\Command\Flow\Until', 'till') 65 | ->using('request', 'outer') 66 | ->using('condition', function ($cxt) { $i = $cxt->get('2up', 0); return $i == 3; }) 67 | ->using('allowInternal', TRUE) 68 | ; 69 | $runner = $this->runner($r); 70 | $res = $runner->run('outerouter'); 71 | 72 | $this->assertEquals(3, $res->get('2up')); 73 | $this->assertEquals(3, $res->get('up')); 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/FortissimoConfigTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($fc instanceof FortissimoConfig); 21 | } 22 | 23 | public function testGetConfig() { 24 | $fc = new FortissimoConfig(self::command); 25 | $array = $fc->getConfig(); 26 | $this->assertTrue(is_array($array), 'Returned a configuration array.'); 27 | $this->assertEquals(8, count($array), 'Has eight categories.'); 28 | } 29 | 30 | public function testIsLegalRequestName() { 31 | 32 | $good = array('a', '1', 'a1', 'a-1', '1_a', 'abcdefghijklmnop1234567-_', '-_', 'ABC'); 33 | foreach ($good as $a) { 34 | $this->assertTrue(FortissimoConfig::isLegalRequestName($a), "$a is a legal name"); 35 | } 36 | 37 | $bad = array('', ' ', '/', '|', '\\', '+', 'ø', 'å', '&', 'a*b', 'abc=def', 'url:plus', 'url?plus'); 38 | foreach ($bad as $a) { 39 | $this->assertFalse(FortissimoConfig::isLegalRequestName($a), "$a is an illegal name"); 40 | } 41 | } 42 | 43 | public function testHasRequest() { 44 | $requestName = 'item'; 45 | $fc = new \Fortissimo\Config(self::command); 46 | 47 | $this->assertTrue($fc->hasRequest($requestName), "Has a request named $request."); 48 | } 49 | 50 | public function testGetRequest() { 51 | $requestName = 'dummy'; 52 | $fc = new FortissimoConfig(self::command); 53 | 54 | $req = $fc->getRequest($requestName); 55 | 56 | $this->assertTrue($req instanceof \Fortissimo\Request, 'Request is a fortissimo request.'); 57 | $this->assertTrue($req instanceof IteratorAggregate, 'Request is iterable.'); 58 | } 59 | 60 | } 61 | 62 | class AbstractCommandMock implements \Fortissimo\Command { 63 | protected $name = NULL; 64 | protected $cxt = NULL; 65 | protected $params = NULL; 66 | 67 | public function __construct($name) { 68 | $this->name = $name; 69 | } 70 | 71 | public function execute($paramArray, \Fortissimo\ExecutionContext $cxt) { 72 | if ($paramArray['retval']) 73 | $cxt->put($this->name, $paramArray['retval']); 74 | } 75 | 76 | public function isCacheable() {return FALSE;} 77 | } 78 | 79 | class CommandMockOne extends AbstractCommandMock { 80 | } 81 | class CommandMockTwo extends AbstractCommandMock { 82 | } 83 | class CommandMockThree extends AbstractCommandMock { 84 | } 85 | -------------------------------------------------------------------------------- /src/Fortissimo/Runtime/WebRunner.php: -------------------------------------------------------------------------------- 1 | registry)) { 30 | throw new \Fortissimo\Runtime\Exception('No registry found.'); 31 | } 32 | 33 | $cxt = $this->initialContext(); 34 | //$cxt->attachFortissimo($this->ff); 35 | $cxt->attachRegistry($this->registry); 36 | 37 | try { 38 | $this->ff->handleRequest($route, $cxt, $this->allowInternalRequests); 39 | } 40 | catch(\Fortissimo\RequestNotFoundException $nfe) { 41 | $cxt->log($nfe, \Fortissimo::LOG_USER); 42 | if ($this->ff->hasRequest($route, '@404')) { 43 | return $this->run('@404'); 44 | } 45 | else { 46 | $this->generateErrorHeader('404 Not Found'); 47 | print "File Not Found"; 48 | } 49 | } 50 | return $cxt; 51 | } 52 | 53 | public function initialContext() { 54 | $cxt = $this->ff->createBasicContext(); 55 | $this->addPathsToContext($cxt); 56 | return $cxt; 57 | } 58 | 59 | protected function generateErrorHeader($msg) { 60 | if (strpos($_SERVER['GATEWAY_INTERFACE'], 'CGI') !== FALSE) { 61 | header('Status: ' . $msg); 62 | } 63 | else { 64 | header('HTTP/1.1 ' . $msg); 65 | } 66 | } 67 | 68 | /** 69 | * Add paths to the context. 70 | * 71 | * This adds standard URI paths into the context. 72 | */ 73 | protected function addPathsToContext($cxt) { 74 | $mapper = $cxt->getRequestMapper(); 75 | 76 | $fullPath = $_SERVER['REQUEST_URI']; 77 | $basePath = $mapper->basePath($fullPath); 78 | $localPath = $mapper->localPath($fullPath, $basePath); 79 | $baseURL = $mapper->baseURL(); 80 | 81 | $cxt->add('fullPath', $fullPath); 82 | $cxt->add('basePath', $basePath); 83 | $cxt->add('localPath', $localPath); 84 | $cxt->add('baseURL', $baseURL); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /doc/commands.php: -------------------------------------------------------------------------------- 1 | usesGroup('MyGroup') 20 | ->doesCommand('bar') 21 | ->whichInvokes('MyBar') 22 | ->withParam('text') 23 | ->whoseValueIs('This is some text') 24 | ->doesCommand('baz') 25 | ->whichInvokes('MyBaz') 26 | ->withParam('path') 27 | ->from('get:path') 28 | ; 29 | 30 | /** 31 | * A group is a grouping of commands that cannot be executed as a request. 32 | * 33 | * They can be referenced in requests, though. Think of it as a way to create a group 34 | * of commands that you can use whenever it is convenient. 35 | */ 36 | 37 | // Greate a group. 38 | Config::group('bootstrap') 39 | ->doesCommand('bar') 40 | ->whichInvokes('MyBar') 41 | ->withParam('text') 42 | ->whoseValueIs('This is some text') 43 | ; 44 | 45 | /* 46 | * Fortissimo provides a very thin database abstraction layer. 47 | * 48 | * To use it with MongoDB, simply customize the setup below. To use another 49 | * database, implement FortissimoDatasource, and then use the implementing 50 | * class in the invoke method here. 51 | * 52 | * You can use as many datasources as you want. Just give each one a different 53 | * name. 54 | */ 55 | 56 | // Create a new datasource which connects to a MongoDB server. 57 | Config::datasource('db') 58 | ->whichInvokes('FortissimoMongoDatasource') 59 | ->withParam('server') 60 | ->whoseValueIs('mongodb://localhost:27017') 61 | ->withParam('defaultDB') 62 | ->whoseValueIs('%%PROJECT%%') 63 | // Only one database can be set as the default. 64 | ->withParam('isDefault') 65 | ->whoseValueIs(TRUE) 66 | ; 67 | 68 | /** 69 | * Fortissimo allows you to specify one or more loggers to which 70 | * important data can be written during the processing of a request. 71 | */ 72 | 73 | // Logs directly into STDOUT (the browser, the console). 74 | Config::logger('foil') 75 | ->whichInvokes('FortissimoOutputInjectionLogger') 76 | ; 77 | 78 | // Buffers log messages in an array to be retrieved later. 79 | // Config::logger('fail') 80 | // ->whichInvokes('ForitissimoArrayInjectionLogger') 81 | // ; 82 | 83 | // Example of how a cache might be declared. 84 | // Config::cache('memcache') 85 | // ->whichInvokes('MemcacheCaching') 86 | // ->withParam('Server') 87 | // ->whoseValueIs('localhost:11211') 88 | // ; -------------------------------------------------------------------------------- /test/Tests/Fortissimo/FortissimoExecutionContextTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($cxt instanceof FortissimoExecutionContext); 16 | 17 | $cxt = new ExecutionContext(array('foo' => 'bar')); 18 | $this->assertTrue($cxt instanceof FortissimoExecutionContext); 19 | } 20 | 21 | public function testSize() { 22 | $cxt = new ExecutionContext(array('foo' => 'bar')); 23 | $this->assertEquals(1, $cxt->size()); 24 | 25 | foreach (range(1,10) as $v) $vals['n' . $v] = $v; 26 | 27 | $cxt = new ExecutionContext($vals); 28 | $this->assertEquals(10, $cxt->size()); 29 | } 30 | 31 | public function testHas() { 32 | $cxt = new ExecutionContext(array('foo' => 'bar', 'narf' => 'bargle')); 33 | $this->assertTrue($cxt->has('narf')); 34 | $this->assertFalse($cxt->has('bargle')); 35 | } 36 | 37 | public function testGet() { 38 | $cxt = new ExecutionContext(array('foo' => 'bar')); 39 | $this->assertEquals('bar', $cxt->get('foo')); 40 | 41 | $this->assertNull($cxt->get('not here')); 42 | } 43 | 44 | public function testAdd() { 45 | $cxt = new ExecutionContext(array('foo' => 'bar')); 46 | $cxt->add('narf', 'bargle'); 47 | $this->assertEquals('bargle', $cxt->get('narf')); 48 | 49 | $cxt->add('foo', 'baz'); 50 | $this->assertEquals('baz', $cxt->get('foo')); 51 | } 52 | 53 | public function testRemove() { 54 | $cxt = new ExecutionContext(array('foo' => 'bar', 'narf' => 'bargle')); 55 | $cxt->remove('narf'); 56 | $this->assertEquals(1, $cxt->size()); 57 | $this->assertNull($cxt->get('narf')); 58 | } 59 | 60 | public function testToArray() { 61 | $initial = array('foo' => 'bar'); 62 | $cxt = new ExecutionContext($initial); 63 | $this->assertEquals($initial, $cxt->toArray()); 64 | } 65 | 66 | public function testFromArray() { 67 | $initial = array('foo' => 'bar', 'narf' => 'bargle'); 68 | $cxt = new ExecutionContext(array('a' => 'b')); 69 | $cxt->fromArray($initial); 70 | $this->assertEquals(2, $cxt->size()); 71 | $this->assertTrue($cxt->has('narf')); 72 | $this->assertEquals('bargle', $cxt->get('narf')); 73 | } 74 | 75 | public function testIterator() { 76 | $cxt = new ExecutionContext(array('foo' => 'bar', 'narf' => 'bargle')); 77 | $count = 0; 78 | foreach ($cxt as $item=>$val) { 79 | $this->assertNotNull($item); 80 | $this->assertNotNull($val); 81 | ++$count; 82 | } 83 | $this->assertEquals(2, $count); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Fortissimo/Cache/Base.php: -------------------------------------------------------------------------------- 1 | params = $params; 32 | $this->name = $name; 33 | $this->default = isset($params['isDefault']) && filter_var($params['isDefault'], FILTER_VALIDATE_BOOLEAN); 34 | } 35 | 36 | public function setDatasourceManager(\Fortissimo\Datasource\Manager $manager) { 37 | $this->datasourceManager = $manager; 38 | } 39 | 40 | public function setLogManager(\Fortissimo\Logger\Manager $manager) { 41 | $this->logManager = $manager; 42 | } 43 | 44 | public function getName() { 45 | return $this->name; 46 | } 47 | 48 | /** 49 | * Determine whether this is the default cache. 50 | * 51 | * Note that this may be called *before* init(). 52 | * 53 | * @return boolean 54 | * Returns TRUE if this is the default. Typically the default status is 55 | * assigned in the commands.xml file. 56 | */ 57 | public function isDefault() { 58 | return $this->default; 59 | } 60 | 61 | /** 62 | * Perform any necessary initialization. 63 | */ 64 | public abstract function init(); 65 | 66 | /** 67 | * Add an item to the cache. 68 | * 69 | * @param string $key 70 | * A short (<255 character) string that will be used as the key. This is short 71 | * so that database-based caches can optimize for varchar fields instead of 72 | * text fields. 73 | * @param string $value 74 | * The string that will be stored as the value. 75 | * @param integer $expires_after 76 | * The number of seconds that should be considered the max age of the cached item. The 77 | * details of how this is interpreted are cache dependent. 78 | */ 79 | public abstract function set($key, $value, $expires_after = NULL); 80 | /** 81 | * Clear the entire cache. 82 | */ 83 | public abstract function clear(); 84 | /** 85 | * Delete an item from the cache. 86 | * 87 | * @param string $key 88 | * The key to remove from the cache. 89 | */ 90 | public abstract function delete($key); 91 | /** 92 | * Retrieve an item from the cache. 93 | * 94 | * @param string $key 95 | * The key to return. 96 | * @return mixed 97 | * The string found in the cache, or NULL if nothing was found. 98 | */ 99 | public abstract function get($key); 100 | } 101 | -------------------------------------------------------------------------------- /src/Fortissimo/Cache/Memcache.php: -------------------------------------------------------------------------------- 1 | cache('\Fortissimo\Cache\Memcache', 'memcache') 29 | * ->using('servers', array('example.com:11211', 'example.com:11212')) 30 | * ->using('persistent', FALSE) 31 | * ->using('compress', TRUE) 32 | * ->using('isDefault', TRUE) 33 | * ; 34 | * ?> 35 | * @endcode 36 | * 37 | * @ingroup Fortissimo 38 | */ 39 | class Memcache extends Base { 40 | protected $memcache = NULL; 41 | protected $compress = FALSE; 42 | 43 | /** 44 | * Initialize access to a memcached cluster. 45 | * 46 | */ 47 | public function init() { 48 | 49 | if (empty($this->params['server'])) { 50 | throw \Fortissimo\InterruptException('No memcache server was specified, but init was attempted.'); 51 | } 52 | 53 | $this->memcache = new \Memcache(); 54 | $this->compress = isset($this->params['compress']) 55 | && filter_var($this->params['compress'], FILTER_VALIDATE_BOOLEAN); 56 | 57 | $servers = $this->params['server']; 58 | $persist = isset($this->params['persistent']) 59 | && filter_var($this->params['persistent'], FILTER_VALIDATE_BOOLEAN); 60 | 61 | if (is_string($servers)) { 62 | $servers = array($servers); 63 | } 64 | 65 | foreach ($servers as $server) { 66 | list($name, $port) = explode(':', $server, 2); 67 | $this->memcache->addServer($name, $port, $persist); 68 | } 69 | } 70 | 71 | public function clear() { 72 | $this->memcache->flush(); 73 | } 74 | 75 | public function delete($key) { 76 | $this->memcache->delete($key); 77 | } 78 | public function set($key, $value, $expires_after = NULL) { 79 | $value = serialize($value); 80 | $this->memcache->set($key, $value, $this->compress, $expires_after); 81 | } 82 | public function get($key) { 83 | $value = $this->memcache->get($key, $this->compress); 84 | return unserialize($value); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | # Fortissimo # 2 | Copyright (c) 2010-12, Matt Butcher 3 | 4 | Fortissimo is a PHP framework. It's key features are: 5 | 6 | * _Rapid_: You can write applications quickly. 7 | * _Scalable_: Unlike other frameworks, Fortissimo is designed to scale across servers. 8 | * _Chain-of-command, not MVC_: Fortissimo uses a different base design pattern, called 9 | Chain-of-Command. CoC is easy, makes the most of reusable components, and scales 10 | very well. Take a look at commands.xml to see how this works. 11 | * _Well documented_: Every function in the library is documented, and the 12 | documentation can easily be extracted and converted to HTML (see below). In 13 | addition, your applications are self-documenting, making maintenance easier. 14 | * _NSFW_: There are No Stupid Fortissimo Wrappers. In other words, you don't need to 15 | learn a new API that simply wraps basic PHP functions. Fortissimo is thin. 16 | 17 | ## Installation ## 18 | 19 | The preferred method of installing Fortissimo is through composer. 20 | 21 | ```json 22 | { 23 | "name": "Example", 24 | "description": "Example app", 25 | "require": { 26 | "Masterminds/Fortissimo": "dev-master", 27 | }, 28 | } 29 | ``` 30 | 31 | (For a stable version, use `2.x`) 32 | 33 | You can also clone the Git repository and use it directly. 34 | 35 | ### Prerequisites ### 36 | 37 | For using Fortissimo: 38 | 39 | * [get Composer](http://getcomposer.org) to install Fortissimo. You can 40 | build it out of Git if you prefer. 41 | 42 | For developing Fortissimo: 43 | 44 | * [PHPUnit](http://phpunit.de) for unit testing 45 | * [Doxygen](http://www.stack.nl/~dimitri/doxygen/) for documentation 46 | 47 | ### Getting Started ### 48 | 49 | * Create a new project 50 | * Add a `composer.json` file like the one above 51 | * Run `composer.phar install` to fetch and install Fortissimo in your 52 | project 53 | * Start coding. 54 | - You will want to use the `\Fortissimo\Runner\WebRunner` for web apps 55 | 56 | Check out the [Wiki Documentation](https://github.com/Masterminds/Fortissimo/wiki) for more. 57 | 58 | ## More Fortissimo Goodness! 59 | 60 | * Take a look at the [Masterminds 61 | projects](https://github.com/masterminds). 62 | * [Fortissimo-Base](https://github.com/Masterminds/Fortissimo-Base) aims to be a quick-start tool for using 63 | Fortissimo. 64 | * [Fortissimo-Commons](https://github.com/Masterminds/Fortissimo-Commons) provides many common commands, including those 65 | that used to be part of Fortissimo itself. 66 | * [Forissimo-CLI-Base](https://github.com/Masterminds/Fortissimo-CLI-Base) is a quick-start/skeleton project for CLI applications using Fortissimo. 67 | * [Fortissimo-Twig](https://github.com/Masterminds/Fortissimo-Twig) provides Twig template language support. 68 | 69 | ## ABOUT THE LICENSE ## 70 | Fortissimo is licensed under the MIT license. 71 | 72 | You may build applications on top of Fortissimo without releasing your own code 73 | under an Open Source (or Free Software) license. Of course, the Fortissimo team 74 | is very grateful for any improvements you contribute back to the framework. But 75 | we believe that the best person to choose how you license your software is you. 76 | 77 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Context/AddPathToContext.php: -------------------------------------------------------------------------------- 1 | route('someRoute') 19 | * ->does('\Fortissimo\Command\Context\AddPathToContext', 'c') 20 | * ->using('path', 'foo/bar/123') 21 | * ->using('template', '%s/%s/%d') 22 | * ->using('names', array('name', 'type', 'record_id')) 23 | * ; 24 | * ?> 25 | * 26 | * Names are applied in order of match. So 'name' above will be assigned 27 | * to the match of the first '%s', which will be 'foo'. 28 | * 29 | * Running the route will add the following into the context: 30 | * 31 | * @code 32 | * 'foo', 35 | * 'type' => 'bar', 36 | * 'record_id' => 123, 37 | * ); 38 | * ?> 39 | * @endcode 40 | * 41 | * This also supports more advanced scans. Here is an example from the 42 | * unit tests: 43 | * 44 | * 45 | * @code 46 | * 50 | * @endcode 51 | * 52 | * When the above template is applied to the path, it will pick out: 53 | * 54 | *- foo 55 | *- bar 56 | *- 123 57 | * 58 | * For more information on that style of C++ scanning, see 59 | * the stdio reference: 60 | * http://www.cplusplus.com/reference/clibrary/cstdio/sscanf/ 61 | */ 62 | class AddPathToContext extends \Fortissimo\Command\Base { 63 | const RSEP = "\t"; 64 | public function expects() { 65 | return $this->description('Parse a path and store the path parts as named context items.') 66 | ->usesParam('path', 'The path to parse')->whichIsRequired() 67 | ->usesParam('template', 'A template of what the entry is expected to look like') 68 | ->whichIsRequired() 69 | ->usesParam('names', 'An array of names to which these values will be assigned.') 70 | ; 71 | } 72 | 73 | public function doCommand() { 74 | $path = $this->param('path'); 75 | $template = $this->param('template'); 76 | $names = $this->param('names'); 77 | 78 | $res = $this->scan($path, $template, $names); 79 | 80 | $this->context->addAll($res); 81 | } 82 | 83 | public function scan($path, $template, $names) { 84 | 85 | // To make sscanf see a slash as a separator, 86 | // we replace the slash with a whitespace 87 | // character. 88 | $path = strtr($path, '/', self::RSEP); 89 | $template = strtr($template, '/', self::RSEP); 90 | 91 | $matches = sscanf($path, $template); 92 | 93 | // array_combine is not robust enough to handle 94 | // sscanf failures. 95 | $count = count($matches); 96 | $add = array(); 97 | for ($i = 0; $i < $count; ++$i) { 98 | $add[$names[$i]] = $matches[$i]; 99 | } 100 | 101 | return $add; 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Fortissimo/Logger/MongoCappedLogger.php: -------------------------------------------------------------------------------- 1 | {params, facilities, name} 33 | 34 | public function init() { 35 | // Cap defaults to 1,000. 36 | if (isset($this->params['maxEntries'])) { 37 | $this->maxEntries = (int)$this->params['maxEntries']; 38 | } 39 | 40 | // Defaults to 1M. 41 | if (isset($this->params['maxSize'])) { 42 | $this->maxSize = (int)$this->params['maxSizeInBytes']; 43 | } 44 | 45 | // Get the datasource name. 46 | if (empty($this->params['mongoDatasourceName'])) { 47 | throw new \Fortissimo\InterruptException('No mongoDatasourceName was supplied to ' . $this->name); 48 | } 49 | $this->dsName = $this->params['mongoDatasourceName']; 50 | 51 | // Get the collection name. 52 | if (empty($this->params['collectionName'])) { 53 | throw new \Fortissimo\InterruptException('No collectionName was set for ' . $this->name); 54 | } 55 | $this->collectionName = $this->params['collectionName']; 56 | } 57 | 58 | // Override this to do some initialization when the datasources are set. 59 | public function setDatasourceManager(FortissimoDatasourceManager $manager) { 60 | parent::setDatasourceManager($manager); 61 | 62 | // Try to get the datasource: 63 | $dsWrapper = $this->datasourceManager->datasource($this->dsName); 64 | $this->ds = $dsWrapper->get(); 65 | if (empty($this->ds)) { 66 | throw new \Fortissimo\InterruptException('Could not get datasource named ' . $this->dsName); 67 | } 68 | if (!($this->ds instanceof \MongoDB)) { 69 | throw new \Fortissimo\InterruptException('Expected a MongoDB for ' . $this->dsName); 70 | } 71 | $this->ds->createCollection($this->collectionName, TRUE, $this->maxSize, $this->maxEntries); 72 | } 73 | 74 | public function log($msg, $category, $details) { 75 | $data = array( 76 | 'ts' => $_SERVER['REQUEST_TIME'], 77 | 'msg' => $msg, 78 | 'cat' => $category, 79 | 'dtls' => $details 80 | ); 81 | $this->ds->selectCollection($this->collectionName)->insert($data); 82 | } 83 | 84 | 85 | // Used for loggers that return buffered messages. 86 | // public function getMessages() { return array(); } 87 | } 88 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Flow/Wrapper.php: -------------------------------------------------------------------------------- 1 | parameters); 52 | 53 | foreach ($myParams as $name) { 54 | unset($params[$name]); 55 | } 56 | 57 | //fwrite(STDOUT, implode('===', array_keys($params)) . PHP_EOL); 58 | 59 | $this->childParams = $params; 60 | } 61 | 62 | /** 63 | * Parameters that should be passed through to the wrapped command. 64 | * 65 | * These are params that are not used by the wrapper, and so should 66 | * be passed through (where applicable) to the inner wrapped command. 67 | * 68 | * @return array 69 | * @retval array 70 | * The parameters to be passed through to the underlying command. 71 | * 72 | */ 73 | protected function passthruParams() { 74 | return $this->childParams; 75 | } 76 | 77 | /** 78 | * Given an alias map, rename certain given parameters. 79 | * 80 | * This takes a mapping of aliases to names, and renames 81 | * any parameters from the alias name to the real name. 82 | * 83 | * This is used for passing parameters into the wrapped command 84 | * in cases where both the wrapper and the command declare 85 | * parameters with the same name. 86 | * 87 | * @param array $aliases 88 | * A mapping of alias to real name. 89 | */ 90 | protected function parameterAliases($aliases) { 91 | foreach ($aliases as $alias => $real) { 92 | if (isset($this->childParams[$alias])) { 93 | $this->childParams[$real] = $this->childParams[$alias]; 94 | unset($this->childParams[$alias]); 95 | } 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Flow/Each.php: -------------------------------------------------------------------------------- 1 | 18 | * @endcode 19 | */ 20 | class Each extends Wrapper { 21 | public function expects() { 22 | return $this 23 | -> description('Loop through a list, running a route for each item in the list.') 24 | -> usesParam('list', 'An array through which this will loop.')->whichIsRequired() 25 | -> usesParam('command', 'A command to execute. The command will be run repeatedly and its results acculated.')->whichIsRequired() 26 | -> usesParam('commandName', 'The name of the command.') 27 | -> usesParam('aliases', 'Provide aliases for parameters that need to be passed through.') 28 | ; 29 | } 30 | public function doCommand() { 31 | $list = $this->param('list'); 32 | $command = $this->param('command'); 33 | $aliases = $this->param('aliases'); 34 | $name = $this->param('commandName'); 35 | 36 | if (empty($name)) { 37 | $name = md5(rand()); 38 | } 39 | 40 | if (!empty($aliases)) { 41 | $this->parameterAliases($aliases); 42 | } 43 | 44 | if (is_callable($command)) { 45 | $result = $this->processCallbackLoop($list, $command, $name); 46 | } 47 | else { 48 | $result = $this->processCommandLoop($list, $command, $name); 49 | } 50 | 51 | return $result; 52 | } 53 | 54 | protected function processCallbackLoop($list, $fn, $name) { 55 | $results = array(); 56 | foreach ($list as $k => $v) { 57 | // Add the list item to the context. 58 | $this->context->add($this->name . '_key', $k); 59 | $this->context->add($this->name . '_value', $v); 60 | $res = $fn($this->context, $name, $this->passthruParams()); 61 | 62 | $this->context->add($name, $res); 63 | $results[] = $res; 64 | } 65 | return $results; 66 | } 67 | 68 | protected function processCommandLoop($list, $command, $name) { 69 | //$this->ff = $this->context->fortissimo(); 70 | $results = array(); 71 | foreach ($list as $k => $v) { 72 | $params = $this->passthruParams(); 73 | $params = $this->replaceAdHocFrom($params, $k, $v); 74 | 75 | $results[] = $this->fireInnerCommand($command, $name, $params); 76 | } 77 | 78 | return $results; 79 | 80 | } 81 | 82 | protected function replaceAdHocFrom($params, $key, $value) { 83 | $prefix = $this->name . ':'; 84 | $prefix_len = strlen($prefix); 85 | foreach ($params as $pname => $pval) { 86 | if (strpos($pval, $prefix) === 0) { 87 | $replace = substr($pval, $prefix_len); 88 | if ($replace == 'key') { 89 | $params[$pname] = $key; 90 | } 91 | elseif ($replace == 'value') { 92 | $params[$pname] = $value; 93 | } 94 | } 95 | } 96 | return $params; 97 | } 98 | 99 | protected function fireInnerCommand($klass, $name, $params) { 100 | $cmd = new $klass($name); 101 | $cmd->execute($params, $this->context); 102 | 103 | return $this->context->get($name, NULL); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/Fortissimo/Logger/Manager.php: -------------------------------------------------------------------------------- 1 | loggers = &$config; 34 | } 35 | 36 | public function setCacheManager(\Fortissimo\Cache\Manager $manager) { 37 | foreach ($this->loggers as $name => $obj) $obj->setCacheManager($manager); 38 | } 39 | 40 | public function setDatasourceManager(\Fortissimo\Datasource\Manager $manager) { 41 | foreach ($this->loggers as $name => $obj) $obj->setDatasourceManager($manager); 42 | } 43 | 44 | 45 | /** 46 | * Get a logger. 47 | * 48 | * @param string $name 49 | * The name of the logger, as indicated in the configuration. 50 | * @return Fortissimo::Logger 51 | * The logger corresponding to the name, or NULL if no such logger is found. 52 | */ 53 | public function getLoggerByName($name) { 54 | return $this->loggers[$name]; 55 | } 56 | 57 | /** 58 | * Get all buffered log messages. 59 | * 60 | * Some, but by no means all, loggers buffer messages for later retrieval. 61 | * This method provides a way of retrieving all buffered messages from all 62 | * buffering loggers. Messages are simply concatenated together from all of 63 | * the available loggers. 64 | * 65 | * To fetch the log messages of just one logger instead of all of them, use 66 | * {@link getLoggerByName()}, and then call that logger's {@link Fortissimo::Logger::getMessages()} 67 | * method. 68 | * 69 | * @return array 70 | * An indexed array of messages. 71 | */ 72 | public function getMessages() { 73 | $buffer = array(); 74 | foreach ($this->loggers as $name => $logger) { 75 | $buffer += $logger->getMessages(); 76 | } 77 | return $buffer; 78 | } 79 | 80 | /** 81 | * Log messages. 82 | * 83 | * @param mixed $msg 84 | * A string or an Exception. 85 | * @param string $category 86 | * A string indicating what type of message is 87 | * being logged. Standard values for this are: 88 | * - error 89 | * - warning 90 | * - info 91 | * - debug 92 | * Your application may use whatever values are 93 | * fit. However, underlying loggers may interpret 94 | * these differently. 95 | * @param string $details 96 | * Additional information. When $msg is an exception, 97 | * this will automatically be populated with stack trace 98 | * information UNLESS explicit string information is passed 99 | * here. 100 | */ 101 | public function log($msg, $category, $details = '') { 102 | foreach ($this->loggers as $name => $logger) { 103 | $logger->rawLog($msg, $category); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Fortissimo/Runtime/Runner.php: -------------------------------------------------------------------------------- 1 | ff->createBasicContext(); 61 | } 62 | 63 | /** 64 | * Use the given registry. 65 | * 66 | * Each time a registry is set, a new internal Fortissimo 67 | * server is created -- specific to the registry. 68 | * 69 | * @param object $registry 70 | * The Fortissimo::Registry for this app. 71 | * @retval object THIS 72 | */ 73 | public function useRegistry($registry) { 74 | $this->registry = $registry; 75 | $this->ff = new \Fortissimo($registry); 76 | return $this; 77 | } 78 | 79 | /** 80 | * Execute a request (route). 81 | * 82 | * This executes the named request and returns 83 | * the final context. 84 | * 85 | * @param string $route 86 | * The name of the request. This will be resolved by the 87 | * request mapper (See Fortissimo::RequestMapper). 88 | * @retval object Fortissimo::ExecutionContext 89 | * The final context, containing whatever modifications were 90 | * made during running. 91 | * @throws Fortissimo::Runtime::Exception 92 | * Thrown when the runtime cannot be initialized or executed. 93 | */ 94 | public function run($route = 'default') { 95 | if (empty($this->registry)) { 96 | throw new \Fortissimo\Runtime\Exception('No registry found.'); 97 | } 98 | 99 | $cxt = $this->initialContext(); 100 | //$cxt->attachFortissimo($this->ff); 101 | $cxt->attachRegistry($this->registry); 102 | $this->ff->handleRequest($route, $cxt, $this->allowInternalRequests); 103 | 104 | return $cxt; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Fortissimo/Datasource/Base.php: -------------------------------------------------------------------------------- 1 | params = $params; 47 | $this->name = $name; 48 | $this->default = isset($params['isDefault']) && filter_var($params['isDefault'], FILTER_VALIDATE_BOOLEAN); 49 | } 50 | 51 | public function setCacheManager(\Fortissimo\Cache\Manager $manager) { 52 | $this->cacheManager = $manager; 53 | } 54 | 55 | public function setLogManager(\Fortissimo\Logger\Manager $manager) { 56 | $this->logManager = $manager; 57 | } 58 | 59 | /** 60 | * Get this datasource's name, as set in the configuration. 61 | * 62 | * @return string 63 | * The name of this datasource. 64 | */ 65 | public function getName() { 66 | return $this->name; 67 | } 68 | 69 | /** 70 | * Determine whether this is the default datasource. 71 | * 72 | * Note that this may be called *before* init(). 73 | * 74 | * @return boolean 75 | * Returns TRUE if this is the default. Typically the default status is 76 | * assigned in the commands.xml file. 77 | */ 78 | public function isDefault() { 79 | return $this->default; 80 | } 81 | 82 | /** 83 | * This is called once before the datasource is first used. 84 | * 85 | * While there is no guarantee that this will be called only when necessary, it 86 | * is lazier than the constructor, so initialization of connections may be better 87 | * left to this function than to overridden constructors. 88 | */ 89 | public abstract function init(); 90 | 91 | /** 92 | * Retrieve the underlying datasource object. 93 | * 94 | * Ideally, this returns the underlying data source. In some circumstances, 95 | * it may return NULL. 96 | * 97 | * @return mixed 98 | * The underlying datasource. Example: a PDO object or a Mongo object. 99 | */ 100 | public abstract function get(); 101 | } 102 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/Command/AddPathToContextTest.php: -------------------------------------------------------------------------------- 1 | registry(); 12 | 13 | $reg->route('parsepath') 14 | ->does('\Fortissimo\Command\Context\AddPathToContext', 'i') 15 | ->using('path')->from('g:path') 16 | ->using('template')->from('g:temp')// '%s/%s/%d') 17 | ->using('names', array('name', 'type', 'record_id')) 18 | ; 19 | 20 | $_GET['path'] = 'foo/bar/123'; 21 | $_GET['temp'] = '%s/%s/%d'; 22 | 23 | $runner = $this->runner($reg); 24 | $cxt = $runner->run('parsepath'); 25 | 26 | $this->assertEquals('foo', $cxt->get('name')); 27 | $this->assertEquals('bar', $cxt->get('type')); 28 | $this->assertEquals(123, $cxt->get('record_id')); 29 | 30 | $_GET['path'] = 'start-foo/bar/id-123'; 31 | $_GET['temp'] = 'start-%s/%s/id-%d'; 32 | 33 | $runner = $this->runner($reg); 34 | $cxt = $runner->run('parsepath'); 35 | 36 | $this->assertEquals('foo', $cxt->get('name')); 37 | $this->assertEquals('bar', $cxt->get('type')); 38 | $this->assertEquals(123, $cxt->get('record_id')); 39 | 40 | // Use character ranges 41 | $_GET['path'] = 'start-foo-end/bar/id-123'; 42 | $_GET['temp'] = 'start-%[a-z]-end/%s/id-%d'; 43 | 44 | $runner = $this->runner($reg); 45 | $cxt = $runner->run('parsepath'); 46 | 47 | $this->assertEquals('foo', $cxt->get('name')); 48 | $this->assertEquals('bar', $cxt->get('type')); 49 | $this->assertEquals(123, $cxt->get('record_id')); 50 | 51 | // Skip an argument, test signed int 52 | $_GET['path'] = 'foo/skip/bar/-123'; 53 | $_GET['temp'] = '%s/%*s/%s/%i'; 54 | 55 | $runner = $this->runner($reg); 56 | $cxt = $runner->run('parsepath'); 57 | 58 | $this->assertEquals('foo', $cxt->get('name')); 59 | $this->assertEquals('bar', $cxt->get('type')); 60 | $this->assertEquals(-123, $cxt->get('record_id')); 61 | 62 | // Skip an argument, test signed int 63 | $_GET['path'] = 'foo/skip/b/123'; 64 | $_GET['temp'] = '%2so/%*s/%c/%2d'; 65 | 66 | $runner = $this->runner($reg); 67 | $cxt = $runner->run('parsepath'); 68 | 69 | $this->assertEquals('fo', $cxt->get('name')); 70 | $this->assertEquals('b', $cxt->get('type')); 71 | $this->assertEquals(12, $cxt->get('record_id')); 72 | 73 | // Test partial matches 74 | $_GET['path'] = 'foo/bar/123'; 75 | $_GET['temp'] = '%s/%d/%d'; 76 | 77 | $runner = $this->runner($reg); 78 | $cxt = $runner->run('parsepath'); 79 | 80 | $this->assertEquals('foo', $cxt->get('name')); 81 | $this->assertEmpty($cxt->get('type')); 82 | $this->assertEmpty($cxt->get('record_id')); 83 | 84 | $_GET['path'] = 'foo/bar'; 85 | $_GET['temp'] = '%s/%s/%d'; 86 | 87 | $runner = $this->runner($reg); 88 | $cxt = $runner->run('parsepath'); 89 | 90 | $this->assertEquals('foo', $cxt->get('name')); 91 | $this->assertEquals('bar', $cxt->get('type')); 92 | $this->assertEmpty($cxt->get('record_id')); 93 | 94 | $_GET['path'] = 'foo/'; 95 | $_GET['temp'] = '%s/%d/%d'; 96 | 97 | $runner = $this->runner($reg); 98 | $cxt = $runner->run('parsepath'); 99 | 100 | $this->assertEquals('foo', $cxt->get('name')); 101 | $this->assertEmpty($cxt->get('type')); 102 | $this->assertEmpty($cxt->get('record_id')); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /doc/README.mdown: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ** THIS IS TOTALLY OUT OF DATE** 4 | 5 | This application was built using the Fortissimo Framework. 6 | 7 | # About Fortissimo 8 | 9 | Fortissimo is a PHP application framework designed to help 10 | software developers quickly build websites that will scale. 11 | 12 | A Fortissimo application has the following directory structure: 13 | 14 | PROJECT 15 | | 16 | |- build: Destination for Phing build commands 17 | |- doc: Destination for documentation 18 | | |- Fortissimo: Documentation (included) about Fortissimo 19 | | |- api: Complete API reference for Fortissimo. 20 | | 21 | |- test: Destination for PHPUnit unit tests 22 | |- src 23 | |- build.xml: The Phing make file 24 | |- config : All configuration files 25 | | |- commands.php: The Fortissimo command configuration file 26 | | 27 | |- core: Core files. DO NOT EDIT 28 | | |- Fortissimo: Fortissimo library. Built-in commands live here. 29 | | |- QueryPath: QueryPath library. 30 | | 31 | |- includes: Destination for application-specific code 32 | |- index.php: Bootstrap script 33 | |- .htaccess: Apache configuration directives. 34 | 35 | The primary configuration system for Fortissimo is the commands.xml file. 36 | 37 | All application-specific code should be stored in src/includes. The structure of that 38 | directory should reflect your application. To get all files to autoload correctly, 39 | you may need to add paths in the commands.php file using the `Config::includePath()` function. 40 | 41 | Core files, which should *never* be altered, can be found in src/core. These files 42 | are part of the Fortissimo framework, and will be overwritten during a framework 43 | upgrade. The main server, Fortissimo.php, is typically compressed. It, too, should 44 | not be altered. (If you need to modify a file in src/core, just copy it to src/include 45 | and do your modification there. The autoloader will load yours first.) 46 | 47 | Typically, a Fortissimo application is constructed by creating custom commands, 48 | each of which performs a single task, and then chaining the commands together using 49 | the commands.xml file. A chain of commands is wrapped in a request, where a request 50 | has an ID. When fortissimo handles a request, it will execute each command in the 51 | request one at a time. 52 | 53 | # About Twig 54 | 55 | Twig is a template engine. Fortissimo uses Twig as its presentation layer. 56 | 57 | Learn more about Twig at http://www.twig-project.org 58 | 59 | # About Phing 60 | 61 | Phing is a build tool for PHP. It is similar to ANT, Rake, and GNU Make. 62 | 63 | Use Phing to perform various tasks on your project. For example, this command 64 | can be used to generate documentation from PHPDoc comments: 65 | 66 | $ phing doc 67 | 68 | To see a list of all supported phing commands, run this: 69 | 70 | $ phing -l 71 | 72 | (That's a lowercase L) 73 | 74 | # About QueryPath 75 | 76 | Fortissimo uses the QueryPath tool to work with XML and HTML processing. QueryPath 77 | is available from anywhere in Fortissimo. You can use it in your applications to 78 | process XML and HTML. 79 | 80 | Learn more about QueryPath at http://querypath.org 81 | 82 | **QueryPath is not required for Fortissimo 2.0.** 83 | 84 | # About the License 85 | 86 | Fortissimo is released under an MIT-style license. Because of this, you are free 87 | to build your own applications, and you are not obligated to make those 88 | applications Open Source. While you are under no obligation, we encourage 89 | you to submit bugs, patches, and improvements back to the project so that 90 | the framework itself will improve over time. 91 | -------------------------------------------------------------------------------- /src/Fortissimo/Logger/Syslogger.php: -------------------------------------------------------------------------------- 1 | params['category2priority'])) { 40 | $this->catmap = $this->params['category2priority']; 41 | } 42 | 43 | // Set the app's identity. 44 | $this->ident = (isset($this->params['ident'])) ? $this->params['ident'] : 'Fortissimo'; 45 | 46 | // See if verbose logging is on. 47 | $this->verbose = isset($this->params['verbose']) 48 | && filter_var($this->params['verbose'], FILTER_VALIDATE_BOOLEAN); 49 | 50 | // Set log options: 51 | $this->opts = isset($this->params['logOptions']) ? $this->params['logOptions'] : 0; 52 | 53 | $this->facility = isset($this->params['facility']) ? (int)$this->params['facility'] : LOG_USER; 54 | 55 | openlog($this->ident, $this->opts, $this->facility); 56 | } 57 | 58 | public function log($msg, $category, $details) { 59 | 60 | $level = $this->getLogLevel($category); 61 | if ($this->verbose) { 62 | $msg .= ' ' . $details; 63 | } 64 | 65 | syslog($level, $msg); 66 | } 67 | 68 | /** 69 | * Get the Syslog logging level. 70 | * 71 | * See http://us2.php.net/manual/en/function.syslog.php for info on the logging priorities. 72 | * 73 | * This implementation does not allow you to override the built-in levels. If you want to override 74 | * built-in log levels, you must subclass this and override getLogLevel(). 75 | * 76 | * @param string $severity 77 | * The Fortissimo category. 78 | * @return int 79 | * The syslog LOG_* priority. 80 | */ 81 | protected function getLogLevel($severity) { 82 | $level = LOG_NOTICE; 83 | switch ($severity) { 84 | case \Fortissimo::LOG_FATAL: 85 | case \Fortissimo::LOG_RECOVERABLE: 86 | // Used in unit tests. 87 | case 'Exception': 88 | $level = LOG_ERR; 89 | break; 90 | case \Fortissimo::LOG_USER: 91 | $level = LOG_WARNING; 92 | break; 93 | default: 94 | $level = isset($this->catmap[$severity]) ? $this->catmap[$severity] : LOG_NOTICE; 95 | } 96 | return $level; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Flow/FoldLeft.php: -------------------------------------------------------------------------------- 1 | route('fold') 18 | * ->does('\Fortissimo\Command\Flow\FoldLeft', 'sum') 19 | * ->uses('start', 5) 20 | * ->uses('list', array(1, 1, 2)) 21 | * ->uses('command', function ($start, $head, $cxt) { return $start + $head; }) 22 | * ; 23 | * @endcode 24 | * 25 | * The function above adds the current value to the first value of the 26 | * list and returns. In a fold operation, this function is run in 27 | * sequence (left to right) for each item in the list. Since there are 28 | * three values in `list`, the callback will be executed three times: 29 | * 30 | *- 5 + 1 = 6 31 | *- 6 + 1 = 7 32 | *- 7 + 2 = 9 33 | * 34 | * Consequently, the value of `$context->get('sum')` will be 9. 35 | * 36 | * ## Anonymous Functions 37 | * 38 | * Functions are passed three parameters: 39 | * 40 | * - start: the start value 41 | * - head: the head of the list 42 | * - context: the current Fortissimo::ExecutionContext. 43 | * 44 | * ## Commands 45 | * 46 | * You can fold using a Command instead of an anonymous function. 47 | * 48 | * The command will be given only two parameters: 49 | * - start: The start value 50 | * - head: the head value 51 | * 52 | * For examples of things you can do with `FoldLeft` you may wish to 53 | * read these: 54 | *- http://oldfashionedsoftware.com/2009/07/30/lots-and-lots-of-foldleft-examples/ 55 | *- http://oldfashionedsoftware.com/2009/07/10/scala-code-review-foldleft-and-foldright/ 56 | *- https://dibblego.wordpress.com/2008/01/15/scalalistfoldleft-for-java-programmers/ 57 | */ 58 | class FoldLeft extends Wrapper { 59 | public function expects() { 60 | return $this 61 | ->description('Fold a list left (from head to tail)') 62 | ->usesParam('list', 'The list of items to fold')->whichIsRequired() 63 | ->usesParam('start', 'The initial value that the list will be folded into.') 64 | ->whichHasDefault(0) 65 | ->usesParam('command', 'A callable or a command to be run.')->whichIsRequired() 66 | ; 67 | } 68 | 69 | public function doCommand() { 70 | $list = $this->param('list'); 71 | $start = $this->param('start'); 72 | $fn = $this->param('command'); 73 | 74 | // Get the current fold value. 75 | $z = $this->get($this->name, $start); 76 | 77 | $z = $this->fold($z, $list, $fn); 78 | 79 | return $z; 80 | } 81 | 82 | /** 83 | * Perform a fold. 84 | * 85 | * @param mixed $z 86 | * The start value. 87 | * @param array $list 88 | * An indexed array. 89 | * @param callable $fn 90 | * The callable. 91 | * @return mixed 92 | * @retval mixed 93 | * The final result of the fold. 94 | */ 95 | public function fold($z, $list, $fn) { 96 | if (is_callable($fn)) { 97 | return $this->foldByCallback($z, $list, $fn); 98 | } 99 | else { 100 | return $this->foldByCommand($z, $list, $fn); 101 | } 102 | } 103 | 104 | /** 105 | * Fold using a callaback. 106 | */ 107 | public function foldByCallback($z, $list, $fn) { 108 | foreach ($list as $head) { 109 | $z = $fn($z, $head, $this->context); 110 | } 111 | return $z; 112 | } 113 | /** 114 | * Fold using a command. 115 | * 116 | * This gives the command 'start' and 'head'. 117 | */ 118 | public function foldByCommand($z, $list, $klass) { 119 | $tmpName = md5(time()); 120 | 121 | // Loop through the list 122 | foreach ($list as $head) { 123 | $params = array('start' => $z, 'head' => $head); 124 | $obj = new $klass($tmpName); 125 | $cmd->execute($params, $this->context); 126 | $z = $this->context->get($tmpName, NULL); 127 | } 128 | return $z; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/Fortissimo/Cacheable.php: -------------------------------------------------------------------------------- 1 | expects(); 25 | 26 | $params = $expectations->params(); 27 | $this->assertEquals(4, count($params), 'Command has four arguments'); 28 | 29 | // Since params should be in order, we can shift them off the top: 30 | $testString = array_shift($params); 31 | $this->assertEquals('testString', $testString->getName()); 32 | $this->assertEquals('A test string', $testString->getDescription()); 33 | 34 | $testNumeric = array_shift($params); 35 | 36 | $this->assertEquals('testNumeric', $testNumeric->getName()); 37 | 38 | // Count filters: 39 | $filters = $testNumeric->getFilters(); 40 | $this->assertEquals(1, count($filters)); 41 | $this->assertEquals('float', $filters[0]['type']); 42 | $this->assertNull($filters[0]['options']); 43 | 44 | // Manually execute a filter: 45 | $this->assertEquals(7.5, filter_var(7.5, filter_id($filters[0]['type']), NULL)); 46 | 47 | // Test a failed filter: 48 | $this->assertFalse(filter_var('matt', filter_id($filters[0]['type']), NULL), 'String is not a float.'); 49 | 50 | // Test callbacks 51 | $testNumeric2 = array_shift($params); 52 | $filters = $testNumeric2->getFilters(); 53 | $this->assertEquals('callback', $filters[0]['type']); 54 | $this->assertTrue($filters[0]['options']['options'][0] instanceof SimpleValidatorTest, 'Option callback is a SimpleValidatorTest'); 55 | 56 | $this->assertEquals(7, filter_var(3.5, FILTER_CALLBACK, $filters[0]['options'])); 57 | 58 | } 59 | 60 | public function testDoRequest() { 61 | $ff = new FortissimoHarness(self::config); 62 | $ff->handleRequest('testBaseFortissimoCommand1'); 63 | 64 | $cxt = $ff->getContext(); 65 | 66 | // Check that the command's value equals 7. 67 | $this->assertEquals(7, $cxt->get('simpleCommandTest1')); 68 | } 69 | 70 | } 71 | 72 | class SimpleValidatorTest{ 73 | 74 | //public function validate($name, $type, $value) { 75 | public function validate($value) { 76 | return $value * 2; 77 | } 78 | } 79 | 80 | class SimpleCommandTest extends \Fortissimo\Command\Base { 81 | 82 | public function expects() { 83 | 84 | return $this 85 | ->description('A test command') 86 | 87 | ->usesParam('testString', 'A test string') 88 | ->withFilter('string') 89 | 90 | ->usesParam('testNumeric', 'A test numeric value') 91 | ->withFilter('float') 92 | 93 | ->usesParam('testNumeric2', 'Another test numeric value') 94 | ->withFilter('callback', array('options' => array(new SimpleValidatorTest(), 'validate'))) 95 | 96 | ->usesParam('testInternal', 'Test internal filters') 97 | ->whichHasDefault('FOO') 98 | ->withFilter('this', 'internalValidator') 99 | ; 100 | } 101 | 102 | public function doCommand() { 103 | $param = $this->parameters; 104 | if ($this->param('testString') != 'String1') throw new Exception(sprintf('Expected String1, got %s', print_r($param, TRUE))); 105 | if ($this->param('testNumeric') != 3.5) throw new Exception('Expected float 3.5'); 106 | if ($this->param('testNumeric2') != 7) throw new Exception('Expected float to be 7'); 107 | 108 | return $this->param('testNumeric2'); 109 | } 110 | 111 | public function internalValidator($data) { 112 | return TRUE; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Fortissimo/Command.php: -------------------------------------------------------------------------------- 1 | name (only one command in each request 18 | * can have a given name), a set of zero or more params, passed as an array, 19 | * and a {@link Fortissimo::ExecutionContext} object. This last object contains 20 | * the results (if any) of previously executed commands, and is the depository for 21 | * any data that the present command needs to pass along. 22 | * 23 | * Typically, the last command in a request will format the data found in the context 24 | * and send it to the client for display. 25 | */ 26 | interface Command { 27 | /** 28 | * Create an instance of a command. 29 | * 30 | * @param string $name 31 | * Every instance of a command has a name. When a command adds information 32 | * to the context, it (by convention) stores this information keyed by name. 33 | * Other commands (perhaps other instances of the same class) can then interact 34 | * with this command by name. 35 | * @param boolean $caching 36 | * If this is set to TRUE, the command is assumed to be a caching command, 37 | * which means (a) its output can be cached, and (b) it can be served 38 | * from a cache. It is completely up to the implementation of this interface 39 | * to provide (or not to provide) a link to the caching service. See 40 | * {@link Fortissimo::Command::Base} for an example of a caching service. There is 41 | * no requirement that caching be supported by a command. 42 | */ 43 | public function __construct($name/*, $caching = FALSE*/); 44 | 45 | /** 46 | * Execute the command. 47 | * 48 | * Typically, when a command is executed, it does the following: 49 | * - uses the parameters passed as an array. 50 | * - performs one or more operations 51 | * - stores zero or more pieces of data in the context, typically keyed by this 52 | * object's $name. 53 | * 54 | * Commands do not return values. Any data they produce can be placed into 55 | * the {@link Fortissimo::ExcecutionContext} object. On the occasion of an error, 56 | * the command can either throw a {@link Fortissimo::Exception} (or any subclass 57 | * thereof), in which case the application will attempt to handle the error. Or it 58 | * may throw a {@link Fortissimo::Interrupt}, which will interrupt the flow of the 59 | * application, causing the application to forgo running the remaining commands. 60 | * 61 | * @param array $paramArray 62 | * An associative array of name/value parameters. A value may be of any data 63 | * type, including a classed object or a resource. 64 | * @param Fortissimo::ExecutionContext $cxt 65 | * The execution context. This can be modified by the command. Typically, 66 | * though, it is only written to. Reading from the context may have the 67 | * effect of making the command less portable. 68 | * @throws Fortissimo::Interrupt 69 | * Thrown when the command should be treated as the last command. The entire 70 | * request will be terminated if this is thrown. 71 | * @throws Fortissimo::Exception 72 | * Thrown if the command experiences a general execution error. This may not 73 | * result in the termination of the request. Other commands may be processed after 74 | * this. 75 | */ 76 | public function execute($paramArray, \Fortissimo\ExecutionContext $cxt); 77 | 78 | /** 79 | * Indicates whether the command's additions to the context are cacheable. 80 | * 81 | * For command-level caching to work, Fortissimo needs to be able to determine 82 | * what commands can be cached. If this method returns TRUE, Fortissimo assumes 83 | * that the objects the command places into the context can be cached using 84 | * PHP's {@link serialize()} function. 85 | * 86 | * Just because an item can be cached does not mean that it will. The 87 | * determination over whether a command's results are cached lies in the 88 | * the configuration. 89 | * 90 | * @return boolean 91 | * Boolean TRUE of the object canbe cached, FALSE otherwise. 92 | */ 93 | //public function isCacheable(); 94 | } 95 | -------------------------------------------------------------------------------- /fort: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | array( 21 | 'value' => FALSE, 22 | 'help' => 'Print the help text', 23 | ), 24 | '--explain' => array( 25 | 'value' => FALSE, 26 | 'help' => 'Turn on explaining', 27 | ), 28 | '--config' => array( 29 | 'value' => TRUE, 30 | 'help' => 'Requires FILENAME.PHP: use config with given path/name' 31 | ), 32 | '--base' => array( 33 | 'value' => TRUE, 34 | 'help' => 'Requires DIRECTORY: Use the given directory as the root of Fortissimo. The app is run FROM this directory.' 35 | ), 36 | '--no-internals' => array( 37 | 'value' => FALSE, 38 | 'help' => 'Disallow execution of internal requests (aka at-requests)', 39 | ), 40 | '--list' => array( 41 | 'value' => FALSE, 42 | 'help' => 'List all requests and exit.', 43 | ), 44 | '--ini' => array( 45 | 'value' => TRUE, 46 | 'help' => 'Requires CONF.INI. The path to a Fort INI file.', 47 | ), 48 | '--alias' => array( 49 | 'value' => TRUE, 50 | 'help' => 'Requires ALIAS. The configuration alias to read from the INI file.' 51 | ), 52 | '--get' => array( 53 | 'value' => TRUE, 54 | 'help' => 'Requires NAME=VALUE[,NAME=VALUE[,NAME=VALUE]]. Pass a name/value pair in as GET arguments.' 55 | ), 56 | ); 57 | 58 | $examples = array( 59 | "%s default" => 'Bootstrap the application in this directory and run the "default" request', 60 | "%s --config config/fort.php @create-bundle" => 'Bootstrap the application, use the fort.php instead of registry.php, and run the @create-bundle request.', 61 | "%s --ini my_fort.ini --alias MySite default" => 'Load settings from my_fort.ini, using the MySite section, and then execute the default request.', 62 | ); 63 | 64 | // Print help. 65 | if ($argc <= 1) { 66 | printf('%s expects at least one parameter. Try --help.'. PHP_EOL, $argv[0]); 67 | exit(1); 68 | } 69 | 70 | // Configure the autoloader. 71 | fort_init(); 72 | 73 | $register = new \Fortissimo\Registry(); 74 | $register->logger('\Fortissimo\Logger\OutputInjectionLogger', 'foil'); 75 | 76 | // Register commands here. 77 | fort_register($register); 78 | 79 | $runner = new \Fortissimo\Runtime\CLIRunner($argv); 80 | $runner->useRegistry($register); 81 | 82 | try { 83 | $runner->run('default'); 84 | } 85 | catch (\Exception $e) { 86 | fprintf(STDERR, "Fatal Error: %s\n", $e->getMessage()); 87 | exit(1); 88 | } 89 | 90 | // SUPPORTING FUNCTIONS 91 | 92 | function fort_init() { 93 | $composer_basedir = dirname(__DIR__); 94 | $candidates = array( 95 | '.', 96 | $composer_basedir, 97 | ); 98 | 99 | foreach ($candidates as $path) { 100 | $file = $path . '/vendor/autoload.php'; 101 | if (file_exists($file)) { 102 | require_once $file; 103 | return $path; 104 | } 105 | } 106 | throw new \Exception('Could not find an autoloader. Aborting.'); 107 | } 108 | 109 | function fort_register($registry) { 110 | global $argv, $opts; 111 | $registry->route('default') 112 | ->does('\Fortissimo\Command\CLI\ParseOptions', 'globalOptions') 113 | ->using('options', $argv) 114 | ->using('optionSpec', $opts) 115 | ->using('help', 'Interact with Fortissimo from the command line.') 116 | ->using('usage', '%s [GLOBAL_OPTIONS] COMMAND [LOCAL_OPTIONS]') 117 | ->does('\Fortissimo\Command\Flow\Iterator', 'args') 118 | ->using('array')->from('cxt:globalOptions-extra') 119 | ->does('\Fortissimo\Command\Util\Head', 'target') 120 | ->using('list')->from('cxt:args') 121 | ->does('\Fortissimo\Command\Flow\Forward') 122 | ->using('route', 'help')->from('cxt:target') 123 | ; 124 | $registry->route('help', 'Display help text.') 125 | ->does('\Fortissimo\Command\CLI\ShowHelp') 126 | ; 127 | $registry->route('run', 'Run a Fortissimo route from a given application.') 128 | ->does('\Fortissimo\Command\Util\Head', 'cmd') 129 | ->using('list')->from('cxt:args') 130 | ->does('\Fortissimo\Command\Flow\Forward') 131 | ->using('route')->from('cxt:cmd') 132 | ->does('\Fortissimo\Command\EchoText')->using('text', 'Not Implemented.') 133 | ; 134 | } 135 | -------------------------------------------------------------------------------- /test/Tests/Fortissimo/ConfigTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(8, count($cfg)); 21 | } 22 | 23 | public function testIncludePath() { 24 | Config::includePath('foo/bar'); 25 | $cfg = Config::getConfiguration(); 26 | $this->assertEquals(1, count($cfg[Config::PATHS])); 27 | 28 | Config::includePath('foo/bar2'); 29 | $cfg = Config::getConfiguration(); 30 | $this->assertEquals(2, count($cfg[Config::PATHS])); 31 | } 32 | 33 | public function testRequest() { 34 | $cfg = Config::request('foo') 35 | ->doesCommand('bing') 36 | ->whichInvokes('BingClass') 37 | ->withParam('bar') 38 | ->from('post:bar') 39 | ->doesCommand('bing2') 40 | ->withParam('another') 41 | ->whoseValueIs('turkey') 42 | ->getConfiguration(); 43 | 44 | $this->assertEquals(1, count($cfg[Config::REQUESTS])); 45 | 46 | $entry = $cfg[Config::REQUESTS]['foo']; 47 | $this->assertEquals('BingClass', $entry['bing']['class']); 48 | $this->assertEquals('post:bar', $entry['bing']['params']['bar']['from']); 49 | $this->assertEquals('turkey', $entry['bing2']['params']['another']['value']); 50 | } 51 | 52 | public function testGroup() { 53 | $cfg = Config::group('foo2') 54 | ->doesCommand('bing') 55 | ->whichInvokes('BingClass') 56 | ->withParam('bar') 57 | ->from('post:bar') 58 | ->doesCommand('bing2') 59 | ->withParam('another') 60 | ->whoseValueIs('turkey') 61 | ->getConfiguration(); 62 | 63 | $this->assertEquals(1, count($cfg[Config::GROUPS])); 64 | 65 | $entry = $cfg[Config::GROUPS]['foo2']; 66 | $this->assertEquals('BingClass', $entry['bing']['class']); 67 | $this->assertEquals('post:bar', $entry['bing']['params']['bar']['from']); 68 | $this->assertEquals('turkey', $entry['bing2']['params']['another']['value']); 69 | } 70 | 71 | public function testGroupInRequest() { 72 | Config::group('groupInRequest')->doesCommand('blarg')->whichInvokes('BlargClass'); 73 | $cfg = Config::request('myRequest') 74 | ->doesCommand('blork')->whichInvokes('BlorkClass') 75 | ->usesGroup('groupInRequest') 76 | ->doesCommand('last')->whichInvokes('LastClass') 77 | ->getConfiguration(); 78 | 79 | $expects = array('blork', 'blarg', 'last'); 80 | $i = 0; 81 | foreach ($cfg[Config::REQUESTS]['myRequest'] as $command => $params) { 82 | $this->assertEquals($expects[$i++], $command); 83 | } 84 | 85 | } 86 | 87 | public function testDatasources() { 88 | // Define one. 89 | $cfg = Config::datasource('foo')->whichInvokes('bar'); 90 | 91 | // Define a second one. 92 | $cfg = Config::datasource('db') 93 | ->whichInvokes('FortissimoMongoDatasource') 94 | ->withParam('server') 95 | ->whoseValueIs('mongodb://localhost:27017') 96 | ->withParam('defaultDB') 97 | ->whoseValueIs('BONGO') 98 | // Only one database can be set as the default. 99 | ->withParam('isDefault') 100 | ->whoseValueIs(TRUE) 101 | ->getConfiguration(); 102 | 103 | $this->assertEquals(2, count($cfg[Config::DATASOURCES])); 104 | 105 | $entry = $cfg[Config::DATASOURCES]['db']; 106 | $this->assertEquals('BONGO', $entry['params']['defaultDB']['value']); 107 | $this->assertEquals('FortissimoMongoDatasource', $entry['class']); 108 | $this->assertEquals('bar', $cfg[Config::DATASOURCES]['foo']['class']); 109 | } 110 | 111 | public function testLoggers() { 112 | $cfg = Config::logger('lumberjack')->whichInvokes('ImOkay')->getConfiguration(); 113 | $this->assertEquals('ImOkay', $cfg[Config::LOGGERS]['lumberjack']['class']); 114 | } 115 | 116 | public function testCaches() { 117 | $cfg = Config::cache('memcache')->whichInvokes('Memcachier')->getConfiguration(); 118 | $this->assertEquals('Memcachier', $cfg[Config::CACHES]['memcache']['class']); 119 | } 120 | 121 | public function testUseRequestMapper() { 122 | $cfg = Config::useRequestMapper('FortissimoRequestMapper')->getConfiguration(); 123 | $this->assertEquals('FortissimoRequestMapper', $cfg[Config::REQUEST_MAPPER]); 124 | } 125 | } 126 | 127 | -------------------------------------------------------------------------------- /src/Fortissimo/Autoloader.php: -------------------------------------------------------------------------------- 1 | Note that phar is experimental, and may be removed in future releases. 80 | */ 81 | class FortissimoAutoloader { 82 | 83 | protected $extensions = array('.php', '.cmd.php', '.inc'); 84 | protected $include_paths = array(); 85 | 86 | public function __construct() { 87 | //$full_path = get_include_path(); 88 | //$include_paths = explode(PATH_SEPARATOR, $full_path); 89 | $basePath = dirname(__FILE__); 90 | $this->include_paths[] = $basePath . '/includes'; 91 | $this->include_paths[] = $basePath . '/core'; 92 | $this->include_paths[] = $basePath . '/core/Fortissimo'; 93 | $this->include_paths[] = $basePath . '/core/Fortissimo/Theme'; 94 | $this->include_paths[] = $basePath . '/phar'; 95 | } 96 | 97 | /** 98 | * Add an array of paths to the include path used by the autoloader. 99 | * 100 | * @param array $paths 101 | * Indexed array of paths. 102 | */ 103 | public function addIncludePaths($paths) { 104 | $this->include_paths = array_merge($this->include_paths, $paths); 105 | } 106 | 107 | /** 108 | * Attempt to load the file containing the given class. 109 | * 110 | * @param string $class 111 | * The name of the class to load. 112 | * @see spl_autoload_register() 113 | */ 114 | public function load($class) { 115 | 116 | // Micro-optimization for Twig, which supplies 117 | // its own classloader. 118 | if (strpos($class, 'Twig_') === 0) return; 119 | 120 | // Namespace translation: 121 | $class = str_replace('\\', '/', $class); 122 | 123 | foreach ($this->include_paths as $dir) { 124 | $path = $dir . DIRECTORY_SEPARATOR . $class; 125 | foreach ($this->extensions as $ext) { 126 | if (file_exists($path . $ext)) { 127 | //print 'Found ' . $path . $ext . '
'; 128 | require $path . $ext; 129 | return; 130 | } 131 | } 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/BaseParameter.php: -------------------------------------------------------------------------------- 1 | name = $name; 35 | $this->description = $description; 36 | } 37 | 38 | /** 39 | * Add a filter to this parameter. 40 | * 41 | * A parameter can have any number of filters. Filters are used to 42 | * either clean (sanitize) a value or check (validate) a value. In the first 43 | * case, the system will attempt to remove bad data. In the second case, the 44 | * system will merely check to see if the data is acceptable. 45 | * 46 | * Fortissimo supports all of the filters supplied by PHP. For a complete 47 | * list, including valide options, see 48 | * {@link http://us.php.net/manual/en/book.filter.php}. 49 | * 50 | * Filters each have options, and the options can augment filter behavior, sometimes 51 | * in remarkable ways. See {@link http://us.php.net/manual/en/filter.filters.php} for 52 | * complete documentation on all filters and all of their options. 53 | * 54 | * @param string $filter 55 | * One of the predefined filter types supported by PHP. You can obtain the list 56 | * from the PHP builtin function {@link filter_list()}. Here are values currently 57 | * documented: 58 | * - int: Checks whether a value is an integer. 59 | * - boolean: Checks whether a value is a boolean. 60 | * - float: Checks whether a value is an integer (optionally, in a range). 61 | * - validate_regexp: Check whether a parameter's value matches a given regular expression. 62 | * - validate_url: Checks whether a URL is valid. 63 | * - validate_email: Checks whether a value is a valid email address. 64 | * - validate_ip: Checks whether a value is a valid IP address. 65 | * - string: Sanitizes a string, strips tags, can encode or strip special characters. 66 | * - stripped: Same as 'string' 67 | * - encoded: URL-encodes a string 68 | * - special_chars: XML/HTML entity-encodes special characters. 69 | * - unsafe_raw: Does nothing (can optionally encode/strip special chars) 70 | * - email: Removes non-Email characters 71 | * - url: Removes non-URL characters 72 | * - number_int: Removes anything that is not a digit or a sign (+ or -). 73 | * - number_float: Removes anything except digits, signs, . , e and E. 74 | * - magic_quotes: Run {@link addslashes()}. 75 | * - callback: Use the given callback to filter. 76 | * @param mixed $options 77 | * This can be either an array or an OR'd list of flags, as specified in the 78 | * PHP documentation. 79 | * @return Fortissimo::Command::BaseParameter 80 | * Returns this object to facilitate chaining. 81 | */ 82 | public function addFilter($filter, $options = NULL) { 83 | $this->filters[] = array('type' => $filter, 'options' => $options); 84 | return $this; 85 | } 86 | 87 | /** 88 | * Set all filters for this object. 89 | * Validators must be in the form: 90 | * FILTER_SOME_CONST, 'options' => array('some'=>'param')), 93 | * array('type' => FILTER_SOME_CONST, 'options' => array('some'=>'param')) 94 | * ); 95 | * ?> 96 | * @param array $filters 97 | * An indexed array of validator specifications. 98 | * @return Fortissimo::Command::BaseParameter 99 | * Returns this object to facilitate chaining. 100 | */ 101 | public function setFilters($filters) { 102 | $this->filters = $filters; 103 | return $this; 104 | } 105 | 106 | 107 | 108 | public function setRequired($required) { 109 | $this->required = $required; 110 | } 111 | 112 | public function isRequired() {return $this->required;} 113 | 114 | /** 115 | * Set the default value. 116 | */ 117 | public function setDefault($val) { 118 | $this->defaultValue = $val; 119 | } 120 | 121 | /** 122 | * Get the default value. 123 | */ 124 | public function getDefault() { 125 | return $this->defaultValue; 126 | } 127 | 128 | /** 129 | * Get the list of filters. 130 | * @return array 131 | * An array of the form specified in setFilters(). 132 | */ 133 | public function getFilters() { return $this->filters; } 134 | public function getName() { return $this->name; } 135 | public function getDescription() { return $this->description; } 136 | } 137 | -------------------------------------------------------------------------------- /src/Fortissimo/Logger/Base.php: -------------------------------------------------------------------------------- 1 | params = $params; 41 | $this->name = $name; 42 | 43 | // Add support for facility declarations. 44 | if (isset($params['categories'])) { 45 | $fac = $params['categories']; 46 | if (!is_array($fac)) { 47 | $fac = explode(',', $fac); 48 | } 49 | // Assoc arrays provide faster lookups on keys. 50 | $this->facilities = array_combine($fac, $fac); 51 | } 52 | 53 | } 54 | 55 | public function setDatasourceManager(\Fortissimo\Datasource\Manager $manager) { 56 | $this->datasourceManager = $manager; 57 | } 58 | 59 | public function setCacheManager(\Fortissimo\Cache\Manager $manager) { 60 | $this->logManager = $manager; 61 | } 62 | 63 | 64 | /** 65 | * Get the name of this logger. 66 | * 67 | * @return string 68 | * The name of this logger. 69 | */ 70 | public function getName() { 71 | return $this->name; 72 | } 73 | 74 | /** 75 | * Return log messages. 76 | * 77 | * Some, but not all, loggers buffer messages for retrieval later. This 78 | * method should be used to retrieve messages from such loggers. 79 | * 80 | * @return array 81 | * An indexed array of log message strings. By default, this returns an 82 | * empty array. 83 | */ 84 | public function getMessages() { 85 | return array(); 86 | } 87 | 88 | /** 89 | * Check whether this category is being logged. 90 | * 91 | * In general, this check is run from rawLog(), and so does not need to be 92 | * directly called elsewhere. 93 | * 94 | * @param string $category 95 | * The category to check. 96 | * @return boolean 97 | * TRUE if this is logging for the given category, false otherwise. 98 | */ 99 | public function isLoggingThisCategory($category) { 100 | return empty($this->facilities) || isset($this->facilities[$category]); 101 | } 102 | 103 | /** 104 | * Handle raw log requests. 105 | * 106 | * This handles the transformation of objects (Exceptions) 107 | * into loggable strings. 108 | * 109 | * @param mixed $message 110 | * Typically, this is an Exception, some other object, or a string. 111 | * This method normalizes the $message, converting it to a string 112 | * before handing it off to the {@link log()} function. 113 | * @param string $category 114 | * This message is passed on to the logger. 115 | * @param string $details 116 | * A detail for the given message. If $message is an Exception, then 117 | * details will be automatically filled with stack trace information. 118 | */ 119 | public function rawLog($message, $category = 'General Error', $details = '') { 120 | 121 | // If we shouldn't log this category, skip this step. 122 | if (!$this->isLoggingThisCategory($category)) return; 123 | 124 | if ($message instanceof \Exception) { 125 | $buffer = $message->getMessage(); 126 | 127 | if (empty($details)) { 128 | $details = get_class($message) . PHP_EOL; 129 | $details .= $message->getMessage() . PHP_EOL; 130 | $details .= $message->getTraceAsString(); 131 | } 132 | 133 | } 134 | elseif (is_object($message)) { 135 | $buffer = $message->toString(); 136 | } 137 | else { 138 | $buffer = $message; 139 | } 140 | $this->log($buffer, $category, $details); 141 | return; 142 | } 143 | 144 | /** 145 | * Initialize the logger. 146 | * 147 | * This will happen once per server construction (typically 148 | * once per request), and it will occur before the command is executed. 149 | */ 150 | public abstract function init(); 151 | 152 | /** 153 | * Log a message. 154 | * 155 | * @param string $msg 156 | * The message to log. 157 | * @param string $severity 158 | * The log message category. Typical values are 159 | * - warning 160 | * - error 161 | * - info 162 | * - debug 163 | * @param string $details 164 | * Further text information about the logged event. 165 | */ 166 | public abstract function log($msg, $severity, $details); 167 | 168 | } 169 | -------------------------------------------------------------------------------- /doc/doc.php: -------------------------------------------------------------------------------- 1 | request to a series of 10 | * commands. Each command is executed in sequence, and each command can build off of the 11 | * results of the previous commands. 12 | * 13 | * If you are new to Fortissimo, you should get to know the following: 14 | * 15 | * - commands.php: The configuration file. 16 | * - BaseFortissimoCommand: The base command that most of your classes will extend. 17 | * 18 | * Take a look at the built-in Fortissimo commands in src/core/Fortissimo. In particular, 19 | * the FortissimoPHPInfo command is a good starting point, as it shows how to build a command 20 | * with parameters, and it simply outputs phpinfo(). 21 | * 22 | * Learn more: 23 | * - Read QUICKSTART.mdown to get started right away 24 | * - Read the README.mdown in the documentation 25 | * - Take a look at Fortissimo's unit tests 26 | * 27 | * @section getting_started Getting Started 28 | * 29 | * To start a new project, see the documentation in the README file. It explains how to run 30 | * the command-line project generator, which will stub out your entire application for you. 31 | * 32 | * Once you have a base application, you should edit commands.php. While you can configure 33 | * several things there (loggers, caches, include paths, etc.), the main purpose of this file 34 | * is to provide a location to map a request to a chain of commands. 35 | * 36 | * For the most part, developing a Fortissimo application should consist of only a few main tasks: 37 | * define your requests in commands.php, and create commands by writing new classes that 38 | * extend BaseFortissimoCommand. 39 | * 40 | * Your commands should go in src/includes/. As long as the classname and file name are the same, 41 | * Fortissimo's autoloader will automatically find your commands and load them when necessary. 42 | * 43 | * @section default_facilities_explained Default Facilities 44 | * 45 | * Fortissimo provides several facilities that you can make use of: 46 | * 47 | * - Datasources: Fortissimo provides a facility for declaring and working with various data 48 | * storage systems such as relational SQL databases and NoSQL databases like MongoDB or even 49 | * Memcached. Fortissimo comes with support for Mongo DB (FortissimoMongoDatasource) and 50 | * PDO-based SQL drivers (FortissimoPDODatasource). Writing custom datasources is usally trivial. 51 | * - Loggers: Fortissimo has a pluggable logging system with built-in loggers for printing 52 | * straight to output (FortissimoOutputInjectionLogger), an array for later retrieval 53 | * (FortissimoArrayInjectionLogger), or to a system logger (FortissimoSyslogLogger). 54 | * - Caches: Fortissimo supports a very robust notion of caches: Requests can be cached, and 55 | * any command can declare itself cacheable. Thus, individual commands can cache data. In 56 | * addition, the caching layer is exposed to commands, which can cache arbitrary data. Extending 57 | * the caching system is trivial. A PECL/Memcache implementation is provided in 58 | * FortissimoMemcacheCache. 59 | * - Request Mapper: With the popularity of search-engine-friendly (SEF) URLs, Fortissimo provides 60 | * a generic method by which application developers can write their own URL mappers. The 61 | * default FortissimoRequestMapper provides basic support for mapping a URL to a request. You 62 | * can extend this to perform more advanced URL handling, including looking up path aliases 63 | * in a datasource. 64 | * - Include Paths: By default, Fortissimo searches the includes/ directory for your source code. 65 | * Sometimes you will want it to search elsewhere. Use include paths to add new locations for 66 | * Fortissimo to search. This is done with Config::includePath(). 67 | * 68 | * @section fortissimo_license License 69 | Fortissimo 70 | Matt Butcher 71 | Copyright (C) 2009, 2010 Matt Butcher 72 | 73 | Permission is hereby granted, free of charge, to any person obtaining a copy 74 | of this software and associated documentation files (the "Software"), to deal 75 | in the Software without restriction, including without limitation the rights 76 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 77 | copies of the Software, and to permit persons to whom the Software is 78 | furnished to do so, subject to the following conditions: 79 | 80 | The above copyright notice and this permission notice shall be included in 81 | all copies or substantial portions of the Software. 82 | 83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 84 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 85 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 86 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 87 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 88 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 89 | THE SOFTWARE. 90 | */ 91 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/Theme/BaseThemePackage.php: -------------------------------------------------------------------------------- 1 | 'main.php'); 29 | * } 30 | * 31 | * public function functions() { 32 | * // Register this object's helloWorld() function under the name 'hello' 33 | * return array('hello' => array($this, 'helloWorld')); 34 | * } 35 | * 36 | * // The helloWorld function 37 | * public function helloWorld(&$variables) { 38 | * return "Hello World!"; 39 | * } 40 | * } 41 | * ?> 42 | * @endcode 43 | * 44 | * When the above is registered, we could call either of these theme targets: 45 | * 46 | * @code 47 | * 52 | * @endcode 53 | */ 54 | class BaseThemePackage { 55 | 56 | /** 57 | * Provides a list of all of the template targets that this package supports. 58 | * 59 | * A package can declare theme functions and theme templates. This returns information 60 | * about which theme templates the class declares. The expected format of the returned 61 | * data is path. If the path is absolute (starts with a slash), an absolute path will be 62 | * used. If the path is relative (no leading slash), the theme path will be prepended. 63 | * 64 | * See the example in BaseThemePackage. 65 | * 66 | * @return array 67 | * An associative array of the form `array( 'target' => '/path/to/template.php' )`. 68 | */ 69 | public function templates() { 70 | return array(); 71 | } 72 | 73 | /** 74 | * Provides a list of all of the function targets that this package supports. 75 | * 76 | * A package can declare theme functions and theme templates. This returns information 77 | * about which theme functions the class provides. The expected format of the returned 78 | * data is an associative array of targets (names) to callbacks, where a "callback" is 79 | * any valid PHP callback, including closures and lambdas in PHP 5.3. 80 | * 81 | * See the example in BaseThemePackage. 82 | * 83 | * @return array 84 | * An associative array of the form `array( 'target' => $callback )`. 85 | * 86 | * @see http://us2.php.net/manual/en/language.pseudo-types.php#language.types.callback Callbacks 87 | */ 88 | public function functions() { 89 | return array(); 90 | } 91 | 92 | /** 93 | * Register preprocessors. 94 | * 95 | * Advanced data processing in the theme layer. 96 | * 97 | * Preprocessors are run before the main renderer. This provides an opportunity to 98 | * "hook into" the data before the page is rendered. As long as $variables is passed 99 | * by reference, you can modify the data in place. 100 | * 101 | * More than one preprocessor can be run against $variables. They are run in the order 102 | * in which they are registered. 103 | * 104 | * Example: 105 | * @code 106 | * array($this, 'menuPreprocessor'), 111 | * ); 112 | * } 113 | * 114 | * public function menuPreprocessor(&$variables) { 115 | * // $variables are the variables 116 | * } 117 | * } 118 | * ?> 119 | * @endcode 120 | * 121 | * @return array 122 | * An associative array of $target names to a preprocessor callback. 123 | */ 124 | public function preprocessors() { 125 | return array(); 126 | } 127 | 128 | /** 129 | * Register postprocessors. 130 | * 131 | * Advanced processing in the theme layer. 132 | * 133 | * See preprocessors() for an example. 134 | * 135 | * Postprocessors are executed after the target's main render function is called. 136 | * While only one renderer (template or function) can be called per item, multiple 137 | * pre- and postprocessors can be called. They are executed in sequence. 138 | * 139 | * Example: 140 | * @code 141 | * array($this, 'menuPostprocessor'), 146 | * ); 147 | * } 148 | * 149 | * public function menuPostprocessor(&$content, &$variables) { 150 | * // $content is the rendered content 151 | * // $variables are the ariables 152 | * } 153 | * } 154 | * ?> 155 | * @endcode 156 | * 157 | * @return array 158 | * An associative array of $target names to a postprocessor callback. 159 | */ 160 | public function postprocessors() { 161 | return array(); 162 | } 163 | } -------------------------------------------------------------------------------- /src/Fortissimo/Cache/Manager.php: -------------------------------------------------------------------------------- 1 | caches = $caches; 32 | } 33 | 34 | public function setLogManager($manager) { 35 | foreach ($this->caches as $name => $obj) $obj->setLogManager($manager); 36 | } 37 | 38 | public function setDatasourceManager($manager) { 39 | foreach ($this->caches as $name => $obj) $obj->setDatasourceManager($manager); 40 | } 41 | 42 | /** 43 | * Given a name, retrieve the cache. 44 | * 45 | * @return FortissimoRequestCache 46 | * If there is a cache with this name, the cache object will be 47 | * returned. Otherwise, this will return NULL. 48 | */ 49 | public function getCacheByName($name) { 50 | return $this->caches[$name]; 51 | } 52 | 53 | /** 54 | * Get an array of cache names. 55 | * 56 | * This will generate a list of names for all of the caches 57 | * that are currently active. This name can be passed to getCacheByName() 58 | * to get a particular cache. 59 | * 60 | * @return array 61 | * An indexed array of cache names. 62 | */ 63 | public function getCacheNames() { 64 | return array_keys($this->caches); 65 | } 66 | 67 | /** 68 | * Get the default cache. 69 | */ 70 | public function getDefaultCache() { 71 | foreach ($this->caches as $name => $cache) { 72 | if ($cache->isDefault()) return $cache; 73 | } 74 | } 75 | 76 | /** 77 | * Get a value from the caches. 78 | * 79 | * This will read sequentially through each defined cache until it 80 | * finds a match. If no match is found, this will return NULL. Otherwise, it 81 | * will return the value. 82 | */ 83 | public function get($key) { 84 | foreach ($this->caches as $n => $cache) { 85 | $res = $cache->get($key); 86 | 87 | // Short-circuit if we find a value. 88 | if (isset($res)) return $res; 89 | } 90 | } 91 | 92 | /** 93 | * Store a value in a cache. 94 | * 95 | * This will write a value to a cache. If a cache name is given 96 | * as the third parameter, then that named cache will be used. 97 | * 98 | * If no cache is named, the value will be stored in the first available cache. 99 | * 100 | * If no cache is found, this will silently continue. If a name is given, but the 101 | * named cache is not found, the next available cache will be used. 102 | * 103 | * @param string $key 104 | * The cache key 105 | * @param string $value 106 | * The value to store 107 | * @param string $cache 108 | * The name of the cache to store the value in. If not given, the cache 109 | * manager will store the item wherever it is most convenient. 110 | * @param int $expires_after 111 | * An integer indicating how many seconds this item should live in the cache. Some 112 | * caching backends may choose to ignore this. Some (like pecl/memcache, pecl/memcached) may 113 | * have an upper limit (30 days). Setting this to NULL will invoke the caching backend's 114 | * default. 115 | */ 116 | public function set($key, $value, $expires_after = NULL, $cache = NULL) { 117 | 118 | // If a named cache key is found, set: 119 | if (isset($cache) && isset($this->caches[$cache])) { 120 | return $this->caches[$cache]->set($key, $value, $expires_after); 121 | } 122 | 123 | // XXX: Right now, we just use the first item in the cache: 124 | /* 125 | $keys = array_keys($this->caches); 126 | if (count($keys) > 0) { 127 | return $this->caches[$keys[0]]->set($key, $value, $expires_after); 128 | } 129 | */ 130 | $cache = $this->getDefaultCache(); 131 | if (!empty($cache)) $cache->set($key, $value, $expires_after); 132 | } 133 | 134 | /** 135 | * Check whether the value is available in a cache. 136 | * 137 | * Note that in most cases, running {@link has()} before running 138 | * {@link get()} will result in the same access being run twice. For 139 | * performance reasons, you are probably better off calling just 140 | * {@link get()} if you are accessing a value. 141 | * 142 | * @param string $key 143 | * The key to check for. 144 | * @return boolean 145 | * TRUE if the key is found in the cache, false otherwise. 146 | */ 147 | public function has($key) { 148 | foreach ($this->caches as $n => $cache) { 149 | $res = $cache->has($key); 150 | 151 | // Short-circuit if we find a value. 152 | if ($res) return $res; 153 | } 154 | } 155 | 156 | /** 157 | * Check which cache (if any) contains the given key. 158 | * 159 | * If you are just trying to retrieve a cache value, use {@link get()}. 160 | * You should use this only if you are trying to determine which underlying 161 | * cache holds the given value. 162 | * 163 | * @param string $key 164 | * The key to search for. 165 | * @return string 166 | * The name of the cache that contains this key. If the key is 167 | * not found in any cache, NULL will be returned. 168 | */ 169 | public function whichCacheHas($key) { 170 | foreach ($this->caches as $n => $cache) { 171 | $res = $cache->has($key); 172 | 173 | // Short-circuit if we find a value. 174 | if ($res) return $n; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Fortissimo/Command/BaseParameterCollection.php: -------------------------------------------------------------------------------- 1 | description = $description;} 28 | 29 | public function usesParam($name, $description) { 30 | $param = new BaseParameter($name, $description); 31 | $this->params[++$this->paramCounter] = $param; 32 | 33 | return $this; 34 | } 35 | /** 36 | * Add a filter to this parameter. 37 | * 38 | * A parameter can have any number of filters. Filters are used to 39 | * either clean (sanitize) a value or check (validate) a value. In the first 40 | * case, the system will attempt to remove bad data. In the second case, the 41 | * system will merely check to see if the data is acceptable. 42 | * 43 | * Fortissimo supports all of the filters supplied by PHP. For a complete 44 | * list, including valid options, see 45 | * http://us.php.net/manual/en/book.filter.php. 46 | * 47 | * Filters each have options, and the options can augment filter behavior, sometimes 48 | * in remarkable ways. See http://us.php.net/manual/en/filter.filters.php for 49 | * complete documentation on all filters and all of their options. 50 | * 51 | * @param string $filter 52 | * One of the predefined filter types supported by PHP. You can obtain the list 53 | * from the PHP builtin function filter_list(). Here are values currently 54 | * documented: 55 | * - int: Checks whether a value is an integer. 56 | * - boolean: Checks whether a value is a boolean. 57 | * - float: Checks whether a value is an integer (optionally, in a range). 58 | * - validate_regexp: Check whether a parameter's value matches a given regular expression. 59 | * - validate_url: Checks whether a URL is valid. 60 | * - validate_email: Checks whether a value is a valid email address. 61 | * - validate_ip: Checks whether a value is a valid IP address. 62 | * - string: Sanitizes a string, strips tags, can encode or strip special characters. 63 | * - stripped: Same as 'string' 64 | * - encoded: URL-encodes a string 65 | * - special_chars: XML/HTML entity-encodes special characters. 66 | * - unsafe_raw: Does nothing (can optionally encode/strip special chars) 67 | * - email: Removes non-Email characters 68 | * - url: Removes non-URL characters 69 | * - number_int: Removes anything that is not a digit or a sign (+ or -). 70 | * - number_float: Removes anything except digits, signs, . , e and E. 71 | * - magic_quotes: Run addslashes(). 72 | * - callback: Use the given callback to filter. 73 | * - this: A convenience for 'callback' with the options array('options'=>array($this, 'func')) 74 | * @param mixed $options 75 | * This can be either an array or an OR'd list of flags, as specified in the 76 | * PHP documentation. 77 | */ 78 | public function withFilter($filter, $options = NULL) { 79 | $this->params[$this->paramCounter]->addFilter($filter, $options); 80 | return $this; 81 | } 82 | 83 | public function description() { 84 | return $this->description; 85 | } 86 | 87 | /** 88 | * Provide a description of what value or values are returned. 89 | * 90 | * @param string $description 91 | * A description of what the invoking command returns from its 92 | * {@link BaseFortissimo::Command::doCommand()} method. 93 | */ 94 | public function andReturns($description) { 95 | $this->returns = $description; 96 | return $this; 97 | } 98 | 99 | public function whichIsRequired() { 100 | $this->params[$this->paramCounter]->setRequired(TRUE); 101 | return $this; 102 | } 103 | 104 | public function whichHasDefault($default) { 105 | $this->params[$this->paramCounter]->setDefault($default); 106 | return $this; 107 | } 108 | 109 | /** 110 | * Declares an event for this command. 111 | * 112 | * This indicates (though does not enforce) that this command may 113 | * at some point in execution fire an event with the given event name. 114 | * 115 | * Event listeners can bind to this command's event and be notified when the 116 | * event fires. 117 | * 118 | * @param string $name 119 | * The name of the event. Example: 'load'. 120 | * @param string $description 121 | * A description of the event. 122 | * @return 123 | * This object. 124 | */ 125 | public function declaresEvent($name, $description) { 126 | $this->events[$name] = $description; 127 | return $this; 128 | } 129 | 130 | /** 131 | * Set all events for this object. 132 | * 133 | * The $events array must follow this form: 134 | * 135 | * @code 136 | * 'Long description help text', 139 | * 'other_event' => 'Description of conditions under which other_event is called.', 140 | * ); 141 | * ?> 142 | * @endcode 143 | */ 144 | public function setEvents(array $events) { 145 | $this->events = $events; 146 | return $this; 147 | } 148 | 149 | public function events() { return $this->events; } 150 | 151 | public function returnDescription() { 152 | return $this->returns; 153 | } 154 | 155 | public function setParams($array) { 156 | $this->params = $array; 157 | } 158 | 159 | public function params() { 160 | return $this->params; 161 | } 162 | 163 | public function getIterator() { 164 | return new \ArrayIterator($this->params); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Fortissimo/RequestMapper.php: -------------------------------------------------------------------------------- 1 | useRequestMapper('ClassName'); 19 | * ?> 20 | * @endcode 21 | * 22 | * For a user-oriented description, see App::useRequestMapper(). 23 | */ 24 | class RequestMapper { 25 | 26 | protected $loggerManager; 27 | protected $cacheManager; 28 | protected $datasourceManager; 29 | 30 | /** 31 | * Construct a new request mapper. 32 | * 33 | * 34 | * 35 | * @param FortissimoLoggerManager $loggerManager 36 | * The logger manager. 37 | * @param FortissimoCacheManager $cacheManager 38 | * The cache manager. 39 | * @param FortissimoDatasourceManager $datasourceManager 40 | * The datasource manager 41 | */ 42 | public function __construct($loggerManager, $cacheManager, $datasourceManager) { 43 | $this->loggerManager = $loggerManager; 44 | $this->cacheManager = $cacheManager; 45 | $this->datasourceManager = $datasourceManager; 46 | } 47 | 48 | 49 | /** 50 | * Map a given string to a request name. 51 | * 52 | * @param string $uri 53 | * For web apps, this is a URI passed from index.php. A commandline app may pass the request 54 | * name directly. 55 | * @return string 56 | * The name of the request to execute. 57 | */ 58 | public function uriToRequest($uri) { 59 | return $uri; 60 | } 61 | 62 | /** 63 | * Map a request into a URI (usually a URL). 64 | * 65 | * This takes a request name and transforms it into an absolute URL. 66 | */ 67 | public function requestToUri($request = 'default', $params = array(), $fragment = NULL) { 68 | // base 69 | $baseURL = $this->baseURL(); 70 | $fragment = empty($fragment) ? '' : '#' . $fragment; 71 | 72 | $buffer = $baseURL . $fragment; 73 | 74 | 75 | // FIXME: Need to respect Apache rewrite rules. 76 | if ($request != 'default') $params['ff'] = $request; 77 | 78 | if (!empty($params)) { 79 | // XXX: Do & need to be recoded as & here? 80 | $qstr = http_build_query($params); 81 | $buffer .= '?' . $qstr; 82 | } 83 | 84 | return $buffer; 85 | } 86 | 87 | /** 88 | * The base path to the application. 89 | * 90 | * The base path is calculated as the URI up to 91 | * the currently executing script. This method can 92 | * adjust to Apache mod_rewrite. 93 | * 94 | * @param string $uri 95 | * The URI. If this is not supplied, $_SERVER['REQUEST_URI'] 96 | * is used. 97 | * @retval string 98 | * The base path. 99 | */ 100 | public function basePath($uri = NULL) { 101 | if (!isset($uri)) { 102 | $uri = $_SERVER['REQUEST_URI']; 103 | } 104 | 105 | $fullPath = parse_url($uri, PHP_URL_PATH); 106 | $script = dirname($_SERVER['SCRIPT_NAME']); 107 | $basedir = substr($fullPath, 0, strlen($script)); 108 | // printf("Full: %s, Script: %s, Base: %s\n", $fullPath, $script, $basedir); 109 | 110 | return $basedir; 111 | } 112 | 113 | /** 114 | * Get the local path. 115 | * 116 | * This returns the path relative to the application's 117 | * base path. 118 | * 119 | * Generally, path is calculated from the request URI. 120 | * 121 | * Typically, this is useful in cases where the URL does not match the 122 | * script name, such as cases where mod_rewrite was used. 123 | * 124 | * @param string $uri 125 | * The URI. If not given, $_SERVER['REQUEST_URI'] is used. 126 | * @param string $basepath 127 | * The base path to the app. If not supplied, this is 128 | * computed from basePath(). 129 | * @retval string 130 | * The computed local path. This will be any part of the path 131 | * that appears after the base path. 132 | */ 133 | public function localPath($uri = NULL, $basepath = NULL) { 134 | if (!isset($uri)) { 135 | $uri = $_SERVER['REQUEST_URI']; 136 | } 137 | 138 | if (!isset($basepath)) { 139 | $basepath = $this->basePath($uri); 140 | } 141 | 142 | return substr($uri, strlen($basepath)); 143 | 144 | } 145 | 146 | /** 147 | * The canonical host name to be used in Fortissimo. 148 | * 149 | * By default, this is fetched from the $_SERVER variables as follows: 150 | * - If a Host: header is passed, this attempts to use that ($_SERVER['HTTP_HOST']) 151 | * - If no host header is passed, server name ($_SERVER['SERVER_NAME']) is used. 152 | * 153 | * This can be used in URLs and other references. 154 | * 155 | * @return string 156 | * The hostname. 157 | */ 158 | public function hostname() { 159 | return !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']; 160 | } 161 | 162 | /** 163 | * Get the base URL for this instance. 164 | * 165 | * @return string 166 | * A string of the form 'http[s]://hostname[:port]/[base_uri]' 167 | */ 168 | public function baseURL() { 169 | //$uri = empty($_SERVER['REQUEST_URI']) ? '/' : $_SERVER['REQUEST_URI']; 170 | $uri = $this->basePath(); 171 | $host = $this->hostname(); 172 | $scheme = empty($_SERVER['HTTPS']) ? 'http://' : 'https://'; 173 | 174 | $default_port = $this->isHTTPS() ? 443 : 80; 175 | 176 | if ($_SERVER['SERVER_PORT'] != $default_port) { 177 | $host .= ':' . $_SERVER['SERVER_PORT']; 178 | } 179 | 180 | return $scheme . $host . $uri; 181 | } 182 | 183 | /** 184 | * Tries to determine if the request is over SSL. 185 | * 186 | * Checks the HTTPS flag in $_SERVER and looks for the 187 | * X-Forwarded-Proto proxy header. 188 | * 189 | * @retval boolean 190 | * TRUE if this is HTTPS, FALSE otherwise. 191 | */ 192 | public function isHTTPS() { 193 | // Local support. 194 | if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') { 195 | return TRUE; 196 | } 197 | // Proxy support. 198 | if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') { 199 | return TRUE; 200 | } 201 | return FALSE; 202 | 203 | } 204 | } 205 | --------------------------------------------------------------------------------