Ver código fonte

Procedures are now compiled and cached from partials allowing for partial script injection

Jonny Wenmoth 10 anos atrás
pai
commit
f939babff7
43 arquivos alterados com 1560 adições e 956 exclusões
  1. 11 1
      phpunit.xml.dist
  2. 4 2
      src/JonnyW/PhantomJs/Cache/FileCache.php
  3. 50 182
      src/JonnyW/PhantomJs/Client.php
  4. 21 62
      src/JonnyW/PhantomJs/ClientInterface.php
  5. 221 0
      src/JonnyW/PhantomJs/Engine.php
  6. 2 2
      src/JonnyW/PhantomJs/Http/RequestInterface.php
  7. 29 2
      src/JonnyW/PhantomJs/Procedure/ChainProcedureLoader.php
  8. 15 6
      src/JonnyW/PhantomJs/Procedure/Procedure.php
  9. 161 0
      src/JonnyW/PhantomJs/Procedure/ProcedureCompiler.php
  10. 60 0
      src/JonnyW/PhantomJs/Procedure/ProcedureCompilerInterface.php
  11. 16 4
      src/JonnyW/PhantomJs/Procedure/ProcedureFactory.php
  12. 1 4
      src/JonnyW/PhantomJs/Procedure/ProcedureInterface.php
  13. 18 4
      src/JonnyW/PhantomJs/Procedure/ProcedureLoader.php
  14. 10 0
      src/JonnyW/PhantomJs/Procedure/ProcedureLoaderInterface.php
  15. 11 20
      src/JonnyW/PhantomJs/Procedure/ProcedureValidator.php
  16. 3 6
      src/JonnyW/PhantomJs/Procedure/ProcedureValidatorInterface.php
  17. 8 0
      src/JonnyW/PhantomJs/Resources/config/config.yml
  18. 44 0
      src/JonnyW/PhantomJs/Resources/config/services.yml
  19. 0 0
      src/JonnyW/PhantomJs/Resources/procedures/global_variables.partial
  20. 0 165
      src/JonnyW/PhantomJs/Resources/procedures/http_capture.proc
  21. 35 94
      src/JonnyW/PhantomJs/Resources/procedures/http_default.proc
  22. 21 0
      src/JonnyW/PhantomJs/Resources/procedures/page_clip_rect.partial
  23. 5 0
      src/JonnyW/PhantomJs/Resources/procedures/page_custom_headers.partial
  24. 12 0
      src/JonnyW/PhantomJs/Resources/procedures/page_on_error.partial
  25. 5 0
      src/JonnyW/PhantomJs/Resources/procedures/page_on_resource_received.partial
  26. 4 0
      src/JonnyW/PhantomJs/Resources/procedures/page_on_resource_timeout.partial
  27. 16 0
      src/JonnyW/PhantomJs/Resources/procedures/page_open.partial
  28. 3 0
      src/JonnyW/PhantomJs/Resources/procedures/page_settings.partial
  29. 14 0
      src/JonnyW/PhantomJs/Resources/procedures/page_viewport_size.partial
  30. 14 0
      src/JonnyW/PhantomJs/Resources/procedures/phantom_on_error.partial
  31. 25 0
      src/JonnyW/PhantomJs/Resources/procedures/procedure_capture.partial
  32. 23 0
      src/JonnyW/PhantomJs/Resources/procedures/procedure_default.partial
  33. 1 1
      src/JonnyW/PhantomJs/Resources/procedures/validator.proc
  34. 14 6
      src/JonnyW/PhantomJs/Tests/Integration/ClientTest.php
  35. 265 0
      src/JonnyW/PhantomJs/Tests/Integration/Procedure/ProcedureCompilerTest.php
  36. 5 130
      src/JonnyW/PhantomJs/Tests/Integration/Procedure/ProcedureValidatorTest.php
  37. 21 0
      src/JonnyW/PhantomJs/Tests/Unit/Cache/FileCacheTest.php
  38. 43 244
      src/JonnyW/PhantomJs/Tests/Unit/ClientTest.php
  39. 231 0
      src/JonnyW/PhantomJs/Tests/Unit/EngineTest.php
  40. 41 0
      src/JonnyW/PhantomJs/Tests/Unit/Procedure/ChainProcedureLoaderTest.php
  41. 19 3
      src/JonnyW/PhantomJs/Tests/Unit/Procedure/ProcedureFactoryTest.php
  42. 38 1
      src/JonnyW/PhantomJs/Tests/Unit/Procedure/ProcedureLoaderTest.php
  43. 20 17
      src/JonnyW/PhantomJs/Tests/Unit/Procedure/ProcedureTest.php

+ 11 - 1
phpunit.xml.dist

@@ -1,6 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<phpunit bootstrap="./src/JonnyW/PhantomJs/Tests/bootstrap.php" colors="true">
+<phpunit 
+    bootstrap                   = "./src/JonnyW/PhantomJs/Tests/bootstrap.php" 
+    backupGlobals               = "false"
+    backupStaticAttributes      = "false"
+    colors                      = "true"
+    convertErrorsToExceptions   = "true"
+    convertNoticesToExceptions  = "true"
+    convertWarningsToExceptions = "true"
+    processIsolation            = "false"
+    stopOnFailure               = "true"
+    syntaxCheck                 = "false">
     <testsuites>
         <testsuite name="PhantomJS Test Suite">
             <directory suffix="Test.php">./src/JonnyW/PhantomJs/Tests/*</directory>

+ 4 - 2
src/JonnyW/PhantomJs/Cache/FileCache.php

@@ -102,8 +102,10 @@ class FileCache implements CacheInterface
      */
     public function delete($id)
     {
-        if ($this->exists($id)) {
-            unlink($this->getFilename($id));
+        $files = glob($this->getFilename($id));
+
+        if (count($files)) {
+            array_map('unlink', $files);
         }
     }
 

+ 50 - 182
src/JonnyW/PhantomJs/Client.php

@@ -8,9 +8,8 @@
  */
 namespace JonnyW\PhantomJs;
 
-use JonnyW\PhantomJs\Exception\InvalidExecutableException;
 use JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface;
-use JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface;
+use JonnyW\PhantomJs\Procedure\ProcedureCompilerInterface;
 use JonnyW\PhantomJs\Http\MessageFactoryInterface;
 use JonnyW\PhantomJs\Http\RequestInterface;
 use JonnyW\PhantomJs\Http\ResponseInterface;
@@ -31,6 +30,14 @@ class Client implements ClientInterface
      */
     private static $instance;
 
+    /**
+     * PhantomJs engine.
+     *
+     * @var \JonnyW\PhantomJs\Engine
+     * @access protected
+     */
+    protected $engine;
+
     /**
      * Procedure loader.
      *
@@ -42,10 +49,10 @@ class Client implements ClientInterface
     /**
      * Procedure validator.
      *
-     * @var \JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface
+     * @var \JonnyW\PhantomJs\Procedure\ProcedureCompilerInterface
      * @access protected
      */
-    protected $procedureValidator;
+    protected $procedureCompiler;
 
     /**
      * Message factory.
@@ -56,53 +63,30 @@ class Client implements ClientInterface
     protected $messageFactory;
 
     /**
-     * Path to PhantomJs executable
+     * Procedure template
      *
      * @var string
      * @access protected
      */
-    protected $phantomJs;
-
-    /**
-     * Debug flag.
-     *
-     * @var boolean
-     * @access protected
-     */
-    protected $debug;
-
-    /**
-     * PhantomJs run options.
-     *
-     * @var array
-     * @access protected
-     */
-    protected $options;
-
-    /**
-     * Log info
-     *
-     * @var string
-     * @access protected
-     */
-    protected $log;
+    protected $procedure;
 
     /**
      * Internal constructor
      *
      * @access public
-     * @param  \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface    $procedureLoader
-     * @param  \JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface $procedureValidator
-     * @param  \JonnyW\PhantomJs\Http\MessageFactoryInterface          $messageFactory
+     * @param  \JonnyW\PhantomJs\Engine                               $engine
+     * @param  \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface   $procedureLoader
+     * @param  \JonnyW\PhantomJs\Procedure\ProcedureCompilerInterface $procedureCompiler
+     * @param  \JonnyW\PhantomJs\Http\MessageFactoryInterface         $messageFactory
      * @return void
      */
-    public function __construct(ProcedureLoaderInterface $procedureLoader, ProcedureValidatorInterface $procedureValidator, MessageFactoryInterface $messageFactory)
+    public function __construct(Engine $engine, ProcedureLoaderInterface $procedureLoader, ProcedureCompilerInterface $procedureCompiler, MessageFactoryInterface $messageFactory)
     {
-        $this->procedureLoader    = $procedureLoader;
-        $this->procedureValidator = $procedureValidator;
-        $this->messageFactory     = $messageFactory;
-        $this->phantomJs          = 'bin/phantomjs';
-        $this->options            = array();
+        $this->engine            = $engine;
+        $this->procedureLoader   = $procedureLoader;
+        $this->procedureCompiler = $procedureCompiler;
+        $this->messageFactory    = $messageFactory;
+        $this->procedure         = 'http_default';
     }
 
     /**
@@ -118,8 +102,9 @@ class Client implements ClientInterface
             $serviceContainer = ServiceContainer::getInstance();
 
             self::$instance = new static(
+                $serviceContainer->get('engine'),
                 $serviceContainer->get('procedure_loader'),
-                $serviceContainer->get('procedure_validator'),
+                $serviceContainer->get('procedure_compiler'),
                 $serviceContainer->get('message_factory')
             );
         }
@@ -127,6 +112,17 @@ class Client implements ClientInterface
         return self::$instance;
     }
 
+    /**
+     * Get PhantomJs engine.
+     *
+     * @access public
+     * @return \JonnyW\PhantomJs\Engine
+     */
+    public function getEngine()
+    {
+        return $this->engine;
+    }
+
     /**
      * Get message factory instance
      *
@@ -159,174 +155,46 @@ class Client implements ClientInterface
      */
     public function send(RequestInterface $request, ResponseInterface $response)
     {
-        $procedure = $this->procedureLoader->load($request->getType());
+        $procedure = $this->procedureLoader->load($this->procedure);
 
-        $this->procedureValidator->validate(
-            $this,
-            $procedure,
-            $request
-        );
+        $this->procedureCompiler->compile($procedure, $request);
 
-        $procedure->run($this, $request, $response);
+        $procedure->run($request, $response);
 
         return $response;
     }
 
     /**
-     * Get PhantomJs run command with
-     * loader and run options.
+     * Get log.
      *
      * @access public
      * @return string
      */
-    public function getCommand()
-    {
-        $phantomJs = $this->getPhantomJs();
-        $options   = $this->getOptions();
-
-        $this->validateExecutable($phantomJs);
-
-        if ($this->debug) {
-            array_push($options, '--debug=true');
-        }
-
-        return sprintf('%s %s', $phantomJs, implode(' ', $options));
-    }
-
-    /**
-     * Set new PhantomJs executable path.
-     *
-     * @access public
-     * @param  string                   $path
-     * @return \JonnyW\PhantomJs\Client
-     */
-    public function setPhantomJs($path)
-    {
-        $this->validateExecutable($path);
-
-        $this->phantomJs = $path;
-
-        return $this;
-    }
-
-    /**
-     * Get PhantomJs executable path.
-     *
-     * @access public
-     * @return string
-     */
-    public function getPhantomJs()
-    {
-        return $this->phantomJs;
-    }
-
-    /**
-     * Set PhantomJs run options.
-     *
-     * @access public
-     * @param  array                    $options
-     * @return \JonnyW\PhantomJs\Client
-     */
-    public function setOptions(array $options)
-    {
-        $this->options = $options;
-
-        return $this;
-    }
-
-    /**
-     * Get PhantomJs run options.
-     *
-     * @access public
-     * @return array
-     */
-    public function getOptions()
-    {
-        return (array) $this->options;
-    }
-
-    /**
-     * Add single PhantomJs run option.
-     *
-     * @access public
-     * @param  string                   $option
-     * @return \JonnyW\PhantomJs\Client
-     */
-    public function addOption($option)
-    {
-        if (!in_array($option, $this->options)) {
-            $this->options[] = $option;
-        }
-
-        return $this;
-    }
-
-    /**
-     * Debug.
-     *
-     * @access public
-     * @param  boolean                  $doDebug
-     * @return \JonnyW\PhantomJs\Client
-     */
-    public function debug($doDebug)
+    public function getLog()
     {
-        $this->debug = $doDebug;
-
-        return $this;
+        return $this->getEngine()->getLog();
     }
 
     /**
-     * Log info.
+     * Set procedure template.
      *
      * @access public
-     * @param  string                   $info
-     * @return \JonnyW\PhantomJs\Client
+     * @param  string $procedure
+     * @return void
      */
-    public function log($info)
+    public function setProcedure($procedure)
     {
-        $this->log = $info;
-
-        return $this;
+        $this->procedure = $procedure;
     }
 
     /**
-     * Get log info.
+     * Get procedure template.
      *
      * @access public
      * @return string
      */
-    public function getLog()
+    public function getProcedure()
     {
-        return $this->log;
-    }
-
-    /**
-     * Clear log info.
-     *
-     * @access public
-     * @return \JonnyW\PhantomJs\Client
-     */
-    public function clearLog()
-    {
-        $this->log = '';
-
-        return $this;
-    }
-
-    /**
-     * Validate execuable file.
-     *
-     * @access private
-     * @param  string                                                 $file
-     * @return boolean
-     * @throws \JonnyW\PhantomJs\Exception\InvalidExecutableException
-     */
-    private function validateExecutable($file)
-    {
-        if (!file_exists($file) || !is_executable($file)) {
-            throw new InvalidExecutableException(sprintf('File does not exist or is not executable: %s', $file));
-        }
-
-        return true;
+        return $this->procedure;
     }
 }

+ 21 - 62
src/JonnyW/PhantomJs/ClientInterface.php

@@ -27,101 +27,60 @@ interface ClientInterface
     public static function getInstance();
 
     /**
-     * Get message factory instance
+     * Get engine instance.
      *
      * @access public
-     * @return \JonnyW\PhantomJs\Http\MessageFactoryInterface
+     * @return \JonnyW\PhantomJs\Engine
      */
-    public function getMessageFactory();
+    public function getEngine();
 
     /**
-     * Send request
+     * Get message factory instance
      *
      * @access public
-     * @param \JonnyW\PhantomJs\Http\RequestInterface  $request
-     * @param \JonnyW\PhantomJs\Http\ResponseInterface $response
+     * @return \JonnyW\PhantomJs\Http\MessageFactoryInterface
      */
-    public function send(RequestInterface $request, ResponseInterface $response);
+    public function getMessageFactory();
 
     /**
-     * Get PhantomJs run command with
-     * loader and run options.
+     * Get procedure loader instance
      *
      * @access public
-     * @return string
+     * @return \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface
      */
-    public function getCommand();
+    public function getProcedureLoader();
 
     /**
-     * Set new PhantomJs executable path.
+     * Send request
      *
      * @access public
-     * @param string $path
+     * @param \JonnyW\PhantomJs\Http\RequestInterface  $request
+     * @param \JonnyW\PhantomJs\Http\ResponseInterface $response
      */
-    public function setPhantomJs($path);
+    public function send(RequestInterface $request, ResponseInterface $response);
 
     /**
-     * Get PhantomJs executable path.
+     * Get log.
      *
      * @access public
      * @return string
      */
-    public function getPhantomJs();
-
-    /**
-     * Set PhantomJs run options.
-     *
-     * @access public
-     * @param array $options
-     */
-    public function setOptions(array $options);
-
-    /**
-     * Get PhantomJs run options.
-     *
-     * @access public
-     * @return array
-     */
-    public function getOptions();
-
-    /**
-     * Add single PhantomJs run option.
-     *
-     * @access public
-     * @param string $option
-     */
-    public function addOption($option);
-
-    /**
-     * Debug.
-     *
-     * @access public
-     * @param boolean $doDebug
-     */
-    public function debug($doDebug);
+    public function getLog();
 
     /**
-     * Log info.
+     * Set procedure template.
      *
      * @access public
-     * @param  string                   $info
-     * @return \JonnyW\PhantomJs\Client
+     * @param  string $procedure
+     * @return void
      */
-    public function log($info);
+    public function setProcedure($procedure);
 
     /**
-     * Get log info.
+     * Get procedure template.
      *
      * @access public
      * @return string
      */
-    public function getLog();
-
-    /**
-     * Clear log info.
-     *
-     * @access public
-     * @return \JonnyW\PhantomJs\Client
-     */
-    public function clearLog();
+    public function getProcedure();
 }

+ 221 - 0
src/JonnyW/PhantomJs/Engine.php

@@ -0,0 +1,221 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs;
+
+use JonnyW\PhantomJs\Exception\InvalidExecutableException;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class Engine
+{
+    /**
+     * Executable path.
+     *
+     * @var string
+     * @access protected
+     */
+    protected $path;
+
+    /**
+     * Debug flag.
+     *
+     * @var boolean
+     * @access protected
+     */
+    protected $debug;
+
+    /**
+     * PhantomJs run options.
+     *
+     * @var array
+     * @access protected
+     */
+    protected $options;
+
+    /**
+     * Log info
+     *
+     * @var string
+     * @access protected
+     */
+    protected $log;
+
+    /**
+     * Internal constructor
+     *
+     * @access public
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->path    = 'bin/phantomjs';
+        $this->options = array();
+    }
+
+    /**
+     * Get PhantomJs run command with
+     * loader run options.
+     *
+     * @access public
+     * @return string
+     */
+    public function getCommand()
+    {
+        $path    = $this->getPath();
+        $options = $this->getOptions();
+
+        $this->validateExecutable($path);
+
+        if ($this->debug) {
+            array_push($options, '--debug=true');
+        }
+
+        return sprintf('%s %s', $path, implode(' ', $options));
+    }
+
+    /**
+     * Set path.
+     *
+     * @access public
+     * @param  string                   $path
+     * @return \JonnyW\PhantomJs\Client
+     */
+    public function setPath($path)
+    {
+        $this->validateExecutable($path);
+
+        $this->path = $path;
+
+        return $this;
+    }
+
+    /**
+     * Get path.
+     *
+     * @access public
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * Set PhantomJs run options.
+     *
+     * @access public
+     * @param  array                    $options
+     * @return \JonnyW\PhantomJs\Client
+     */
+    public function setOptions(array $options)
+    {
+        $this->options = $options;
+
+        return $this;
+    }
+
+    /**
+     * Get PhantomJs run options.
+     *
+     * @access public
+     * @return array
+     */
+    public function getOptions()
+    {
+        return (array) $this->options;
+    }
+
+    /**
+     * Add single PhantomJs run option.
+     *
+     * @access public
+     * @param  string                   $option
+     * @return \JonnyW\PhantomJs\Client
+     */
+    public function addOption($option)
+    {
+        if (!in_array($option, $this->options)) {
+            $this->options[] = $option;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Debug.
+     *
+     * @access public
+     * @param  boolean                  $doDebug
+     * @return \JonnyW\PhantomJs\Client
+     */
+    public function debug($doDebug)
+    {
+        $this->debug = $doDebug;
+
+        return $this;
+    }
+
+    /**
+     * Log info.
+     *
+     * @access public
+     * @param  string                   $info
+     * @return \JonnyW\PhantomJs\Client
+     */
+    public function log($info)
+    {
+        $this->log = $info;
+
+        return $this;
+    }
+
+    /**
+     * Get log info.
+     *
+     * @access public
+     * @return string
+     */
+    public function getLog()
+    {
+        return $this->log;
+    }
+
+    /**
+     * Clear log info.
+     *
+     * @access public
+     * @return \JonnyW\PhantomJs\Client
+     */
+    public function clearLog()
+    {
+        $this->log = '';
+
+        return $this;
+    }
+
+    /**
+     * Validate execuable file.
+     *
+     * @access private
+     * @param  string                                                 $file
+     * @return boolean
+     * @throws \JonnyW\PhantomJs\Exception\InvalidExecutableException
+     */
+    private function validateExecutable($file)
+    {
+        if (!file_exists($file) || !is_executable($file)) {
+            throw new InvalidExecutableException(sprintf('File does not exist or is not executable: %s', $file));
+        }
+
+        return true;
+    }
+}

+ 2 - 2
src/JonnyW/PhantomJs/Http/RequestInterface.php

@@ -24,8 +24,8 @@ interface RequestInterface
     const METHOD_DELETE  = 'DELETE';
     const METHOD_PATCH   = 'PATCH';
 
-    const REQUEST_TYPE_DEFAULT = 'http_default';
-    const REQUEST_TYPE_CAPTURE = 'http_capture';
+    const REQUEST_TYPE_DEFAULT = 'default';
+    const REQUEST_TYPE_CAPTURE = 'capture';
 
     /**
      * Get request type

+ 29 - 2
src/JonnyW/PhantomJs/Procedure/ChainProcedureLoader.php

@@ -43,7 +43,7 @@ class ChainProcedureLoader implements ProcedureLoaderInterface
      */
     public function addLoader(ProcedureLoaderInterface $procedureLoader)
     {
-        $this->procedureLoaders[] = $procedureLoader;
+        array_unshift($this->procedureLoaders, $procedureLoader);
     }
 
     /**
@@ -56,7 +56,7 @@ class ChainProcedureLoader implements ProcedureLoaderInterface
      */
     public function load($id)
     {
-        /** @var \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface $loader */
+        /** @var \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface $loader **/
         foreach ($this->procedureLoaders as $loader) {
 
             try {
@@ -71,4 +71,31 @@ class ChainProcedureLoader implements ProcedureLoaderInterface
 
         throw new \InvalidArgumentException(sprintf('No valid procedure loader could be found to load the \'%s\' procedure.', $id));
     }
+
+    /**
+     * Load procedure template by id.
+     *
+     * @access public
+     * @param  string                    $id
+     * @param  string                    $extension (default: 'proc')
+     * @throws \InvalidArgumentException
+     * @return string
+     */
+    public function loadTemplate($id, $extension = 'proc')
+    {
+        /** @var \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface $loader **/
+        foreach ($this->procedureLoaders as $loader) {
+
+            try {
+
+                $template = $loader->loadTemplate($id, $extension);
+
+                return $template;
+
+            } catch (\Exception $e) {}
+
+        }
+
+        throw new \InvalidArgumentException(sprintf('No valid procedure loader could be found to load the \'%s\' procedure template.', $id));
+    }
 }

+ 15 - 6
src/JonnyW/PhantomJs/Procedure/Procedure.php

@@ -8,7 +8,7 @@
  */
 namespace JonnyW\PhantomJs\Procedure;
 
-use JonnyW\PhantomJs\ClientInterface;
+use JonnyW\PhantomJs\Engine;
 use JonnyW\PhantomJs\Cache\CacheInterface;
 use JonnyW\PhantomJs\Parser\ParserInterface;
 use JonnyW\PhantomJs\Template\TemplateRendererInterface;
@@ -22,6 +22,14 @@ use JonnyW\PhantomJs\Exception\ProcedureFailedException;
  */
 class Procedure implements ProcedureInterface
 {
+    /**
+     * PhantomJS engine
+     *
+     * @var \JonnyW\PhantomJs\Engine
+     * @access protected
+     */
+    protected $engine;
+
     /**
      * Parser instance.
      *
@@ -58,12 +66,14 @@ class Procedure implements ProcedureInterface
      * Internal constructor.
      *
      * @access public
+     * @param \JonnyW\PhantomJs\Engine                             $engine
      * @param \JonnyW\PhantomJs\Parser\ParserInterface             $parser
      * @param \JonnyW\PhantomJs\Cache\CacheInterface               $cacheHandler
      * @param \JonnyW\PhantomJs\Template\TemplateRendererInterface $renderer
      */
-    public function __construct(ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
+    public function __construct(Engine $engine, ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
     {
+        $this->engine       = $engine;
         $this->parser       = $parser;
         $this->cacheHandler = $cacheHandler;
         $this->renderer     = $renderer;
@@ -73,14 +83,13 @@ class Procedure implements ProcedureInterface
      * Run procedure.
      *
      * @access public
-     * @param  \JonnyW\PhantomJs\ClientInterface                    $client
      * @param  \JonnyW\PhantomJs\Procedure\InputInterface           $input
      * @param  \JonnyW\PhantomJs\Procedure\OutputInterface          $output
      * @throws \JonnyW\PhantomJs\Exception\ProcedureFailedException
      * @throws \JonnyW\PhantomJs\Exception\NotWritableException
      * @return void
      */
-    public function run(ClientInterface $client, InputInterface $input, OutputInterface $output)
+    public function run(InputInterface $input, OutputInterface $output)
     {
         try {
 
@@ -94,7 +103,7 @@ class Procedure implements ProcedureInterface
                 array('pipe', 'w')
             );
 
-            $process = proc_open(escapeshellcmd(sprintf('%s %s', $client->getCommand(), $executable)), $descriptorspec, $pipes, null, null);
+            $process = proc_open(escapeshellcmd(sprintf('%s %s', $this->engine->getCommand(), $executable)), $descriptorspec, $pipes, null, null);
 
             if (!is_resource($process)) {
                 throw new ProcedureFailedException('proc_open() did not return a resource');
@@ -113,7 +122,7 @@ class Procedure implements ProcedureInterface
                 $this->parser->parse($result)
             );
 
-            $client->log($log);
+            $this->engine->log($log);
 
             $this->remove($executable);
 

+ 161 - 0
src/JonnyW/PhantomJs/Procedure/ProcedureCompiler.php

@@ -0,0 +1,161 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace JonnyW\PhantomJs\Procedure;
+
+use JonnyW\PhantomJs\Cache\CacheInterface;
+use JonnyW\PhantomJs\Template\TemplateRendererInterface;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class ProcedureCompiler implements ProcedureCompilerInterface
+{
+    /**
+     * Procedure loader
+     *
+     * @var \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface
+     * @access protected
+     */
+    protected $procedureLoader;
+
+    /**
+     * Procedure validator
+     *
+     * @var \JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface
+     * @access protected
+     */
+    protected $procedureValidator;
+
+    /**
+     * Cache handler
+     *
+     * @var \JonnyW\PhantomJs\Cache\CacheInterface
+     * @access protected
+     */
+    protected $cacheHandler;
+
+    /**
+     * Renderer
+     *
+     * @var \JonnyW\PhantomJs\Template\TemplateRendererInterface
+     * @access protected
+     */
+    protected $renderer;
+
+    /**
+     * Cache enabled
+     *
+     * @var boolean
+     * @access protected
+     */
+    protected $cacheEnabled;
+
+    /**
+     * Internal constructor
+     *
+     * @access public
+     * @param \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface    $procedureLoader
+     * @param \JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface $procedureValidator
+     * @param \JonnyW\PhantomJs\Cache\CacheInterface                  $cacheHandler
+     * @param \JonnyW\PhantomJs\Template\TemplateRendererInterface    $renderer
+     */
+    public function __construct(ProcedureLoaderInterface $procedureLoader, ProcedureValidatorInterface $procedureValidator,
+        CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
+    {
+        $this->procedureLoader    = $procedureLoader;
+        $this->procedureValidator = $procedureValidator;
+        $this->cacheHandler       = $cacheHandler;
+        $this->renderer           = $renderer;
+        $this->cacheEnabled       = true;
+    }
+
+    /**
+     * Compile partials into procedure.
+     *
+     * @access public
+     * @param  \JonnyW\PhantomJs\Procedure\ProcedureInterface $procedure
+     * @param  \JonnyW\PhantomJs\Procedure\InputInterface     $input
+     * @return void
+     */
+    public function compile(ProcedureInterface $procedure, InputInterface $input)
+    {
+        $cacheKey = sprintf('phantomjs_%s_%s', $input->getType(), md5($procedure->getTemplate()));
+
+        if ($this->cacheEnabled && $this->cacheHandler->exists($cacheKey)) {
+            $template = $this->cacheHandler->fetch($cacheKey);
+        }
+
+        if (empty($template)) {
+
+            $template  = $this->renderer
+                ->render($procedure->getTemplate(), array('engine' => $this, 'procedure_type' => $input->getType()));
+
+            $test = clone $procedure;
+            $test->setTemplate($template);
+
+            $compiled = $test->compile($input);
+
+            $this->procedureValidator->validate($compiled);
+
+            if ($this->cacheEnabled) {
+                $this->cacheHandler->save($cacheKey, $template);
+            }
+        }
+
+        $procedure->setTemplate($template);
+    }
+
+    /**
+     * Load partial template.
+     *
+     * @access public
+     * @param  string $name
+     * @return string
+     */
+    public function load($name)
+    {
+        return $this->procedureLoader->loadTemplate($name, 'partial');
+    }
+
+    /**
+     * Enable cache.
+     *
+     * @access public
+     * @return void
+     */
+    public function enableCache()
+    {
+        $this->cacheEnabled = true;
+    }
+
+    /**
+     * Disable cache.
+     *
+     * @access public
+     * @return void
+     */
+    public function disableCache()
+    {
+        $this->cacheEnabled = false;
+    }
+
+    /**
+     * Clear cache.
+     *
+     * @access public
+     * @return void
+     */
+    public function clearCache()
+    {
+        $this->cacheHandler->delete('phantomjs_*');
+    }
+}

+ 60 - 0
src/JonnyW/PhantomJs/Procedure/ProcedureCompilerInterface.php

@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace JonnyW\PhantomJs\Procedure;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+interface ProcedureCompilerInterface
+{
+    /**
+     * Compile partials into procedure.
+     *
+     * @access public
+     * @param \JonnyW\PhantomJs\Procedure\ProcedureInterface $procedure
+     * @param \JonnyW\PhantomJs\Procedure\InputInterface     $input
+     */
+    public function compile(ProcedureInterface $procedure, InputInterface $input);
+
+    /**
+     * Load partial template.
+     *
+     * @access public
+     * @param  string $name
+     * @return string
+     */
+    public function load($name);
+
+    /**
+     * Enable cache.
+     *
+     * @access public
+     * @return void
+     */
+    public function enableCache();
+
+    /**
+     * Disable cache.
+     *
+     * @access public
+     * @return void
+     */
+    public function disableCache();
+
+    /**
+     * Clear cache.
+     *
+     * @access public
+     * @return void
+     */
+    public function clearCache();
+}

+ 16 - 4
src/JonnyW/PhantomJs/Procedure/ProcedureFactory.php

@@ -9,9 +9,10 @@
 
 namespace JonnyW\PhantomJs\Procedure;
 
-use \JonnyW\PhantomJs\Cache\CacheInterface;
-use \JonnyW\PhantomJs\Parser\ParserInterface;
-use \JonnyW\PhantomJs\Template\TemplateRendererInterface;
+use JonnyW\PhantomJs\Engine;
+use JonnyW\PhantomJs\Cache\CacheInterface;
+use JonnyW\PhantomJs\Parser\ParserInterface;
+use JonnyW\PhantomJs\Template\TemplateRendererInterface;
 
 /**
  * PHP PhantomJs
@@ -20,6 +21,14 @@ use \JonnyW\PhantomJs\Template\TemplateRendererInterface;
  */
 class ProcedureFactory implements ProcedureFactoryInterface
 {
+    /**
+     * PhantomJS engine
+     *
+     * @var \JonnyW\PhantomJs\Engine
+     * @access protected
+     */
+    protected $engine;
+
     /**
      * Parser.
      *
@@ -48,12 +57,14 @@ class ProcedureFactory implements ProcedureFactoryInterface
      * Internal constructor.
      *
      * @access public
+     * @param \JonnyW\PhantomJs\Engine                             $engine
      * @param \JonnyW\PhantomJs\Parser\ParserInterface             $parser
      * @param \JonnyW\PhantomJs\Cache\CacheInterface               $cacheHandler
      * @param \JonnyW\PhantomJs\Template\TemplateRendererInterface $renderer
      */
-    public function __construct(ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
+    public function __construct(Engine $engine, ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
     {
+        $this->engine       = $engine;
         $this->parser       = $parser;
         $this->cacheHandler = $cacheHandler;
         $this->renderer     = $renderer;
@@ -68,6 +79,7 @@ class ProcedureFactory implements ProcedureFactoryInterface
     public function createProcedure()
     {
         $procedure = new Procedure(
+            $this->engine,
             $this->parser,
             $this->cacheHandler,
             $this->renderer

+ 1 - 4
src/JonnyW/PhantomJs/Procedure/ProcedureInterface.php

@@ -8,8 +8,6 @@
  */
 namespace JonnyW\PhantomJs\Procedure;
 
-use JonnyW\PhantomJs\ClientInterface;
-
 /**
  * PHP PhantomJs
  *
@@ -21,11 +19,10 @@ interface ProcedureInterface
      * Run procedure.
      *
      * @access public
-     * @param \JonnyW\PhantomJs\ClientInterface           $client
      * @param \JonnyW\PhantomJs\Procedure\InputInterface  $input
      * @param \JonnyW\PhantomJs\Procedure\OutputInterface $output
      */
-    public function run(ClientInterface $client, InputInterface $input, OutputInterface $output);
+    public function run(InputInterface $input, OutputInterface $output);
 
     /**
      * Set procedure template.

+ 18 - 4
src/JonnyW/PhantomJs/Procedure/ProcedureLoader.php

@@ -56,15 +56,29 @@ class ProcedureLoader implements ProcedureLoaderInterface
      */
     public function load($id)
     {
-        $path     = $this->locator->locate(sprintf('%s.proc', $id));
-        $template = $this->loadFile($path);
-
         $procedure = $this->procedureFactory->createProcedure();
-        $procedure->setTemplate($template);
+        $procedure->setTemplate(
+            $this->loadTemplate($id)
+        );
 
         return $procedure;
     }
 
+    /**
+     * Load procedure template by id.
+     *
+     * @access public
+     * @param  string $id
+     * @param  string $extension (default: 'proc')
+     * @return string
+     */
+    public function loadTemplate($id, $extension = 'proc')
+    {
+        $path = $this->locator->locate(sprintf('%s.%s', $id, $extension));
+
+        return $this->loadFile($path);
+    }
+
     /**
      * Load procedure file content.
      *

+ 10 - 0
src/JonnyW/PhantomJs/Procedure/ProcedureLoaderInterface.php

@@ -23,4 +23,14 @@ interface ProcedureLoaderInterface
      * @return \JonnyW\PhantomJs\Procedure\ProcedureInterface
      */
     public function load($id);
+
+    /**
+     * Load procedure template by id.
+     *
+     * @access public
+     * @param  string $id
+     * @param  string $extension (default: 'proc')
+     * @return string
+     */
+    public function loadTemplate($id, $extension = 'proc');
 }

+ 11 - 20
src/JonnyW/PhantomJs/Procedure/ProcedureValidator.php

@@ -8,7 +8,6 @@
  */
 namespace JonnyW\PhantomJs\Procedure;
 
-use JonnyW\PhantomJs\ClientInterface;
 use JonnyW\PhantomJs\Validator\EngineInterface;
 use JonnyW\PhantomJs\Exception\SyntaxException;
 use JonnyW\PhantomJs\Exception\RequirementException;
@@ -53,20 +52,14 @@ class ProcedureValidator implements ProcedureValidatorInterface
      * Validate procedure.
      *
      * @access public
-     * @param  \JonnyW\PhantomJs\ClientInterface                        $client
-     * @param  \JonnyW\PhantomJs\Procedure\ProcedureInterface           $procedure
-     * @param  \JonnyW\PhantomJs\Procedure\InputInterface               $message
+     * @param  string                                                   $procedure
      * @return boolean
      * @throws \JonnyW\PhantomJs\Exception\ProcedureValidationException
      */
-    public function validate(ClientInterface $client, ProcedureInterface $procedure, InputInterface $message)
+    public function validate($procedure)
     {
-        $compiled = $procedure->compile(
-            $message
-        );
-
-        $this->validateSyntax($client, $compiled);
-        $this->validateRequirements($client, $compiled);
+        $this->validateSyntax($procedure);
+        $this->validateRequirements($procedure);
 
         return true;
     }
@@ -75,21 +68,20 @@ class ProcedureValidator implements ProcedureValidatorInterface
      * Validate syntax.
      *
      * @access protected
-     * @param  \JonnyW\PhantomJs\ClientInterface           $client
-     * @param  stromg                                      $compiled
+     * @param  string                                      $procedure
      * @return void
      * @throws \JonnyW\PhantomJs\Exception\SyntaxException
      */
-    protected function validateSyntax(ClientInterface $client, $compiled)
+    protected function validateSyntax($procedure)
     {
         $input  = new Input();
         $output = new Output();
 
-        $input->set('procedure', $compiled);
+        $input->set('procedure', $procedure);
         $input->set('engine', $this->engine->toString());
 
         $validator = $this->procedureLoader->load('validator');
-        $validator->run($client, $input, $output);
+        $validator->run($input, $output);
 
         $errors = $output->get('errors');
 
@@ -102,14 +94,13 @@ class ProcedureValidator implements ProcedureValidatorInterface
      * validateRequirements function.
      *
      * @access protected
-     * @param  \JonnyW\PhantomJs\ClientInterface                $client
-     * @param  stromg                                           $compiled
+     * @param  string                                           $procedure
      * @return void
      * @throws \JonnyW\PhantomJs\Exception\RequirementException
      */
-    protected function validateRequirements(ClientInterface $client, $compiled)
+    protected function validateRequirements($procedure)
     {
-        if (preg_match('/phantom\.exit\(/', $compiled, $matches) !== 1) {
+        if (preg_match('/phantom\.exit\(/', $procedure, $matches) !== 1) {
             throw new RequirementException('Your procedure must contain a \'phantom.exit(1);\' command to avoid the PhantomJS process hanging');
         }
     }

+ 3 - 6
src/JonnyW/PhantomJs/Procedure/ProcedureValidatorInterface.php

@@ -8,8 +8,6 @@
  */
 namespace JonnyW\PhantomJs\Procedure;
 
-use JonnyW\PhantomJs\ClientInterface;
-
 /**
  * PHP PhantomJs
  *
@@ -21,10 +19,9 @@ interface ProcedureValidatorInterface
      * Validate procedure.
      *
      * @access public
-     * @param  \JonnyW\PhantomJs\ClientInterface              $client
-     * @param  \JonnyW\PhantomJs\Procedure\ProcedureInterface $procedure
-     * @param  \JonnyW\PhantomJs\Procedure\InputInterface     $message
+     * @param  string                                                   $procedure
      * @return boolean
+     * @throws \JonnyW\PhantomJs\Exception\ProcedureValidationException
      */
-    public function validate(ClientInterface $client, ProcedureInterface $procedure, InputInterface $message);
+    public function validate($procedure);
 }

+ 8 - 0
src/JonnyW/PhantomJs/Resources/config/config.yml

@@ -10,6 +10,12 @@ parameters:
     phantomjs.validator_dir: "%phantomjs.resource_dir%/validators"
     phantomjs.validator_engine: "esprima-2.0.0.js"
 
+##############
+### ENGINE ###
+##############
+
+    phantomjs.engine.class: JonnyW\PhantomJs\Engine
+
 ##################
 ### PROCEDURES ###
 ##################
@@ -19,6 +25,7 @@ parameters:
     phantomjs.procedure.procedure_factory.class: JonnyW\PhantomJs\Procedure\ProcedureFactory
     phantomjs.procedure.procedure_loader_factory.class: JonnyW\PhantomJs\Procedure\ProcedureLoaderFactory
     phantomjs.procedure.procedure_validator.class: JonnyW\PhantomJs\Procedure\ProcedureValidator
+    phantomjs.procedure.procedure_compiler.class: JonnyW\PhantomJs\Procedure\ProcedureCompiler
 
 ############
 ### HTTP ###
@@ -44,6 +51,7 @@ parameters:
 
     phantomjs.template.template_renderer.class: JonnyW\PhantomJs\Template\TemplateRenderer
     phantomjs.twig.environment.class: Twig_Environment
+    phantomjs.twig.lexer.class: Twig_Lexer
     phantomjs.twig.string_loader.class: Twig_Loader_String
 
 ##################

+ 44 - 0
src/JonnyW/PhantomJs/Resources/config/services.yml

@@ -1,5 +1,15 @@
 services:
 
+##############
+### ENGINE ###
+##############
+
+    engine:
+        alias: phantomjs.engine
+
+    phantomjs.engine:
+        class: %phantomjs.engine.class%
+
 ##################
 ### PROCEDURES ###
 ##################
@@ -10,6 +20,9 @@ services:
     procedure_validator:
         alias: phantomjs.procedure.procedure_validator
 
+    procedure_compiler:
+        alias: phantomjs.procedure.procedure_compiler
+
     procedure_loader_factory:
         alias: phantomjs.procedure.procedure_loader_factory
 
@@ -35,6 +48,7 @@ services:
     phantomjs.procedure.procedure_factory:
         class: %phantomjs.procedure.procedure_factory.class%
         arguments:
+            - @engine
             - @phantomjs.parser.json_parser
             - @phantomjs.cache.file_cache
             - @phantomjs.template.template_renderer
@@ -46,6 +60,15 @@ services:
             - @phantomjs.procedure.procedure_factory
         public: false
 
+    phantomjs.procedure.procedure_compiler:
+        class: %phantomjs.procedure.procedure_compiler.class%
+        arguments:
+            - @procedure_loader
+            - @procedure_validator
+            - @phantomjs.cache.file_cache
+            - @phantomjs.procedure.template_renderer
+        public: false
+
     phantomjs.procedure.procedure_validator:
         class: %phantomjs.procedure.procedure_validator.class%
         arguments:
@@ -112,6 +135,27 @@ services:
         class: %phantomjs.twig.string_loader.class%
         public: false
 
+    phantomjs.procedure.template_renderer:
+        class: %phantomjs.template.template_renderer.class%
+        arguments:
+            - @phantomjs.procedure.twig.environment
+        public: false
+
+    phantomjs.procedure.twig.environment:
+        class: %phantomjs.twig.environment.class%
+        arguments:
+            - @phantomjs.twig.string_loader
+        public: false
+        calls:
+            - [ "setLexer", [ @phantomjs.procedure.twig.lexer ] ]
+
+    phantomjs.procedure.twig.lexer:
+        class: %phantomjs.twig.lexer.class%
+        arguments:
+            - @phantomjs.procedure.twig.environment
+            - { tag_variable: [ "[[", "]]" ], tag_comment: [ "[#", "#]" ], tag_block:  [ "[%", "%]" ], interpolation:  [ "#[", "]" ] }
+        public: false
+
 ##################
 ### RESOURCES ####
 ##################

+ 0 - 0
src/JonnyW/PhantomJs/Resources/procedures/global_variables.partial


+ 0 - 165
src/JonnyW/PhantomJs/Resources/procedures/http_capture.proc

@@ -1,165 +0,0 @@
-{% autoescape false %}
-
-/**
- * Set up page and script parameters.
- */
-var page       = require('webpage').create(),
-    system     = require('system'),
-    response   = {},
-    headers    = {{ input.getHeaders('json') }},
-    delay      = {{ input.getDelay() }},
-    top        = {{ input.getRectTop() }},
-    left       = {{ input.getRectLeft() }},
-    width      = {{ input.getRectWidth() }},
-    height     = {{ input.getRectHeight() }},
-    vpWidth    = {{ input.getViewportWidth() }},
-    vpHeight   = {{ input.getViewportHeight() }},
-    debug      = [],
-    logs       = [],
-    procedure  = {};
-
-
-/**
- * Define width & height of screenshot.
- */
-if(width && height) {
-    
-    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Set capture clipping size ~ top: ' + top + ' left: ' + left + ' ' + width + 'x' + height);
-    
-    page.clipRect = {
-        top: top,
-        left: left,
-        width: width,
-        height: height
-    };
-}
-
-/**
- * Set viewport size.
- */
-if(vpWidth && vpHeight) {
-    
-    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Set viewport size ~ width: ' + vpWidth + ' height: ' + vpHeight);
-    
-    page.viewportSize = {
-        width: vpWidth,
-        height: vpHeight
-    };
-}
-
-/**
- * Define custom headers.
- */
-page.customHeaders = headers ? headers : {};
-
-/**
- * Set timeout.
- */
-page.settings.resourceTimeout = {{ input.getTimeout() }};
-
-/**
- * Set error in response on timeout.
- */
-page.onResourceTimeout = function (e) {
-    response        = e;
-    response.status = e.errorCode;
-};
-
-/**
- * Set response from resource.
- */
-page.onResourceReceived = function (r) {
-    if(!response.status) response = r;
-};
-
-/**
- * Add page errors to logs.
- */
-page.onError = function (msg, trace) {
- 
-    var error = {
-        message: msg,
-        trace: []
-    };
-    
-    trace.forEach(function(t) {
-        error.trace.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
-    });
-    
-    logs.push(error);
-};
-
-/**
- * Global error handling.
- */
-phantom.onError = function(msg, trace) {
-  
-    var stack = [];
-    
-    trace.forEach(function(t) {
-        stack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
-    });
-
-    response.status  = 500;
-    response.content = msg;
-    response.console = stack;
-
-    system.stdout.write(JSON.stringify(response, undefined, 4));
-    phantom.exit(1);
-};
-
-/**
- * Open page.
- *
- * @param string url
- * @param string method
- * @param string parameters
- * @param callable callback
- */
-page.open ('{{ input.getUrl() }}', '{{ input.getMethod() }}', '{{ input.getBody() }}', function (status) {
-
-    if(!delay) {
-        return procedure.execute(status);
-    }
-    
-    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Delaying page render for ' + delay + ' second(s)');
-    
-    window.setTimeout(function () { 
-    
-        debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Rendering page after delaying for ' + delay + ' second(s)');
-        procedure.execute(status); 
-    
-    }, (delay * 1000));
-});
-
-/**
- * Command to execute on page load.
- */
-procedure.execute = function (status) {
-
-    if (status === 'success') {
-        
-        try {
-        
-            page.render('{{ input.getCaptureFile() }}');
-            
-            response.content = page.evaluate(function () {
-                return document.getElementsByTagName('html')[0].innerHTML
-            });
-        
-        } catch(e) {
-    
-            response.status  = 500;
-            response.content = e.message;
-        }
-    }
-    
-    response.console = logs;
-    
-    system.stderr.write(debug.join('\\n') + '\\n');
-    system.stdout.write(JSON.stringify(response, undefined, 4));
-    
-    phantom.exit();
-};
-
-{% endautoescape %}

+ 35 - 94
src/JonnyW/PhantomJs/Resources/procedures/http_default.proc

@@ -1,143 +1,84 @@
+
+[% autoescape false %]
 {% autoescape false %}
 
 /**
- * Set up page and script parameters.
+ * Set up page and script parameters
  */
 var page       = require('webpage').create(),
     system     = require('system'),
-    headers    = {{ input.getHeaders('json') }},
-    delay      = {{ input.getDelay() }},
-    vpWidth    = {{ input.getViewportWidth() }},
-    vpHeight   = {{ input.getViewportHeight() }},
     response   = {},
     debug      = [],
     logs       = [],
     procedure  = {};
 
 /**
- * Set viewport size.
+ * Global variables
+ */
+[[ engine.load('global_variables') ]]
+
+/**
+ * Define width & height of screenshot
  */
-if(vpWidth && vpHeight) {
-    
-    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Set viewport size ~ width: ' + vpWidth + ' height: ' + vpHeight);
-    
-    page.viewportSize = {
-        width: vpWidth,
-        height: vpHeight
-    };
-}
+[[ engine.load('page_clip_rect') ]]
+
+/**
+ * Define viewport size.
+ */
+[[ engine.load('page_viewport_size') ]]
+
 
 /**
  * Define custom headers.
  */
-page.customHeaders = headers ? headers : {};
+[[ engine.load('page_custom_headers') ]]
 
 /**
- * Set timeout.
+ * Page settings
  */
-page.settings.resourceTimeout = {{ input.getTimeout() }};
+[[ engine.load('page_settings') ]]
 
 /**
- * Set error in response on timeout.
+ * On resource timeout
  */
-page.onResourceTimeout = function (e) {
-    response        = e;
-    response.status = e.errorCode;
+page.onResourceTimeout = function (error) {
+    [[ engine.load('page_on_resource_timeout') ]]
 };
 
 /**
- * Set response from resource.
+ * On resource received
  */
-page.onResourceReceived = function (r) {
-    if(!response.status) response = r;
+page.onResourceReceived = function (resource) {
+    [[ engine.load('page_on_resource_received') ]]
 };
 
 /**
- * Add page errors to logs.
+ * Handle page errors
  */
 page.onError = function (msg, trace) {
- 
-    var error = {
-        message: msg,
-        trace: []
-    };
-    
-    trace.forEach(function(t) {
-        error.trace.push((t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
-    });
-    
-    logs.push(error);
+    [[ engine.load('page_on_error') ]]
 };
 
 /**
- * Global error handling.
+ * Handle global errors
  */
 phantom.onError = function(msg, trace) {
-  
-    var stack = [];
-    
-    trace.forEach(function(t) {
-        stack.push((t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
-    });
-
-    response.status  = 500;
-    response.content = msg;
-    response.console = stack;
-
-    system.stdout.write(JSON.stringify(response, undefined, 4));
-    phantom.exit(1);
+    [[ engine.load('phantom_on_error') ]]
 };
 
 /**
- * Open page.
- *
- * @param string url
- * @param string method
- * @param string parameters
- * @param callable callback
+ * Open page
  */
 page.open ('{{ input.getUrl() }}', '{{ input.getMethod() }}', '{{ input.getBody() }}', function (status) {
-    
-    if(!delay) {
-        return procedure.execute(status);
-    }
-    
-    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Delaying page render for ' + delay + ' second(s)');
-    
-    window.setTimeout(function () { 
-    
-        debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Rendering page after delaying for ' + delay + ' second(s)');
-        procedure.execute(status); 
-        
-    }, (delay * 1000));
+    [[ engine.load('page_open') ]]
 });
 
 /**
- * Command to execute on page load.
+ * Execute procedure
  */
 procedure.execute = function (status) {
-
-    if (status === 'success') {
-        
-        try {
-        
-            response.content = page.evaluate(function () {
-                return document.getElementsByTagName('html')[0].innerHTML
-            });
-        
-        } catch(e) {
-    
-            response.status  = 500;
-            response.content = e.message;
-        }
-    }
-    
-    response.console = logs;
-    
-    system.stderr.write(debug.join('\\n') + '\\n');
-    system.stdout.write(JSON.stringify(response, undefined, 4));
-    
-    phantom.exit();
+    [[ engine.load( 'procedure_' ~ procedure_type ) ]]
 };
 
-{% endautoescape %}
+{% endautoescape %}
+[% endautoescape %]

+ 21 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_clip_rect.partial

@@ -0,0 +1,21 @@
+
+{% if input.getType() == 'capture' %}
+
+var top    = {{ input.getRectTop() }},
+    left   = {{ input.getRectLeft() }},
+    width  = {{ input.getRectWidth() }},
+    height = {{ input.getRectHeight() }};
+
+if(width && height) {
+    
+    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Set capture clipping size ~ top: ' + top + ' left: ' + left + ' ' + width + 'x' + height);
+    
+    page.clipRect = {
+        top: top,
+        left: left,
+        width: width,
+        height: height
+    };
+}
+
+{% endif %}

+ 5 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_custom_headers.partial

@@ -0,0 +1,5 @@
+
+var headers = {{ input.getHeaders('json') }};
+
+page.customHeaders = headers ? headers : {};
+

+ 12 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_on_error.partial

@@ -0,0 +1,12 @@
+
+var error = {
+    message: msg,
+    trace: []
+};
+
+trace.forEach(function(t) {
+    error.trace.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
+});
+
+logs.push(error);
+

+ 5 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_on_resource_received.partial

@@ -0,0 +1,5 @@
+
+if(!response.status) {
+    response = resource;
+}
+

+ 4 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_on_resource_timeout.partial

@@ -0,0 +1,4 @@
+
+response        = error;
+response.status = error.errorCode;
+

+ 16 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_open.partial

@@ -0,0 +1,16 @@
+
+var delay = {{ input.getDelay() }};
+
+if(!delay) {
+    return procedure.execute(status);
+}
+
+debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Delaying page render for ' + delay + ' second(s)');
+
+window.setTimeout(function () { 
+
+    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Rendering page after delaying for ' + delay + ' second(s)');
+    procedure.execute(status); 
+
+}, (delay * 1000));
+

+ 3 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_settings.partial

@@ -0,0 +1,3 @@
+
+page.settings.resourceTimeout = {{ input.getTimeout() }};
+

+ 14 - 0
src/JonnyW/PhantomJs/Resources/procedures/page_viewport_size.partial

@@ -0,0 +1,14 @@
+
+var width  = {{ input.getViewportWidth() }},
+    height = {{ input.getViewportHeight() }};
+
+if(width && height) {
+    
+    debug.push(new Date().toISOString().slice(0, -5) + ' [INFO] PhantomJS - Set viewport size ~ width: ' + width + ' height: ' + height);
+    
+    page.viewportSize = {
+        width: width,
+        height: height
+    };
+}
+

+ 14 - 0
src/JonnyW/PhantomJs/Resources/procedures/phantom_on_error.partial

@@ -0,0 +1,14 @@
+    
+var stack = [];
+
+trace.forEach(function(t) {
+    stack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
+});
+
+response.status  = 500;
+response.content = msg;
+response.console = stack;
+
+system.stdout.write(JSON.stringify(response, undefined, 4));
+phantom.exit(1);
+

+ 25 - 0
src/JonnyW/PhantomJs/Resources/procedures/procedure_capture.partial

@@ -0,0 +1,25 @@
+
+if (status === 'success') {
+    
+    try {
+    
+        page.render('{{ input.getCaptureFile() }}');
+        
+        response.content = page.evaluate(function () {
+            return document.getElementsByTagName('html')[0].innerHTML
+        });
+    
+    } catch(e) {
+
+        response.status  = 500;
+        response.content = e.message;
+    }
+}
+
+response.console = logs;
+
+system.stderr.write(debug.join('\\n') + '\\n');
+system.stdout.write(JSON.stringify(response, undefined, 4));
+
+phantom.exit();
+

+ 23 - 0
src/JonnyW/PhantomJs/Resources/procedures/procedure_default.partial

@@ -0,0 +1,23 @@
+
+if (status === 'success') {
+    
+    try {
+    
+        response.content = page.evaluate(function () {
+            return document.getElementsByTagName('html')[0].innerHTML
+        });
+    
+    } catch(e) {
+
+        response.status  = 500;
+        response.content = e.message;
+    }
+}
+
+response.console = logs;
+
+system.stderr.write(debug.join('\\n') + '\\n');
+system.stdout.write(JSON.stringify(response, undefined, 4));
+
+phantom.exit();
+

+ 1 - 1
src/JonnyW/PhantomJs/Resources/procedures/validator.proc

@@ -24,6 +24,6 @@ system.stdout.write(
   JSON.stringify(syntax, undefined, 4)
 );
 
-phantom.exit(1);
+phantom.exit();
 
 {% endautoescape %}

+ 14 - 6
src/JonnyW/PhantomJs/Tests/Integration/ClientTest.php

@@ -10,6 +10,7 @@ namespace JonnyW\PhantomJs\Tests\Integration;
 
 use JonnyW\PhantomJs\Test\TestCase;
 use JonnyW\PhantomJs\Client;
+use JonnyW\PhantomJs\DependencyInjection\ServiceContainer;
 
 /**
  * PHP PhantomJs
@@ -60,13 +61,12 @@ EOF;
         $procedureLoader        = $procedureLoaderFactory->createProcedureLoader($this->directory);
 
         $client = $this->getClient();
+        $client->setProcedure('test');
         $client->getProcedureLoader()->addLoader($procedureLoader);
 
         $request  = $client->getMessageFactory()->createRequest();
         $response = $client->getMessageFactory()->createResponse();
 
-        $request->setType('test');
-
         $client->send($request, $response);
 
         $this->assertSame($content, $response->getContent());
@@ -95,13 +95,12 @@ EOF;
         $procedureLoader        = $procedureLoaderFactory->createProcedureLoader($this->directory);
 
         $client = $this->getClient();
+        $client->setProcedure('test');
         $client->getProcedureLoader()->addLoader($procedureLoader);
 
         $request  = $client->getMessageFactory()->createRequest();
         $response = $client->getMessageFactory()->createResponse();
 
-        $request->setType('test');
-
         $client->send($request, $response);
     }
 
@@ -584,7 +583,7 @@ EOF;
     public function testDebugLogsDebugInfoToClientLog()
     {
         $client = $this->getClient();
-        $client->debug(true);
+        $client->getEngine()->debug(true);
 
         $request  = $client->getMessageFactory()->createRequest();
         $response = $client->getMessageFactory()->createResponse();
@@ -608,7 +607,16 @@ EOF;
      */
     protected function getClient()
     {
-        return Client::getInstance();
+        $serviceContainer = ServiceContainer::getInstance();
+
+        $client = new Client(
+            $serviceContainer->get('engine'),
+            $serviceContainer->get('procedure_loader'),
+            $serviceContainer->get('procedure_compiler'),
+            $serviceContainer->get('message_factory')
+        );
+
+        return $client;
     }
 
 /** +++++++++++++++++++++++++++++++++++ **/

+ 265 - 0
src/JonnyW/PhantomJs/Tests/Integration/Procedure/ProcedureCompilerTest.php

@@ -0,0 +1,265 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs\Tests\Integration\Procedure;
+
+use JonnyW\PhantomJs\Http\Request;
+use JonnyW\PhantomJs\Procedure\ProcedureCompiler;
+use JonnyW\PhantomJs\DependencyInjection\ServiceContainer;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class ProcedureCompilerTest extends \PHPUnit_Framework_TestCase
+{
+
+/** +++++++++++++++++++++++++++++++++++ **/
+/** ++++++++++++++ TESTS ++++++++++++++ **/
+/** +++++++++++++++++++++++++++++++++++ **/
+
+    /**
+     * Test can compile procedure
+     *
+     * @access public
+     * @return void
+     */
+    public function testCanCompileProcedure()
+    {
+        $procedure = $this->getProcedure('http_default');
+
+        $uncompiled = $procedure->getTemplate();
+
+        $request = $this->getRequest();
+        $request->setUrl('http://test.com');
+
+        $compiler = $this->getProcedureCompiler();
+        $compiler->compile($procedure, $request);
+
+        $this->assertNotSame($uncompiled, $procedure->getTemplate());
+    }
+
+    /**
+     * Test procedure is loaded from cache
+     * if cache is enabled.
+     *
+     * @access public
+     * @return void
+     */
+    public function testProcedureIsLoadedFromCacheIfCacheIsEnabled()
+    {
+        $procedure1 = $this->getProcedure('http_default');
+        $procedure2 = $this->getProcedure('http_default');
+
+        $request = $this->getRequest();
+        $request->setUrl('http://test.com');
+
+        $renderer = $this->getMock('\JonnyW\PhantomJs\Template\TemplateRendererInterface');
+        $renderer->expects($this->exactly(1))
+            ->method('render')
+            ->will($this->returnValue('var test=1; phantom.exit(1);'));
+
+        $serviceContainer = ServiceContainer::getInstance();
+
+        $compiler = new ProcedureCompiler(
+            $serviceContainer->get('phantomjs.procedure.chain_loader'),
+            $serviceContainer->get('phantomjs.procedure.procedure_validator'),
+            $serviceContainer->get('phantomjs.cache.file_cache'),
+            $renderer
+        );
+
+        $compiler->enableCache();
+        $compiler->compile($procedure1, $request);
+        $compiler->compile($procedure2, $request);
+    }
+
+    /**
+     * Test procedure is not loaded from
+     * cache if cache is disabled.
+     *
+     * @access public
+     * @return void
+     */
+    public function testProcedureIsNotLoadedFromCacheIfCacheIsDisabled()
+    {
+        $procedure1 = $this->getProcedure('http_default');
+        $procedure2 = $this->getProcedure('http_default');
+
+        $request = $this->getRequest();
+        $request->setUrl('http://test.com');
+
+        $renderer = $this->getMock('\JonnyW\PhantomJs\Template\TemplateRendererInterface');
+        $renderer->expects($this->exactly(2))
+            ->method('render')
+            ->will($this->returnValue('var test=1; phantom.exit(1);'));
+
+        $serviceContainer = ServiceContainer::getInstance();
+
+        $compiler = new ProcedureCompiler(
+            $serviceContainer->get('phantomjs.procedure.chain_loader'),
+            $serviceContainer->get('phantomjs.procedure.procedure_validator'),
+            $serviceContainer->get('phantomjs.cache.file_cache'),
+            $renderer
+        );
+
+        $compiler->disableCache();
+        $compiler->compile($procedure1, $request);
+        $compiler->compile($procedure2, $request);
+    }
+
+    /**
+     * Test procedure cache can be cleared.
+     *
+     * @access public
+     * @return void
+     */
+    public function testProcedureCacheCanBeCleared()
+    {
+        $procedure1 = $this->getProcedure('http_default');
+        $procedure2 = $this->getProcedure('http_default');
+
+        $request = $this->getRequest();
+        $request->setUrl('http://test.com');
+
+        $renderer = $this->getMock('\JonnyW\PhantomJs\Template\TemplateRendererInterface');
+        $renderer->expects($this->exactly(2))
+            ->method('render')
+            ->will($this->returnValue('var test=1; phantom.exit(1);'));
+
+        $serviceContainer = ServiceContainer::getInstance();
+
+        $compiler = new ProcedureCompiler(
+            $serviceContainer->get('phantomjs.procedure.chain_loader'),
+            $serviceContainer->get('phantomjs.procedure.procedure_validator'),
+            $serviceContainer->get('phantomjs.cache.file_cache'),
+            $renderer
+        );
+
+        $compiler->compile($procedure1, $request);
+        $compiler->clearCache();
+        $compiler->compile($procedure2, $request);
+    }
+
+    /**
+     * Test syntax exception is thrown if compiled
+     * template is not valid.
+     *
+     * @access public
+     * @return void
+     */
+    public function testSyntaxExceptionIsThrownIfCompiledTemplateIsNotValid()
+    {
+        $this->setExpectedException('\JonnyW\PhantomJs\Exception\SyntaxException');
+
+        $template = <<<EOF
+    console.log(;
+EOF;
+        $procedure = $this->getProcedure('http_default');
+        $procedure->setTemplate($template);
+
+        $request = $this->getRequest();
+        $request->setUrl('http://test.com');
+
+        $compiler = $this->getProcedureCompiler();
+        $compiler->compile($procedure, $request);
+
+    }
+
+/** +++++++++++++++++++++++++++++++++++ **/
+/** ++++++++++ TEST ENTITIES ++++++++++ **/
+/** +++++++++++++++++++++++++++++++++++ **/
+
+    /**
+     * Get procedure compiler.
+     *
+     * @access protected
+     * @return \JonnyW\PhantomJs\Procedure\ProcedureCompiler
+     */
+    protected function getProcedureCompiler()
+    {
+        $serviceContainer = ServiceContainer::getInstance();
+
+        $compiler = new ProcedureCompiler(
+            $serviceContainer->get('phantomjs.procedure.chain_loader'),
+            $serviceContainer->get('phantomjs.procedure.procedure_validator'),
+            $serviceContainer->get('phantomjs.cache.file_cache'),
+            $serviceContainer->get('phantomjs.procedure.template_renderer')
+        );
+
+        return $compiler;
+    }
+
+    /**
+     * getProcedure function.
+     *
+     * @access protected
+     * @param  string                                         $id
+     * @return \JonnyW\PhantomJs\Procedure\ProcedureInterface
+     */
+    protected function getProcedure($id)
+    {
+        return ServiceContainer::getInstance()->get('procedure_loader')->load($id);
+    }
+
+    /**
+     * Get request
+     *
+     * @access protected
+     * @return \JonnyW\PhantomJs\Http\Request
+     */
+    protected function getRequest()
+    {
+        $request = new Request();
+
+        return $request;
+    }
+
+/** +++++++++++++++++++++++++++++++ **/
+/** ++++++++++ UTILITIES ++++++++++ **/
+/** +++++++++++++++++++++++++++++++ **/
+
+    /**
+     * Set up tasks.
+     *
+     * @access protected
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->cleanup();
+    }
+
+    /**
+     * Tear down tasks.
+     *
+     * @access protected
+     * @return void
+     */
+    protected function tearDown()
+    {
+        parent::setUp();
+
+        $this->cleanup();
+    }
+
+    /**
+     * Clean up cache files.
+     *
+     * @access protected
+     * @return void
+     */
+    protected function cleanup()
+    {
+        $cache = sprintf('%s/phantomjs_*', sys_get_temp_dir());
+
+        array_map('unlink', glob($cache));
+    }
+}

+ 5 - 130
src/JonnyW/PhantomJs/Tests/Integration/Procedure/ProcedureValidatorTest.php

@@ -8,14 +8,8 @@
  */
 namespace JonnyW\PhantomJs\Tests\Integration\Procedure;
 
-use Twig_Environment;
-use Twig_Loader_String;
 use Symfony\Component\Config\FileLocator;
 use JonnyW\PhantomJs\Client;
-use JonnyW\PhantomJs\Cache\FileCache;
-use JonnyW\PhantomJs\Parser\JsonParser;
-use JonnyW\PhantomJs\Template\TemplateRenderer;
-use JonnyW\PhantomJs\Procedure\Input;
 use JonnyW\PhantomJs\Procedure\Procedure;
 use JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface;
 use JonnyW\PhantomJs\Procedure\ProcedureValidator;
@@ -49,14 +43,8 @@ class ProcedureValidatorTest extends \PHPUnit_Framework_TestCase
         $procedureLoader = $this->getProcedureLoader();
         $esprima         = $this->getEsprima();
 
-        $client    = $this->getClient();
-        $procedure = $this->getProcedure();
-        $message   = $this->getInput();
-
-        $procedure->setTemplate('return false; var');
-
         $validator = $this->getValidator($procedureLoader, $esprima);
-        $validator->validate($client, $procedure, $message);
+        $validator->validate('return false; var');
     }
 
     /**
@@ -70,16 +58,10 @@ class ProcedureValidatorTest extends \PHPUnit_Framework_TestCase
         $procedureLoader = $this->getProcedureLoader();
         $esprima         = $this->getEsprima();
 
-        $client    = $this->getClient();
-        $procedure = $this->getProcedure();
-        $message   = $this->getInput();
-
-        $procedure->setTemplate('return false; var');
-
         try {
 
             $validator = $this->getValidator($procedureLoader, $esprima);
-            $validator->validate($client, $procedure, $message);
+            $validator->validate('return false; var');
 
         } catch (\JonnyW\PhantomJs\Exception\SyntaxException $e) {
             $this->assertNotEmpty($e->getErrors());
@@ -101,14 +83,8 @@ class ProcedureValidatorTest extends \PHPUnit_Framework_TestCase
         $procedureLoader = $this->getProcedureLoader();
         $esprima         = $this->getEsprima();
 
-        $client    = $this->getClient();
-        $procedure = $this->getProcedure();
-        $message   = $this->getInput();
-
-        $procedure->setTemplate('var test = function () { console.log("ok"); }');
-
         $validator = $this->getValidator($procedureLoader, $esprima);
-        $validator->validate($client, $procedure, $message);
+        $validator->validate('var test = function () { console.log("ok"); }');
     }
 
     /**
@@ -122,15 +98,9 @@ class ProcedureValidatorTest extends \PHPUnit_Framework_TestCase
         $procedureLoader = $this->getProcedureLoader();
         $esprima         = $this->getEsprima();
 
-        $client    = $this->getClient();
-        $procedure = $this->getProcedure();
-        $message   = $this->getInput();
-
-        $procedure->setTemplate('var test = function () { console.log("ok"); }; phantom.exit(1);');
-
         $validator = $this->getValidator($procedureLoader, $esprima);
 
-        $this->assertTrue($validator->validate($client, $procedure, $message));
+        $this->assertTrue($validator->validate('var test = function () { console.log("ok"); }; phantom.exit(1);'));
     }
 
     /**
@@ -145,15 +115,9 @@ class ProcedureValidatorTest extends \PHPUnit_Framework_TestCase
         $procedureLoader = $this->getProcedureLoader();
         $esprima         = $this->getEsprima();
 
-        $client    = $this->getClient();
-        $procedure = $this->getProcedure();
-        $message   = $this->getInput();
-
-        $procedure->setTemplate('/** * Test comment **/ var test = function () { console.log("ok"); }; phantom.exit(1);');
-
         $validator = $this->getValidator($procedureLoader, $esprima);
 
-        $this->assertTrue($validator->validate($client, $procedure, $message));
+        $this->assertTrue($validator->validate('/** * Test comment **/ var test = function () { console.log("ok"); }; phantom.exit(1);'));
     }
 
 /** +++++++++++++++++++++++++++++++++++ **/
@@ -175,20 +139,6 @@ class ProcedureValidatorTest extends \PHPUnit_Framework_TestCase
         return $validator;
     }
 
-    /**
-     * Get client.
-     *
-     * @access protected
-     * @return \JonnyW\PhantomJs\Client
-     */
-    protected function getClient()
-    {
-        $client = Client::getInstance();
-        $client->setPhantomJs(sprintf('%s/../../../../../../bin/phantomjs', __DIR__));
-
-        return $client;
-    }
-
     /**
      * Get procedure loader.
      *
@@ -200,81 +150,6 @@ class ProcedureValidatorTest extends \PHPUnit_Framework_TestCase
         return  Client::getInstance()->getProcedureLoader();
     }
 
-    /**
-     * Get procedure instance.
-     *
-     * @access protected
-     * @return \JonnyW\PhantomJs\Procedure\Procedure
-     */
-    protected function getProcedure()
-    {
-        $procedure = new Procedure(
-            $this->getParser(),
-            $this->getCache(),
-            $this->getRenderer()
-        );
-
-        return $procedure;
-    }
-
-    /**
-     * Get parser.
-     *
-     * @access protected
-     * @return \JonnyW\PhantomJs\Parser\JsonParser
-     */
-    protected function getParser()
-    {
-        $parser = new JsonParser();
-
-        return $parser;
-    }
-
-    /**
-     * Get cache.
-     *
-     * @access protected
-     * @param  string                            $cacheDir  (default: '')
-     * @param  string                            $extension (default: 'proc')
-     * @return \JonnyW\PhantomJs\Cache\FileCache
-     */
-    protected function getCache($cacheDir = '', $extension = 'proc')
-    {
-        $cache = new FileCache(($cacheDir ? $cacheDir : sys_get_temp_dir()), 'proc');
-
-        return $cache;
-    }
-
-    /**
-     * Get template renderer.
-     *
-     * @access protected
-     * @return \JonnyW\PhantomJs\Template\TemplateRenderer
-     */
-    protected function getRenderer()
-    {
-        $twig = new Twig_Environment(
-            new Twig_Loader_String()
-        );
-
-        $renderer = new TemplateRenderer($twig);
-
-        return $renderer;
-    }
-
-    /**
-     * Get input
-     *
-     * @access protected
-     * @return \JonnyW\PhantomJs\Procedure\Input
-     */
-    protected function getInput()
-    {
-        $input = new Input();
-
-        return $input;
-    }
-
     /**
      * Get esprima.
      *

+ 21 - 0
src/JonnyW/PhantomJs/Tests/Unit/Cache/FileCacheTest.php

@@ -198,6 +198,27 @@ class FileCacheTest extends \PHPUnit_Framework_TestCase
         $this->assertFileNotExists($file);
     }
 
+    /**
+     * Test data can be deleted from
+     * cache using wildcard.
+     *.
+     *
+     * @access public
+     * @return void
+     */
+    public function testDataCanBeDeletedFromCacheUsingWildcard()
+    {
+        $fileCache = $this->getFileCache($this->directory, 'txt');
+
+        $file1 = $fileCache->save('test_file_1', 'Test1');
+        $file2 = $fileCache->save('test_file_2', 'Test2');
+
+        $fileCache->delete('test_file_*');
+
+        $this->assertFileNotExists($file1);
+        $this->assertFileNotExists($file2);
+    }
+
 /** +++++++++++++++++++++++++++++++++++ **/
 /** ++++++++++ TEST ENTITIES ++++++++++ **/
 /** +++++++++++++++++++++++++++++++++++ **/

+ 43 - 244
src/JonnyW/PhantomJs/Tests/Unit/ClientTest.php

@@ -9,9 +9,10 @@
 namespace JonnyW\PhantomJs\Tests\Unit;
 
 use JonnyW\PhantomJs\Client;
+use JonnyW\PhantomJs\Engine;
 use JonnyW\PhantomJs\Http\MessageFactoryInterface;
 use JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface;
-use JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface;
+use JonnyW\PhantomJs\Procedure\ProcedureCompilerInterface;
 
 /**
  * PHP PhantomJs
@@ -38,270 +39,54 @@ class ClientTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * Test can get message factory
-     *
-     * @return void
-     */
-    public function testCanGetMessageFactory()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-
-        $this->assertInstanceOf('\JonnyW\PhantomJs\Http\MessageFactoryInterface', $client->getMessageFactory());
-    }
-
-    /**
-     * Test can get procedure loader.
-     *
-     * @return void
-     */
-    public function testCanGetProcedureLoader()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-
-        $this->assertInstanceOf('\JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface', $client->getProcedureLoader());
-    }
-
-    /**
-     * Test invalid executable exception is thrown
-     * if phantom JS path is invalid.
-     *
-     * @access public
-     * @return void
-     */
-    public function testInvalidExecutableExceptionIsThrownIfPhantomJSPathIsInvalid()
-    {
-        $this->setExpectedException('\JonnyW\PhantomJs\Exception\InvalidExecutableException');
-
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->setPhantomJs('/invalid/phantomjs/path');
-    }
-
-    /**
-     * Test default phantom JS path is returned
-     * if no custom path is set.
-     *
-     * @access public
-     * @return void
-     */
-    public function testDefaultPhantomJSPathIsReturnedIfNoCustomPathIsSet()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-
-        $this->assertSame('bin/phantomjs', $client->getPhantomJs());
-    }
-
-    /**
-     * Test can log data.
-     *
-     * @access public
-     * @return void
-     */
-    public function testCanLogData()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $log = 'Test log info';
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->log($log);
-
-        $this->assertSame($log, $client->getLog());
-    }
-
-    /**
-     * Test can clear log.
-     *
-     * @access public
-     * @return void
-     */
-    public function testCanClearLog()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $log = 'Test log info';
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->log($log);
-        $client->clearLog();
-
-        $this->assertEmpty($client->getLog());
-    }
-
-    /**
-     * Test can add run option.
-     *
-     * @access public
-     * @return void
-     */
-    public function testCanAddRunOption()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $options = array(
-            'option1',
-            'option2'
-        );
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->setOptions($options);
-        $client->addOption('option3');
-
-        array_push($options, 'option3');
-
-        $this->assertSame($options, $client->getOptions());
-    }
-
-    /**
-     * Test invalid executable exception is thrown when
-     * building command if path to phantom JS is valid.
-     *
-     * @access public
-     * @return void
-     */
-    public function testInvalidExecutableExceptionIsThrownWhenBuildingCommandIfPathToPhantomJSIsInvalid()
-    {
-        $this->setExpectedException('\JonnyW\PhantomJs\Exception\InvalidExecutableException');
-
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-
-        $phantomJs = new \ReflectionProperty(get_class($client), 'phantomJs');
-        $phantomJs->setAccessible(true);
-        $phantomJs->setValue($client, 'invalid/path');
-
-        $client->getCommand();
-    }
-
-    /**
-     * Test command contains phantom JS executable
-     *
-     * @access public
-     * @return void
-     */
-    public function testCommandContainsPhantomJSExecutable()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-
-        $this->assertContains($client->getPhantomJs(), $client->getCommand());
-    }
-
-    /**
-     * Test debug flag can be set.
+     * Test can get engine.
      *
-     * @access public
      * @return void
      */
-    public function testDebugFlagCanBeSet()
+    public function testCanGetEngne()
     {
+        $engine             = $this->getEngine();
         $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
+        $procedureCompiler  = $this->getProcedureCompiler();
         $messageFactory     = $this->getMessageFactory();
 
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->debug(true);
+        $client = $this->getClient($engine, $procedureLoader, $procedureCompiler, $messageFactory);
 
-        $this->assertContains('--debug=true', $client->getCommand());
+        $this->assertInstanceOf('\JonnyW\PhantomJs\Engine', $client->getEngine());
     }
 
     /**
-     * Test debug flag is not set if
-     * debugging is not enabled.
-     *
-     * @access public
-     * @return void
-     */
-    public function testDebugFlagIsNotSetIfDebuggingIsNotEnabled()
-    {
-        $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
-        $messageFactory     = $this->getMessageFactory();
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->debug(false);
-
-        $this->assertNotContains('--debug=true', $client->getCommand());
-    }
-
-    /**
-     * Test command contains run options.
+     * Test can get message factory
      *
-     * @access public
      * @return void
      */
-    public function testCommandContainsRunOptions()
+    public function testCanGetMessageFactory()
     {
+        $engine             = $this->getEngine();
         $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
+        $procedureCompiler  = $this->getProcedureCompiler();
         $messageFactory     = $this->getMessageFactory();
 
-        $option1 = '--local-storage-path=/some/path';
-        $option2 = '--local-storage-quota=5';
-        $option3 = '--local-to-remote-url-access=true';
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->addOption($option1);
-        $client->addOption($option2);
-        $client->addOption($option3);
-
-        $command = $client->getCommand();
+        $client = $this->getClient($engine, $procedureLoader, $procedureCompiler, $messageFactory);
 
-        $this->assertContains($option1, $command);
-        $this->assertContains($option2, $command);
-        $this->assertContains($option3, $command);
+        $this->assertInstanceOf('\JonnyW\PhantomJs\Http\MessageFactoryInterface', $client->getMessageFactory());
     }
 
     /**
-     * Test debug flag is set if runs options
-     * are also set.
+     * Test can get procedure loader.
      *
-     * @access public
      * @return void
      */
-    public function testDebugFlagIsSetIfRunOptionsAreAlsoSet()
+    public function testCanGetProcedureLoader()
     {
+        $engine             = $this->getEngine();
         $procedureLoader    = $this->getProcedureLoader();
-        $procedureValidator = $this->getProcedureValidator();
+        $procedureCompiler  = $this->getProcedureCompiler();
         $messageFactory     = $this->getMessageFactory();
 
-        $option = '--local-storage-path=/some/path';
-
-        $client = $this->getClient($procedureLoader, $procedureValidator, $messageFactory);
-        $client->addOption($option);
-        $client->debug(true);
+        $client = $this->getClient($engine, $procedureLoader, $procedureCompiler, $messageFactory);
 
-        $command = $client->getCommand();
-
-        $this->assertContains($option, $command);
-        $this->assertContains('--debug=true', $command);
+        $this->assertInstanceOf('\JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface', $client->getProcedureLoader());
     }
 
 /** +++++++++++++++++++++++++++++++++++ **/
@@ -311,14 +96,15 @@ class ClientTest extends \PHPUnit_Framework_TestCase
     /**
      * Get client instance
      *
-     * @param  \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface    $procedureLoader
-     * @param  \JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface $procedureValidator
-     * @param  \JonnyW\PhantomJs\Http\MessageFactoryInterface          $messageFactory
+     * @param  \JonnyW\PhantomJs\Engine                               $engine
+     * @param  \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface   $procedureLoader
+     * @param  \JonnyW\PhantomJs\Procedure\ProcedureCompilerInterface $procedureCompiler
+     * @param  \JonnyW\PhantomJs\Http\MessageFactoryInterface         $messageFactory
      * @return \JonnyW\PhantomJs\Client
      */
-    protected function getClient(ProcedureLoaderInterface $procedureLoader, ProcedureValidatorInterface $procedureValidator, MessageFactoryInterface $messageFactory)
+    protected function getClient(Engine $engine, ProcedureLoaderInterface $procedureLoader, ProcedureCompilerInterface $procedureCompiler, MessageFactoryInterface $messageFactory)
     {
-        $client = new Client($procedureLoader, $procedureValidator, $messageFactory);
+        $client = new Client($engine, $procedureLoader, $procedureCompiler, $messageFactory);
 
         return $client;
     }
@@ -327,6 +113,19 @@ class ClientTest extends \PHPUnit_Framework_TestCase
 /** ++++++++++ MOCKS / STUBS ++++++++++ **/
 /** +++++++++++++++++++++++++++++++++++ **/
 
+    /**
+     * Get engine
+     *
+     * @access protected
+     * @return \JonnyW\PhantomJs\Engine
+     */
+    protected function getEngine()
+    {
+        $engine = $this->getMock('\JonnyW\PhantomJs\Engine');
+
+        return $engine;
+    }
+
     /**
      * Get message factory
      *
@@ -357,12 +156,12 @@ class ClientTest extends \PHPUnit_Framework_TestCase
      * Get procedure validator.
      *
      * @access protected
-     * @return \JonnyW\PhantomJs\Procedure\ProcedureLoaderInterface
+     * @return \JonnyW\PhantomJs\Procedure\ProcedureCompilerInterface
      */
-    protected function getProcedureValidator()
+    protected function getProcedureCompiler()
     {
-        $procedureValidator = $this->getMock('\JonnyW\PhantomJs\Procedure\ProcedureValidatorInterface');
+        $procedureCompiler = $this->getMock('\JonnyW\PhantomJs\Procedure\ProcedureCompilerInterface');
 
-        return $procedureValidator;
+        return $procedureCompiler;
     }
 }

+ 231 - 0
src/JonnyW/PhantomJs/Tests/Unit/EngineTest.php

@@ -0,0 +1,231 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs\Tests\Unit;
+
+use JonnyW\PhantomJs\Engine;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class EngineTest extends \PHPUnit_Framework_TestCase
+{
+
+/** +++++++++++++++++++++++++++++++++++ **/
+/** ++++++++++++++ TESTS ++++++++++++++ **/
+/** +++++++++++++++++++++++++++++++++++ **/
+
+    /**
+     * Test invalid executable exception is thrown
+     * if phantom JS path is invalid.
+     *
+     * @access public
+     * @return void
+     */
+    public function testInvalidExecutableExceptionIsThrownIfPhantomJSPathIsInvalid()
+    {
+        $this->setExpectedException('\JonnyW\PhantomJs\Exception\InvalidExecutableException');
+
+        $engine = $this->getEngine();
+        $engine->setPath('/invalid/phantomjs/path');
+    }
+
+    /**
+     * Test default phantom JS path is returned
+     * if no custom path is set.
+     *
+     * @access public
+     * @return void
+     */
+    public function testDefaultPhantomJSPathIsReturnedIfNoCustomPathIsSet()
+    {
+        $engine = $this->getEngine();
+
+        $this->assertSame('bin/phantomjs', $engine->getPath());
+    }
+
+    /**
+     * Test can log data.
+     *
+     * @access public
+     * @return void
+     */
+    public function testCanLogData()
+    {
+        $log = 'Test log info';
+
+        $engine = $this->getEngine();
+        $engine->log($log);
+
+        $this->assertSame($log, $engine->getLog());
+    }
+
+    /**
+     * Test can clear log.
+     *
+     * @access public
+     * @return void
+     */
+    public function testCanClearLog()
+    {
+        $log = 'Test log info';
+
+        $engine = $this->getEngine();
+        $engine->log($log);
+        $engine->clearLog();
+
+        $this->assertEmpty($engine->getLog());
+    }
+
+    /**
+     * Test can add run option.
+     *
+     * @access public
+     * @return void
+     */
+    public function testCanAddRunOption()
+    {
+        $options = array(
+            'option1',
+            'option2'
+        );
+
+        $engine = $this->getEngine();
+        $engine->setOptions($options);
+        $engine->addOption('option3');
+
+        array_push($options, 'option3');
+
+        $this->assertSame($options, $engine->getOptions());
+    }
+
+    /**
+     * Test invalid executable exception is thrown when
+     * building command if path to phantom JS is valid.
+     *
+     * @access public
+     * @return void
+     */
+    public function testInvalidExecutableExceptionIsThrownWhenBuildingCommandIfPathToPhantomJSIsInvalid()
+    {
+        $this->setExpectedException('\JonnyW\PhantomJs\Exception\InvalidExecutableException');
+
+        $engine = $this->getEngine();
+
+        $phantomJs = new \ReflectionProperty(get_class($engine), 'path');
+        $phantomJs->setAccessible(true);
+        $phantomJs->setValue($engine, 'invalid/path');
+
+        $engine->getCommand();
+    }
+
+    /**
+     * Test command contains phantom JS executable
+     *
+     * @access public
+     * @return void
+     */
+    public function testCommandContainsPhantomJSExecutable()
+    {
+        $engine = $this->getEngine();
+
+        $this->assertContains($engine->getPath(), $engine->getCommand());
+    }
+
+    /**
+     * Test debug flag can be set.
+     *
+     * @access public
+     * @return void
+     */
+    public function testDebugFlagCanBeSet()
+    {
+        $engine = $this->getEngine();
+        $engine->debug(true);
+
+        $this->assertContains('--debug=true', $engine->getCommand());
+    }
+
+    /**
+     * Test debug flag is not set if
+     * debugging is not enabled.
+     *
+     * @access public
+     * @return void
+     */
+    public function testDebugFlagIsNotSetIfDebuggingIsNotEnabled()
+    {
+        $engine = $this->getEngine();
+        $engine->debug(false);
+
+        $this->assertNotContains('--debug=true', $engine->getCommand());
+    }
+
+    /**
+     * Test command contains run options.
+     *
+     * @access public
+     * @return void
+     */
+    public function testCommandContainsRunOptions()
+    {
+        $option1 = '--local-storage-path=/some/path';
+        $option2 = '--local-storage-quota=5';
+        $option3 = '--local-to-remote-url-access=true';
+
+        $engine = $this->getEngine();
+        $engine->addOption($option1);
+        $engine->addOption($option2);
+        $engine->addOption($option3);
+
+        $command = $engine->getCommand();
+
+        $this->assertContains($option1, $command);
+        $this->assertContains($option2, $command);
+        $this->assertContains($option3, $command);
+    }
+
+    /**
+     * Test debug flag is set if runs options
+     * are also set.
+     *
+     * @access public
+     * @return void
+     */
+    public function testDebugFlagIsSetIfRunOptionsAreAlsoSet()
+    {
+        $option = '--local-storage-path=/some/path';
+
+        $engine = $this->getEngine();
+        $engine->addOption($option);
+        $engine->debug(true);
+
+        $command = $engine->getCommand();
+
+        $this->assertContains($option, $command);
+        $this->assertContains('--debug=true', $command);
+    }
+
+/** +++++++++++++++++++++++++++++++++++ **/
+/** ++++++++++ TEST ENTITIES ++++++++++ **/
+/** +++++++++++++++++++++++++++++++++++ **/
+
+    /**
+     * Get client instance
+     *
+     * @return \JonnyW\PhantomJs\Engine
+     */
+    protected function getEngine()
+    {
+        $engine = new Engine();
+
+        return $engine;
+    }
+}

+ 41 - 0
src/JonnyW/PhantomJs/Tests/Unit/Procedure/ChainProcedureLoaderTest.php

@@ -82,6 +82,47 @@ class ChainProcedureLoaderTest extends \PHPUnit_Framework_TestCase
         $chainProcedureLoader->load('test');
     }
 
+    /**
+     * Test invalid argument exception is thrown if
+     * not valid loader can be found when loading template
+     *
+     * @access public
+     * @return void
+     */
+    public function testInvalidArgumentExceptionIsThrownIfNoValidLoaderCanBeFoundWhenLoadingTemplate()
+    {
+        $this->setExpectedException('\InvalidArgumentException');
+
+        $procedureLoaders = array();
+
+        $chainProcedureLoader = $this->getChainProcedureLoader($procedureLoaders);
+        $chainProcedureLoader->loadTemplate('test');
+    }
+
+    /**
+     * Test template is returned if
+     * procedure template is loaded.
+     *
+     * @access public
+     * @return void
+     */
+    public function testTemplateIsReturnedIfProcedureTemplateIsLoaded()
+    {
+        $template = 'Test template';
+
+        $procedureLoader = $this->getProcedureLoader();
+        $procedureLoader->method('loadTemplate')
+            ->will($this->returnValue($template));
+
+        $procedureLoaders = array(
+            $procedureLoader
+        );
+
+        $chainProcedureLoader = $this->getChainProcedureLoader($procedureLoaders);
+
+        $this->assertSame($template, $chainProcedureLoader->loadTemplate('test'));
+    }
+
 /** +++++++++++++++++++++++++++++++++++ **/
 /** ++++++++++ TEST ENTITIES ++++++++++ **/
 /** +++++++++++++++++++++++++++++++++++ **/

+ 19 - 3
src/JonnyW/PhantomJs/Tests/Unit/Procedure/ProcedureFactoryTest.php

@@ -10,6 +10,7 @@ namespace JonnyW\PhantomJs\Tests\Unit\Procedure;
 
 use Twig_Environment;
 use Twig_Loader_String;
+use JonnyW\PhantomJs\Engine;
 use JonnyW\PhantomJs\Cache\FileCache;
 use JonnyW\PhantomJs\Cache\CacheInterface;
 use JonnyW\PhantomJs\Parser\JsonParser;
@@ -39,11 +40,12 @@ class ProcedureFactoryTest extends \PHPUnit_Framework_TestCase
      */
     public function testFactoryCanCreateInstanceOfProcedure()
     {
+        $engine    = $this->getEngine();
         $parser    = $this->getParser();
         $cache     = $this->getCache();
         $renderer  = $this->getRenderer();
 
-        $procedureFactory = $this->getProcedureFactory($parser, $cache, $renderer);
+        $procedureFactory = $this->getProcedureFactory($engine, $parser, $cache, $renderer);
 
         $this->assertInstanceOf('\JonnyW\PhantomJs\Procedure\Procedure', $procedureFactory->createProcedure());
     }
@@ -56,18 +58,32 @@ class ProcedureFactoryTest extends \PHPUnit_Framework_TestCase
      * Get procedure factory instance.
      *
      * @access protected
+     * @param  \JonnyW\PhantomJs\Engine                             $engine
      * @param  \JonnyW\PhantomJs\Parser\ParserInterface             $parser
      * @param  \JonnyW\PhantomJs\Cache\CacheInterface               $cacheHandler
      * @param  \JonnyW\PhantomJs\Template\TemplateRendererInterface $renderer
      * @return \JonnyW\PhantomJs\Procedure\ProcedureFactory
      */
-    protected function getProcedureFactory(ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
+    protected function getProcedureFactory(Engine $engine, ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
     {
-        $procedureFactory = new ProcedureFactory($parser, $cacheHandler, $renderer);
+        $procedureFactory = new ProcedureFactory($engine, $parser, $cacheHandler, $renderer);
 
         return $procedureFactory;
     }
 
+    /**
+     * Get engine.
+     *
+     * @access protected
+     * @return \JonnyW\PhantomJs\Engine
+     */
+    protected function getEngine()
+    {
+        $engine = new Engine();
+
+        return $engine;
+    }
+
     /**
      * Get parser.
      *

+ 38 - 1
src/JonnyW/PhantomJs/Tests/Unit/Procedure/ProcedureLoaderTest.php

@@ -11,6 +11,7 @@ namespace JonnyW\PhantomJs\Tests\Unit\Procedure;
 use Twig_Environment;
 use Twig_Loader_String;
 use Symfony\Component\Config\FileLocatorInterface;
+use JonnyW\PhantomJs\Engine;
 use JonnyW\PhantomJs\Cache\FileCache;
 use JonnyW\PhantomJs\Cache\CacheInterface;
 use JonnyW\PhantomJs\Parser\JsonParser;
@@ -135,6 +136,28 @@ class ProcedureLoaderTest extends \PHPUnit_Framework_TestCase
         $this->assertSame($body, $procedureLoader->load('test')->getTemplate());
     }
 
+    /**
+     * Test procedure template can be loaded.
+     *
+     * @access public
+     * @return void
+     */
+    public function testProcedureTemplateCanBeLoaded()
+    {
+        $body = 'TEST_PROCEDURE';
+        $file = $this->writeProcedure($body);
+
+        $procedureFactory = $this->getProcedureFactory();
+        $fileLocator      = $this->getFileLocator();
+
+        $fileLocator->method('locate')
+            ->will($this->returnValue($file));
+
+        $procedureLoader = $this->getProcedureLoader($procedureFactory, $fileLocator);
+
+        $this->assertNotNull($procedureLoader->loadTemplate('test'));
+    }
+
 /** +++++++++++++++++++++++++++++++++++ **/
 /** ++++++++++ TEST ENTITIES ++++++++++ **/
 /** +++++++++++++++++++++++++++++++++++ **/
@@ -165,15 +188,29 @@ class ProcedureLoaderTest extends \PHPUnit_Framework_TestCase
      */
     protected function getProcedureFactory()
     {
+        $engine   = $this->getEngine();
         $parser   = $this->getParser();
         $cache    = $this->getCache();
         $renderer = $this->getRenderer();
 
-        $procedureFactory = new ProcedureFactory($parser, $cache, $renderer);
+        $procedureFactory = new ProcedureFactory($engine, $parser, $cache, $renderer);
 
         return $procedureFactory;
     }
 
+    /**
+     * Get engine.
+     *
+     * @access protected
+     * @return \JonnyW\PhantomJs\Engine
+     */
+    protected function getEngine()
+    {
+        $engine = new Engine();
+
+        return $engine;
+    }
+
     /**
      * Get parser.
      *

+ 20 - 17
src/JonnyW/PhantomJs/Tests/Unit/Procedure/ProcedureTest.php

@@ -10,6 +10,7 @@ namespace JonnyW\PhantomJs\Tests\Unit\Procedure;
 
 use Twig_Environment;
 use Twig_Loader_String;
+use JonnyW\PhantomJs\Engine;
 use JonnyW\PhantomJs\Cache\FileCache;
 use JonnyW\PhantomJs\Cache\CacheInterface;
 use JonnyW\PhantomJs\Parser\JsonParser;
@@ -43,11 +44,12 @@ class ProcedureTest extends \PHPUnit_Framework_TestCase
     {
         $template = 'PROCEDURE_TEMPLATE';
 
+        $engne     = $this->getEngine();
         $parser    = $this->getParser();
         $cache     = $this->getCache();
         $renderer  = $this->getRenderer();
 
-        $procedure = $this->getProcedure($parser, $cache, $renderer);
+        $procedure = $this->getProcedure($engne, $parser, $cache, $renderer);
         $procedure->setTemplate($template);
 
         $this->assertSame($procedure->getTemplate(), $template);
@@ -63,6 +65,7 @@ class ProcedureTest extends \PHPUnit_Framework_TestCase
     {
         $template = 'TEST_{{ input.get("uncompiled") }}_PROCEDURE';
 
+        $engne     = $this->getEngine();
         $parser    = $this->getParser();
         $cache     = $this->getCache();
         $renderer  = $this->getRenderer();
@@ -70,7 +73,7 @@ class ProcedureTest extends \PHPUnit_Framework_TestCase
         $input  = $this->getInput();
         $input->set('uncompiled', 'COMPILED');
 
-        $procedure = $this->getProcedure($parser, $cache, $renderer);
+        $procedure = $this->getProcedure($engne, $parser, $cache, $renderer);
         $procedure->setTemplate($template);
 
         $this->assertSame('TEST_COMPILED_PROCEDURE', $procedure->compile($input));
@@ -87,17 +90,17 @@ class ProcedureTest extends \PHPUnit_Framework_TestCase
     {
         $this->setExpectedException('\JonnyW\PhantomJs\Exception\NotWritableException');
 
+        $engne    = $this->getEngine();
         $parser   = $this->getParser();
         $renderer = $this->getRenderer();
 
         $cache = $this->getCache('/an/invalid/dir');
 
-        $client = $this->getClient();
         $input  = $this->getInput();
         $output = $this->getOutput();
 
-        $procedure = $this->getProcedure($parser, $cache, $renderer);
-        $procedure->run($client, $input, $output);
+        $procedure = $this->getProcedure($engne, $parser, $cache, $renderer);
+        $procedure->run($input, $output);
     }
 
     /**
@@ -117,12 +120,12 @@ class ProcedureTest extends \PHPUnit_Framework_TestCase
         $input    = $this->getInput();
         $output   = $this->getOutput();
 
-        $client = $this->getClient();
-        $client->method('getCommand')
+        $engne = $this->getEngine();
+        $engne->method('getCommand')
             ->will($this->throwException(new \Exception()));
 
-        $procedure = $this->getProcedure($parser, $cache, $renderer);
-        $procedure->run($client, $input, $output);
+        $procedure = $this->getProcedure($engne, $parser, $cache, $renderer);
+        $procedure->run($input, $output);
     }
 
 /** +++++++++++++++++++++++++++++++++++ **/
@@ -133,14 +136,15 @@ class ProcedureTest extends \PHPUnit_Framework_TestCase
      * Get procedure instance.
      *
      * @access protected
+     * @param  \JonnyW\PhantomJs\Engine                             $engine
      * @param  \JonnyW\PhantomJs\Parser\ParserInterface             $parser
      * @param  \JonnyW\PhantomJs\Cache\CacheInterface               $cacheHandler
      * @param  \JonnyW\PhantomJs\Template\TemplateRendererInterface $renderer
      * @return \JonnyW\PhantomJs\Procedure\Procedure
      */
-    protected function getProcedure(ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
+    protected function getProcedure(Engine $engine, ParserInterface $parser, CacheInterface $cacheHandler, TemplateRendererInterface $renderer)
     {
-        $procedure = new Procedure($parser, $cacheHandler, $renderer);
+        $procedure = new Procedure($engine, $parser, $cacheHandler, $renderer);
 
         return $procedure;
     }
@@ -221,16 +225,15 @@ class ProcedureTest extends \PHPUnit_Framework_TestCase
 /** +++++++++++++++++++++++++++++++++++ **/
 
     /**
-     * Get client.
+     * Get engine
      *
      * @access protected
-     * @return \JonnyW\PhantomJs\ClientInterface
+     * @return \JonnyW\PhantomJs\Engine
      */
-    protected function getClient()
+    protected function getEngine()
     {
-        $client = $this->getMock('\JonnyW\PhantomJs\ClientInterface');
+        $engine = $this->getMock('\JonnyW\PhantomJs\Engine');
 
-        return $client;
+        return $engine;
     }
-
 }