diff --git a/src/DrupalCI/Job/CodeBase/JobCodeBase.php b/src/DrupalCI/Job/CodeBase/JobCodeBase.php
index e43c91b..ff57bb8 100644
--- a/src/DrupalCI/Job/CodeBase/JobCodeBase.php
+++ b/src/DrupalCI/Job/CodeBase/JobCodeBase.php
@@ -63,6 +63,38 @@ class JobCodebase {
     $this->setRepositories($repository);
   }
 
+  /**
+   * Any patches used to generate this codebase
+   *
+   * #var \DrupalCI\Job\Codebase\Patch
+   */
+  protected $patches;
+  public function getPatches() { return $this->patches;  }
+  public function setPatches($patches) {  $this->patches = $patches;  }
+  public function addPatch(Patch $patch) {
+    if (!empty($this->patches) && !in_array($patch, $this->patches)) {
+      $this->patches[] = $patch;
+    }
+  }
+
+  /**
+   * A storage variable for any modified files
+   */
+  protected $modified_files = [];
+  public function getModifiedFiles() {  return $this->modified_files;  }
+  public function addModifiedFile($filename) {
+    if (!is_array($this->modified_files)) { $this->modified_files = []; }
+    if (!in_array($filename, $this->modified_files)) { $this->modified_files[] = $filename;  }
+  }
+  public function addModifiedFiles($files) {
+    foreach ($files as $file) {
+      $this->addModifiedFile($file);
+    }
+  }
+
+  /**
+   * @param JobDefinition $job_definition
+   */
   public function setupProject(JobDefinition $job_definition) {
     // Core Project
     // For future compatibility.  In the future, we could potentially add
diff --git a/src/DrupalCI/Job/CodeBase/Patch.php b/src/DrupalCI/Job/CodeBase/Patch.php
new file mode 100644
index 0000000..dbe79f8
--- /dev/null
+++ b/src/DrupalCI/Job/CodeBase/Patch.php
@@ -0,0 +1,311 @@
+<?php
+
+/**
+ * @file
+ * Contains \DrupalCI\Job\CodeBase\Patch
+ */
+
+namespace DrupalCI\Job\CodeBase;
+
+use DrupalCI\Console\Output;
+use DrupalCI\Job\CodeBase\JobCodeBase;
+use Guzzle\Http\Client;
+use Guzzle\Http\ClientInterface;
+
+/**
+ * Class Patch
+ * @package DrupalCI\Job\CodeBase
+ */
+class Patch
+{
+
+  /**
+   * Local or Remote Patch File
+   *
+   * @var string
+   */
+  protected $type = 'remote';
+
+  /**
+   * @return string
+   */
+  public function getType() {
+    return $this->type;
+  }
+
+  /**
+   * @param string $type
+   */
+  public function setType($type) {
+    $this->type = $type;
+  }
+
+  /**
+   * Source patch location
+   *
+   * @var string
+   */
+  protected $source;
+
+  /**
+   * @return string
+   */
+  public function getSource()
+  {
+    return $this->source;
+  }
+
+  /**
+   * @param string $source
+   */
+  protected function setSource($source)
+  {
+    $this->source = $source;
+  }
+
+  /**
+   * Source patch location on the local file system
+   *
+   * @var string
+   */
+  protected $local_source;
+
+  /**
+   * @return string
+   */
+  public function getLocalSource()
+  {
+    return $this->local_source;
+  }
+
+  /**
+   * @param string $local_source
+   */
+  protected function setLocalSource($local_source)
+  {
+    $this->local_source = $local_source;
+  }
+
+  /**
+   * Target patch application directory
+   *
+   * @var string
+   */
+  protected $apply_dir;
+
+  /**
+   * @return string
+   */
+  public function getApplyDir()
+  {
+    return $this->apply_dir;
+  }
+
+  /**
+   * @param string $apply_dir
+   */
+  protected function setApplyDir($apply_dir)
+  {
+    $this->apply_dir = $apply_dir;
+  }
+
+  /**
+   * "Patch has been applied" flag
+   *
+   * @var bool
+   */
+  protected $applied;
+
+  /**
+   * List of files modified by this patch
+   *
+   * @var array
+   */
+  protected $modified_files;
+
+  /**
+   * Job Working directory
+   *
+   * @var string
+   */
+  protected $working_dir;
+
+  /**
+   * @param string $working_dir
+   */
+  protected function setWorkingDir($working_dir)
+  {
+    $this->working_dir = $working_dir;
+  }
+
+  /**
+   * @var \Guzzle\Http\ClientInterface
+   */
+  protected $httpClient;
+
+  /**
+   * @param string $patch_details
+   * @param JobCodeBase $codebase
+   */
+  public function __construct($patch_details, $codebase)
+  {
+    // Copy working directory from the initial codebase
+    $working_dir = $codebase->getWorkingDir();
+    $this->setWorkingDir($working_dir);
+
+    // Set source and apply_dir properties
+    $this->setSource($patch_details['patch_file']);
+    $this->setApplyDir((!empty($patch_details['patch_dir'])) ? $working_dir . DIRECTORY_SEPARATOR . $patch_details['patch_dir'] : $working_dir);
+
+    // Determine whether passed a URL or local file
+    $type = filter_var($patch_details['patch_file'], FILTER_VALIDATE_URL) ? "remote" : "local";
+    $this->setType($type);
+
+    // If a remote file, download a local copy
+    if ($type == "remote") {
+      // Download the patch file
+      // If any errors encountered during download, we expect guzzle to throw
+      // an appropriate exception.
+      $local_source = $this->download();
+    } else {
+      // If a local file, we already know the local source location
+      $local_source = $this->working_dir . DIRECTORY_SEPARATOR . $patch_details['patch_file'];
+    }
+    $this->setLocalSource($local_source);
+
+    // Set initial 'applied' state
+    $this->applied = false;
+
+    // Attach this patch to the codebase object
+    $codebase->addPatch($this);
+  }
+
+  /**
+   * Obtain remote patch file
+   *
+   * @return string
+   */
+  protected function download()
+  {
+    $url = $this->getSource();
+    $file_info = pathinfo($url);
+    $directory = $this->working_dir;
+
+    $destination_file = $directory . DIRECTORY_SEPARATOR . $file_info['basename'];
+    $this->httpClient()
+      ->get($url)
+      ->setResponseBody($destination_file)
+      ->send();
+    Output::writeln("<info>Patch downloaded to <options=bold>$destination_file</options=bold></info>");
+    return $destination_file;
+  }
+
+  /**
+   * Validate patch file and target directory
+   *
+   * @return bool
+   */
+  public function validate()
+  {
+    if ($this->validate_file() && $this->validate_target()) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Validate file exists
+   *
+   * @return bool
+   */
+  public function validate_file()
+  {
+    $source = $this->getLocalSource();
+    $real_file = realpath($source);
+    if ($real_file === FALSE) {
+      // Invalid patch file
+      Output::error("Patch Error", "The patch file <info>$source</info> is invalid.");
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * Validate target directory exists
+   *
+   * @return bool
+   */
+  public function validate_target()
+  {
+    $apply_dir = $this->getApplyDir();
+    $real_directory = realpath($apply_dir);
+    if ($real_directory === FALSE) {
+      // Invalid target directory
+      Output::error("Patch Error", "The target patch directory <info>$apply_dir</info> is invalid.");
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * Apply the patch
+   *
+   * @return bool
+   */
+  public function apply()
+  {
+    $source = realpath($this->getLocalSource());
+    $target = realpath($this->getApplyDir());
+
+    $cmd = "git apply -p1 $source --directory $target 2>&1";
+
+    exec($cmd, $cmdoutput, $result);
+    if ($result !== 0) {
+      // The command threw an error.
+      Output::writeLn($cmdoutput);
+      Output::error("Patch Error", "The patch attempt returned an error.  Error code: $result");
+      // TODO: Pass on the actual return value for the patch attempt
+      return FALSE;
+    }
+    Output::writeLn("<comment>Patch <options=bold>$source</options=bold> applied to directory <options=bold>$target</options=bold></comment>");
+    $this->applied = TRUE;
+    return TRUE;
+  }
+
+  /**
+   * Retrieves the files modified by this patch
+   *
+   * @return array|bool
+   */
+  public function getModifiedFiles()
+  {
+    // Only calculate the modified files if the patch has been applied.
+    if (!$this->applied) {
+      return [];
+    }
+    if (empty($this->modified_files)) {
+      // Calculate modified files
+      $apply_dir = $this->getApplyDir();
+      $cmd = "cd $apply_dir && git diff --name-only";
+      exec($cmd, $cmdoutput, $return);
+      if ($return !== 0) {
+        // git diff returned a non-zero error code
+        Output::writeln("<error>Git diff command returned a non-zero code while attempting to parse modified files. (Return Code: $return)</error>");
+        return FALSE;
+      }
+      $this->modified_files = $cmdoutput;
+    }
+    return $this->modified_files;
+  }
+
+  /**
+   * @return \Guzzle\Http\ClientInterface
+   */
+  protected function httpClient()
+  {
+    if (!isset($this->httpClient)) {
+      $this->httpClient = new Client;
+    }
+    return $this->httpClient;
+  }
+}
\ No newline at end of file
diff --git a/src/DrupalCI/Plugin/BuildSteps/setup/Checkout.php b/src/DrupalCI/Plugin/BuildSteps/setup/Checkout.php
index f712d45..7ea165e 100644
--- a/src/DrupalCI/Plugin/BuildSteps/setup/Checkout.php
+++ b/src/DrupalCI/Plugin/BuildSteps/setup/Checkout.php
@@ -71,7 +71,7 @@ class Checkout extends SetupBase {
     Output::writeln("<comment>Copying files from <options=bold>$source_dir</options=bold> to the local checkout directory <options=bold>$directory</options=bold> ... </comment>");
     // TODO: Make sure target directory is empty
 #    $this->exec("cp -r $source_dir/. $directory", $cmdoutput, $result);
-    $exclude_var = isset($details['DCI_EXCLUDE']) ? "" : "--exclude=\".git\"";
+    $exclude_var = isset($details['DCI_EXCLUDE']) ? '--exclude="' . $details['DCI_EXCLUDE'] . '"' : "";
     $this->exec("rsync -a $exclude_var  $source_dir/. $directory", $cmdoutput, $result);
     if ($result !== 0) {
       Output::error("Copy error", "Error encountered while attempting to copy code to the local checkout directory.");
diff --git a/src/DrupalCI/Plugin/BuildSteps/setup/Patch.php b/src/DrupalCI/Plugin/BuildSteps/setup/Patch.php
index 80be0a3..97907c5 100644
--- a/src/DrupalCI/Plugin/BuildSteps/setup/Patch.php
+++ b/src/DrupalCI/Plugin/BuildSteps/setup/Patch.php
@@ -10,6 +10,7 @@ namespace DrupalCI\Plugin\BuildSteps\setup;
 
 use DrupalCI\Console\Output;
 use DrupalCI\Plugin\JobTypes\JobInterface;
+use DrupalCI\Job\CodeBase\Patch as PatchFile;
 
 /**
  * @PluginID("patch")
@@ -27,31 +28,28 @@ class Patch extends SetupBase {
     // Normalize data to the third format, if necessary
     $data = (count($data) == count($data, COUNT_RECURSIVE)) ? [$data] : $data;
     Output::writeLn("<info>Entering setup_patch().</info>");
+    $codebase = $job->getJobCodebase();
     foreach ($data as $key => $details) {
       if (empty($details['patch_file'])) {
         Output::error("Patch error", "No valid patch file provided for the patch command.");
         $job->error();
         return;
       }
-      $workingdir = realpath($job->getJobCodebase()->getWorkingDir());
-      $patchfile = $details['patch_file'];
-      $patchdir = (!empty($details['patch_dir'])) ? $details['patch_dir'] : $workingdir;
-      // Validate target directory.
-      if (!($directory = $this->validateDirectory($job, $patchdir))) {
-        // Invalid checkout directory
-        Output::error("Patch Error", "The patch directory <info>$directory</info> is invalid.");
+      // Create a new patch object
+      $patch = new PatchFile($details, $codebase);
+      // Validate our patch's source file and target directory
+      if (!$patch->validate()) {
         $job->error();
         return;
       }
-      $cmd = "cd $directory && git apply -v -p1 $patchfile 2>&1 && cd -";
 
-      exec($cmd, $cmdoutput, $result);
-      if ($result !== 0) {
-        // The command threw an error.
-        Output::writeLn($cmdoutput);
-        Output::error("Patch Error", "The patch attempt returned an error.  Error code: $result");
+      // Apply the patch
+      if (!$patch->apply()) {
         $job->error();
-        // TODO: Pass on the actual return value for the patch attempt
+
+        // Hack to create a xml file for processing by Jenkins.
+        // TODO: Remove once proper job failure processing is in place
+
         // Save an xmlfile to the jenkins artifact directory.
         // find jenkins artifact dir
         //
@@ -76,9 +74,9 @@ class Patch extends SetupBase {
         file_put_contents($output_directory . "/patchfailure.xml", $xml_error);
 
         return;
-      }
-      Output::writeLn("<comment>Patch <options=bold>$patchfile</options=bold> applied to directory <options=bold>$directory</options=bold></comment>");
+      };
+      // Update our list of modified files
+      $codebase->addModifiedFiles($patch->getModifiedFiles());
     }
   }
-
 }
diff --git a/src/DrupalCI/Plugin/BuildSteps/setup/SyntaxCheck.php b/src/DrupalCI/Plugin/BuildSteps/setup/SyntaxCheck.php
new file mode 100644
index 0000000..1a7ed60
--- /dev/null
+++ b/src/DrupalCI/Plugin/BuildSteps/setup/SyntaxCheck.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @file
+ * Contains \DrupalCI\Plugin\BuildSteps\setup\SyntaxCheck
+ *
+ * Processes "setup: syntaxcheck:" instructions from within a job definition.
+ */
+
+namespace DrupalCI\Plugin\BuildSteps\setup;
+
+use DrupalCI\Console\Output;
+use DrupalCI\Plugin\JobTypes\JobInterface;
+use DrupalCI\Plugin\BuildSteps\generic\ContainerCommand;
+
+/**
+ * @PluginID("syntaxcheck")
+ */
+class SyntaxCheck extends SetupBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function run(JobInterface $job, $data) {
+    if ($data != FALSE) {
+      Output::writeLn('<info>SyntaxCheck checking for php syntax errors.</info>');
+
+      $codebase = $job->getJobCodebase();
+      $modified_files = $codebase->getModifiedFiles();
+      if (empty($modified_files)) {
+        return;
+      }
+
+      $bash_array = "";
+      foreach ($modified_files as $file) {
+        $bash_array .= "$file ";
+      }
+
+      // TODO: Remove hardcoded /var/www/html.
+      // This should be come JobCodeBase->getLocalDir() or similar
+      $cmd = "cd /var/www/html && i=0; for file in $bash_array; do php -l \$file; ((i+=\$?)); done; return i;";
+      $command = new ContainerCommand();
+      $command->run($job, $cmd);
+    }
+  }
+}
diff --git a/src/DrupalCI/Plugin/JobTypes/simpletest/SimpletestJob.php b/src/DrupalCI/Plugin/JobTypes/simpletest/SimpletestJob.php
index dfb402e..9583cc0 100644
--- a/src/DrupalCI/Plugin/JobTypes/simpletest/SimpletestJob.php
+++ b/src/DrupalCI/Plugin/JobTypes/simpletest/SimpletestJob.php
@@ -47,6 +47,7 @@ class SimpletestJob extends JobBase {
     // 'DCI_XMLOutput' => '/var/www/html/results/xml',
     'DCI_PHPInterpreter' => '/opt/phpenv/shims/php',
     'DCI_RunOptions' => 'color;keep-results',
+    'DCI_SyntaxCheck' => TRUE,
   );
 
   /**
diff --git a/src/DrupalCI/Plugin/JobTypes/simpletest/drupalci.yml b/src/DrupalCI/Plugin/JobTypes/simpletest/drupalci.yml
index 8fb047e..ab7c3a9 100644
--- a/src/DrupalCI/Plugin/JobTypes/simpletest/drupalci.yml
+++ b/src/DrupalCI/Plugin/JobTypes/simpletest/drupalci.yml
@@ -25,6 +25,7 @@ setup:
     - chown -fR www-data:www-data /var/www/html/sites /var/www/html/results
     - chmod 0777 /var/www/html/artifacts
     - chmod 0777 /tmp
+  syntaxcheck: true
 # The 'install' key is currently required for the dbcreate plugin
 # TODO: make dbcreate consistent with other commands and place in pre-install
 install:
diff --git a/src/DrupalCI/Plugin/Preprocess/definition/SyntaxCheck.php b/src/DrupalCI/Plugin/Preprocess/definition/SyntaxCheck.php
new file mode 100644
index 0000000..75b0340
--- /dev/null
+++ b/src/DrupalCI/Plugin/Preprocess/definition/SyntaxCheck.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @file
+ * Contains \DrupalCI\Plugin\Preprocess\definition\SyntaxCheck
+ *
+ * PreProcesses DCI_SyntaxCheck variable.
+ */
+
+namespace DrupalCI\Plugin\Preprocess\definition;
+
+/**
+ * @PluginID("syntaxcheck")
+ */
+class SyntaxCheck {
+
+  /**
+   * {@inheritdoc}
+   *
+   * DCI_SyntaxCheck_Preprocessor
+   *
+   * If set, run php link on committed code.
+   *
+   * Input format: (bool) $value = "true"
+   */
+  public function process(array &$definition, $value) {
+    // Make sure our syntax check runs last.
+    unset($definition['setup']['syntaxcheck']);
+    $definition['setup']['syntaxcheck'] = $value ? "TRUE" : "FALSE";
+  }
+}
