Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Introduced the sql:build command

Added a `Filesystem` class (thanks to Symfony2), added `Finder`
(Symfony2 component) as a dependency, added a bunch of new named
exceptions
  • Loading branch information...
commit b141fdda3808f2e068c648ffb8868e7695f1ac43 1 parent 476c9c2
@willdurand willdurand authored
View
1  bin/propel.php
@@ -12,4 +12,5 @@
$app = new Application('Propel', '2.0 (dev)');
$app->add(new \Propel\Generator\Command\TestPrepare());
+$app->add(new \Propel\Generator\Command\SqlBuild());
$app->run();
View
3  composer.json
@@ -20,7 +20,8 @@
"php": ">=5.3.2",
"symfony/yaml": ">=2.0",
"symfony/console": ">=2.0",
- "monolog/monolog": ">=1.0.2"
+ "monolog/monolog": ">=1.0.2",
+ "symfony/finder": ">=2.0"
},
"autoload": {
"psr-0": {
View
2  composer.lock
@@ -1 +1 @@
-[{"package":"symfony\/console","version":"2.1.0-dev"},{"package":"symfony\/yaml","version":"2.1.0-dev"},{"package":"symfony\/class-loader","version":"2.1.0-dev"}]
+[{"package":"symfony\/yaml","version":"2.1.0-dev"},{"package":"symfony\/class-loader","version":"2.1.0-dev"},{"package":"symfony\/console","version":"2.1.0-dev"},{"package":"symfony\/finder","version":"2.1.0-dev"}]
View
118 src/Propel/Generator/Command/SqlBuild.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+namespace Propel\Generator\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Output\Output;
+use Symfony\Component\Finder\Finder;
+
+use Propel\Generator\Config\GeneratorConfig;
+use Propel\Generator\Manager\SqlManager;
+use Propel\Generator\Util\Filesystem;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+class SqlBuild extends Command
+{
+ const DEFAULT_INPUT_DIRECTORY = '.';
+
+ const DEFAULT_OUTPUT_DIRECTORY = 'generated-sql';
+
+ const DEFAULT_PLATFORM = 'MysqlPlatform';
+
+ /**
+ * @var \Symfony\Component\Console\Output\OutputInterface
+ */
+ private $output;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this
+ ->setDefinition(array(
+ new InputOption('input-dir', null, InputOption::VALUE_REQUIRED, 'The input directory', self::DEFAULT_INPUT_DIRECTORY),
+ new InputOption('output-dir', null, InputOption::VALUE_REQUIRED, 'The output directory', self::DEFAULT_OUTPUT_DIRECTORY),
+ new InputOption('platform', null, InputOption::VALUE_REQUIRED, 'The platform', self::DEFAULT_PLATFORM),
+ new InputOption('validate', null, InputOption::VALUE_NONE, '')
+ ))
+ ->setName('sql:build')
+ ->setDescription('Build SQL files')
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $manager = new SqlManager();
+
+ $buildProperties = realpath($input->getOption('input-dir') . DIRECTORY_SEPARATOR . 'build.properties');
+ $defaultProperties = realpath(__DIR__.'/../../../../tools/generator/default.properties');
+
+ $generatorConfig = new GeneratorConfig(array_merge(
+ $this->getBuildProperties($defaultProperties),
+ $this->getBuildProperties($buildProperties),
+ array(
+ 'propel.platform.class' => $input->getOption('platform'),
+ )
+ ));
+
+ $finder = new Finder();
+ $files = $finder
+ ->name('*schema.xml')
+ ->in($input->getOption('input-dir'))
+ ->depth(0)
+ ->files()
+ ;
+
+ $filesystem = new Filesystem();
+ $filesystem->mkdir($input->getOption('output-dir'));
+
+ $manager->setValidate($input->getOption('validate'));
+ $manager->setGeneratorConfig($generatorConfig);
+ $manager->setIncludedFiles($files);
+ $manager->setLoggerClosure(function($message) use ($output) {
+ $output->writeln($message);
+ });
+ $manager->setWorkingDirectory($input->getOption('output-dir'));
+
+ $manager->buildSql();
+ }
+
+ protected function getBuildProperties($file)
+ {
+ $properties = array();
+
+ if (false === $lines = @file($file)) {
+ throw new \Exception(sprintf('Unable to parse contents of "%s".', $file));
+ }
+
+ foreach ($lines as $line) {
+ $line = trim($line);
+
+ if ('' == $line || in_array($line[0], array('#', ';'))) {
+ continue;
+ }
+
+ $pos = strpos($line, '=');
+ $properties[trim(substr($line, 0, $pos))] = trim(substr($line, $pos + 1));
+ }
+
+ return $properties;
+ }
+}
View
15 src/Propel/Generator/Exception/BuildException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+namespace Propel\Generator\Exception;
+
+class BuildException extends RuntimeException implements ExceptionInterface
+{
+}
View
18 src/Propel/Generator/Exception/ExceptionInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+namespace Propel\Generator\Exception;
+
+/**
+ * @author William Durand <william.durand1@gmail.com>
+ */
+interface ExceptionInterface
+{
+}
View
15 src/Propel/Generator/Exception/InvalidArgumentException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+namespace Propel\Generator\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
View
15 src/Propel/Generator/Exception/RuntimeException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+namespace Propel\Generator\Exception;
+
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
View
3  src/Propel/Generator/Exception/SchemaException.php
@@ -14,8 +14,7 @@
/**
* Class for exceptions thrown during schema parsing
- *
*/
-class SchemaException extends Exception
+class SchemaException extends Exception implements ExceptionInterface
{
}
View
355 src/Propel/Generator/Manager/AbstractManager.php
@@ -0,0 +1,355 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+namespace Propel\Generator\Manager;
+
+use Propel\Generator\Builder\Util\XmlToAppData;
+use Propel\Generator\Config\GeneratorConfig;
+use Propel\Generator\Exception\BuildException;
+use Propel\Generator\Exception\EngineException;
+use Propel\Generator\Model\AppData;
+use Propel\Generator\Model\Database;
+
+use \DomDocument;
+
+/**
+ * An abstract base Propel manager to perform work related to the XML schema file.
+ *
+ * @author Hans Lellelid <hans@xmpl.org> (Propel)
+ * @author Jason van Zyl <jvanzyl@zenplex.com> (Torque)
+ * @author Daniel Rall <dlr@finemaltcoding.com> (Torque)
+ */
+abstract class AbstractManager
+{
+ /**
+ * Data models that we collect. One from each XML schema file.
+ */
+ protected $dataModels = array();
+
+ /**
+ * Map of data model name to database name.
+ * Should probably stick to the convention
+ * of them being the same but I know right now
+ * in a lot of cases they won't be.
+ */
+ protected $dataModelDbMap;
+
+ /**
+ * DB encoding to use for XmlToAppData object
+ */
+ protected $dbEncoding = 'iso-8859-1';
+
+ /**
+ * Whether to perform validation (XSD) on the schema.xml file(s).
+ * @var boolean
+ */
+ protected $validate;
+
+ /**
+ * The XSD schema file to use for validation.
+ * @var string
+ */
+ protected $xsd;
+
+ /**
+ * XSL file to use to normalize (or otherwise transform) schema before validation.
+ * @var string
+ */
+ protected $xsl;
+
+ /**
+ * Gets list of all used xml schemas
+ *
+ * @var array
+ */
+ protected $includedFiles = array();
+
+ /**
+ * @var \closure
+ */
+ private $loggerClosure = null;
+
+ /**
+ * Have datamodels been initialized?
+ * @var boolean
+ */
+ private $dataModelsLoaded = false;
+
+ /**
+ * An initialized GeneratorConfig object.
+ *
+ * @var \Propel\Generator\Config\GeneratorConfig
+ */
+ private $generatorConfig;
+
+ /**
+ * @return array
+ */
+ public function getIncludedFiles()
+ {
+ return $this->includedFiles;
+ }
+
+ /**
+ * @param array
+ */
+ public function setIncludedFiles($includedFiles)
+ {
+ $this->includedFiles = $includedFiles;
+ }
+
+ /**
+ * Return the data models that have been
+ * processed.
+ *
+ * @return List data models
+ */
+ public function getDataModels()
+ {
+ if (!$this->dataModelsLoaded) {
+ $this->loadDataModels();
+ }
+
+ return $this->dataModels;
+ }
+
+ /**
+ * Return the data model to database name map.
+ *
+ * @return Hashtable data model name to database name map.
+ */
+ public function getDataModelDbMap()
+ {
+ if (!$this->dataModelsLoaded) {
+ $this->loadDataModels();
+ }
+
+ return $this->dataModelDbMap;
+ }
+
+ /**
+ * Set whether to perform validation on the datamodel schema.xml file(s).
+ *
+ * @param boolean $validate
+ */
+ public function setValidate($validate)
+ {
+ $this->validatealidate = (boolean) $validate;
+ }
+
+ /**
+ * Set the XSD schema to use for validation of any datamodel schema.xml file(s).
+ *
+ * @param string $xsd
+ */
+ public function setXsd($xsd)
+ {
+ $this->xsd = $xsd;
+ }
+
+ /**
+ * Set the normalization XSLT to use to transform datamodel schema.xml file(s) before validation and parsing.
+ *
+ * @param string $xsl
+ */
+ public function setXsl($xsl)
+ {
+ $this->xsl = $xsl;
+ }
+
+ /**
+ * Set the current target database encoding.
+ *
+ * @param string $encoding Target database encoding
+ */
+ public function setDbEncoding($encoding)
+ {
+ $this->dbEncoding = $encoding;
+ }
+
+ /**
+ * @var \closure $logger A logger closure
+ */
+ public function setLoggerClosure(\closure $logger)
+ {
+ $this->loggerClosure = $logger;
+ }
+
+ /**
+ * Gets all matching XML schema files and loads them into data models for class.
+ * @return void
+ */
+ protected function loadDataModels()
+ {
+ $ads = array();
+ $totalNbTables = 0;
+ $dataModelFiles = $this->getIncludedFiles();
+ $defaultPlatform = $this->getGeneratorConfig()->getConfiguredPlatform();
+
+ // Make a transaction for each file
+ foreach ($dataModelFiles as $schema) {
+ $dmFilename = $schema->getPathName();
+ $this->log('Processing: ' . $schema->getFileName());
+
+ $dom = new DomDocument('1.0', 'UTF-8');
+ $dom->load($dmFilename);
+
+ $this->includeExternalSchemas($dom, $srcDir);
+
+ // normalize (or transform) the XML document using XSLT
+ if ($this->getGeneratorConfig()->getBuildProperty('schemaTransform') && $this->xsl) {
+ $this->log('Transforming ' . $dmFilename . ' using stylesheet ' . $this->xsl->getPath());
+
+ if (!class_exists('\XSLTProcessor')) {
+ $this->log('Could not perform XLST transformation. Make sure PHP has been compiled/configured to support XSLT.');
+ } else {
+ // normalize the document using normalizer stylesheet
+ $xslDom = new DomDocument('1.0', 'UTF-8');
+ $xslDom->load($this->xsl->getAbsolutePath());
+ $xsl = new \XsltProcessor();
+ $xsl->importStyleSheet($xslDom);
+ $dom = $xsl->transformToDoc($dom);
+ }
+ }
+
+ // validate the XML document using XSD schema
+ if ($this->validate && $this->xsd) {
+ $this->log(' Validating XML using schema ' . $this->xsd->getPath());
+
+ if (!$dom->schemaValidate($this->xsd->getAbsolutePath())) {
+ throw new EngineException(<<<EOT
+XML schema file ($xmlFile->getPath()) does not validate.
+See warnings above for reasons validation failed (make sure error_reporting
+is set to show E_WARNING if you don't see any)
+EOT
+ , $this->getLocation());
+ }
+ }
+
+ $xmlParser = new XmlToAppData($defaultPlatform, $this->dbEncoding);
+ $xmlParser->setGeneratorConfig($this->getGeneratorConfig());
+ $ad = $xmlParser->parseString($dom->saveXML(), $dmFilename);
+ $nbTables = $ad->getDatabase(null, false)->countTables();
+ $totalNbTables += $nbTables;
+
+ $this->log(sprintf(' %d tables processed successfully', $nbTables));
+
+ $ad->setName($dmFilename);
+ $ads[] = $ad;
+ }
+
+ $this->log(sprintf('%d tables found in %d schema files.', $totalNbTables, count($dataModelFiles)));
+
+ if (empty($ads)) {
+ throw new BuildException('No schema files were found (matching your schema fileset definition).');
+ }
+
+ foreach ($ads as $ad) {
+ // map schema filename with database name
+ $this->dataModelDbMap[$ad->getName()] = $ad->getDatabase(null, false)->getName();
+ }
+
+ if (count($ads) > 1) {
+ $ad = $this->joinDataModels($ads);
+ $this->dataModels = array($ad);
+ } else {
+ $this->dataModels = $ads;
+ }
+
+ foreach ($this->dataModels as &$ad) {
+ $ad->doFinalInitialization();
+ }
+
+ $this->dataModelsLoaded = true;
+ }
+
+ /**
+ * Replaces all external-schema nodes with the content of xml schema that node refers to
+ *
+ * Recurses to include any external schema referenced from in an included xml (and deeper)
+ * Note: this function very much assumes at least a reasonable XML schema, maybe it'll proof
+ * users don't have those and adding some more informative exceptions would be better
+ *
+ * @param \DomDocument $dom
+ * @param string $srcDir
+ */
+ protected function includeExternalSchemas(DomDocument $dom, $srcDir)
+ {
+ $databaseNode = $dom->getElementsByTagName('database')->item(0);
+ $externalSchemaNodes = $dom->getElementsByTagName('external-schema');
+
+ $nbIncludedSchemas = 0;
+ while ($externalSchema = $externalSchemaNodes->item(0)) {
+ $include = $externalSchema->getAttribute('filename');
+
+ $this->log('Processing external schema: ' . $include);
+
+ $externalSchema->parentNode->removeChild($externalSchema);
+
+ $externalSchemaDom = new DomDocument('1.0', 'UTF-8');
+ $externalSchemaDom->load(realpath($externalSchemaFile));
+
+ // The external schema may have external schemas of its own ; recurse
+ $this->includeExternalSchemas($externalSchemaDom, $srcDir);
+ foreach ($externalSchemaDom->getElementsByTagName('table') as $tableNode) {
+ $databaseNode->appendChild($dom->importNode($tableNode, true));
+ }
+
+ $nbIncludedSchemas++;
+ }
+
+ return $nbIncludedSchemas;
+ }
+
+ /**
+ * Joins the datamodels collected from schema.xml files into one big datamodel.
+ * We need to join the datamodels in this case to allow for foreign keys
+ * that point to tables in different packages.
+ *
+ * @param array[\Propel\Generator\Model\AppData] $ads The datamodels to join
+ * @return \Propel\Generator\Model\AppData The single datamodel with all other datamodels joined in
+ */
+ protected function joinDataModels($ads)
+ {
+ $mainAppData = array_shift($ads);
+ $mainAppData->joinAppDatas($ads);
+
+ return $mainAppData;
+ }
+
+ /**
+ * Gets the GeneratorConfig object for this manager or creates it on-demand.
+ *
+ * @return \Propel\Generator\Config\GeneratorConfig
+ */
+ protected function getGeneratorConfig()
+ {
+ return $this->generatorConfig;
+ }
+
+ protected function validate()
+ {
+ if ($this->validate) {
+ if (!$this->xsd) {
+ throw new BuildException("'validate' set to TRUE, but no XSD specified (use 'xsd' attribute).");
+ }
+ }
+ }
+
+ protected function log($message)
+ {
+ if (null !== $this->loggerClosure) {
+ $closure = $this->loggerClosure;
+ $closure($message);
+ } else {
+ var_dump($message);
+ }
+ }
+}
View
32 src/Propel/Generator/Util/SqlManager.php → src/Propel/Generator/Manager/SqlManager.php
@@ -8,36 +8,38 @@
* @license MIT License
*/
-namespace Propel\Generator\Util;
+namespace Propel\Generator\Manager;
use Propel\Generator\Config\GeneratorConfigInterface;
+use Propel\Generator\Exception\InvalidArgumentException;
-use \InvalidArgumentException;
use \PDO;
+use \PDOException;
+
+use \Exception;
/**
* Service class for managing SQL.
*
* @author William Durand <william.durand1@gmail.com>
*/
-class SqlManager
+class SqlManager extends AbstractManager
{
/**
* @var array
*/
protected $connections;
+
/**
* @var GeneratorConfigInterface
*/
protected $generatorConfig;
- /**
- * @var array
- */
- protected $dataModels;
+
/**
* @var array
*/
protected $databases = null;
+
/**
* @var string
*/
@@ -94,22 +96,6 @@ public function getGeneratorConfig()
}
/**
- * @param array $dataModels
- */
- public function setDataModels($dataModels)
- {
- $this->dataModels = $dataModels;
- }
-
- /**
- * @return array
- */
- public function getDataModels()
- {
- return $this->dataModels;
- }
-
- /**
* @param string $workingDirectory
*/
public function setWorkingDirectory($workingDirectory)
View
3  src/Propel/Generator/Model/XmlElement.php
@@ -10,7 +10,7 @@
namespace Propel\Generator\Model;
-use \InvalidArgumentException;
+use Propel\Generator\Exception\InvalidArgumentException;
/**
* An abstract class for elements represented by XML tags (e.g. Column, Table).
@@ -19,7 +19,6 @@
*/
abstract class XmlElement
{
-
/**
* The name => value attributes from XML.
*
View
275 src/Propel/Generator/Util/Filesystem.php
@@ -0,0 +1,275 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+namespace Propel\Generator\Util;
+
+/**
+ * Provides basic utility to manipulate the file system.
+ * This class comes from the Symfony2 framework.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Filesystem
+{
+ /**
+ * Copies a file.
+ *
+ * This method only copies the file if the origin file is newer than the target file.
+ *
+ * By default, if the target already exists, it is not overridden.
+ *
+ * @param string $originFile The original filename
+ * @param string $targetFile The target filename
+ * @param array $override Whether to override an existing file or not
+ */
+ public function copy($originFile, $targetFile, $override = false)
+ {
+ $this->mkdir(dirname($targetFile));
+
+ if (!$override && is_file($targetFile)) {
+ $doCopy = filemtime($originFile) > filemtime($targetFile);
+ } else {
+ $doCopy = true;
+ }
+
+ if ($doCopy) {
+ copy($originFile, $targetFile);
+ }
+ }
+
+ /**
+ * Creates a directory recursively.
+ *
+ * @param string|array|\Traversable $dirs The directory path
+ * @param int $mode The directory mode
+ *
+ * @return Boolean true if the directory has been created, false otherwise
+ */
+ public function mkdir($dirs, $mode = 0777)
+ {
+ $ret = true;
+ foreach ($this->toIterator($dirs) as $dir) {
+ if (is_dir($dir)) {
+ continue;
+ }
+
+ $ret = @mkdir($dir, $mode, true) && $ret;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Creates empty files.
+ *
+ * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove
+ */
+ public function touch($files)
+ {
+ foreach ($this->toIterator($files) as $file) {
+ touch($file);
+ }
+ }
+
+ /**
+ * Removes files or directories.
+ *
+ * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove
+ */
+ public function remove($files)
+ {
+ $files = iterator_to_array($this->toIterator($files));
+ $files = array_reverse($files);
+ foreach ($files as $file) {
+ if (!file_exists($file)) {
+ continue;
+ }
+
+ if (is_dir($file) && !is_link($file)) {
+ $this->remove(new \FilesystemIterator($file));
+
+ rmdir($file);
+ } else {
+ unlink($file);
+ }
+ }
+ }
+
+ /**
+ * Change mode for an array of files or directories.
+ *
+ * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove
+ * @param integer $mode The new mode
+ * @param integer $umask The mode mask (octal)
+ */
+ public function chmod($files, $mode, $umask = 0000)
+ {
+ $currentUmask = umask();
+ umask($umask);
+
+ foreach ($this->toIterator($files) as $file) {
+ chmod($file, $mode);
+ }
+
+ umask($currentUmask);
+ }
+
+ /**
+ * Renames a file.
+ *
+ * @param string $origin The origin filename
+ * @param string $target The new filename
+ *
+ * @throws \RuntimeException When target file already exists
+ */
+ public function rename($origin, $target)
+ {
+ // we check that target does not exist
+ if (is_readable($target)) {
+ throw new \RuntimeException(sprintf('Cannot rename because the target "%s" already exist.', $target));
+ }
+
+ rename($origin, $target);
+ }
+
+ /**
+ * Creates a symbolic link or copy a directory.
+ *
+ * @param string $originDir The origin directory path
+ * @param string $targetDir The symbolic link name
+ * @param Boolean $copyOnWindows Whether to copy files if on Windows
+ */
+ public function symlink($originDir, $targetDir, $copyOnWindows = false)
+ {
+ if (!function_exists('symlink') && $copyOnWindows) {
+ $this->mirror($originDir, $targetDir);
+
+ return;
+ }
+
+ $ok = false;
+ if (is_link($targetDir)) {
+ if (readlink($targetDir) != $originDir) {
+ unlink($targetDir);
+ } else {
+ $ok = true;
+ }
+ }
+
+ if (!$ok) {
+ symlink($originDir, $targetDir);
+ }
+ }
+
+ /**
+ * Given an existing path, convert it to a path relative to a given starting path
+ *
+ * @var string Absolute path of target
+ * @var string Absolute path where traversal begins
+ *
+ * @return string Path of target relative to starting path
+ */
+ public function makePathRelative($endPath, $startPath)
+ {
+ // Find for which character the the common path stops
+ $offset = 0;
+ while ($startPath[$offset] === $endPath[$offset]) {
+ $offset++;
+ }
+
+ // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
+ $depth = substr_count(substr($startPath, $offset), DIRECTORY_SEPARATOR) + 1;
+
+ // Repeated "../" for each level need to reach the common path
+ $traverser = str_repeat('../', $depth);
+
+ // Construct $endPath from traversing to the common path, then to the remaining $endPath
+ return $traverser.substr($endPath, $offset);
+ }
+
+ /**
+ * Mirrors a directory to another.
+ *
+ * @param string $originDir The origin directory
+ * @param string $targetDir The target directory
+ * @param \Traversable $iterator A Traversable instance
+ * @param array $options An array of boolean options
+ * Valid options are:
+ * - $options['override'] Whether to override an existing file on copy or not (see copy())
+ * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
+ *
+ * @throws \RuntimeException When file type is unknown
+ */
+ public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array())
+ {
+ $copyOnWindows = false;
+ if (isset($options['copy_on_windows']) && !function_exists('symlink')) {
+ $copyOnWindows = $options['copy_on_windows'];
+ }
+
+ if (null === $iterator) {
+ $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
+ $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
+ }
+
+ if ('/' === substr($targetDir, -1) || '\\' === substr($targetDir, -1)) {
+ $targetDir = substr($targetDir, 0, -1);
+ }
+
+ if ('/' === substr($originDir, -1) || '\\' === substr($originDir, -1)) {
+ $originDir = substr($originDir, 0, -1);
+ }
+
+ foreach ($iterator as $file) {
+ $target = $targetDir.'/'.str_replace($originDir.DIRECTORY_SEPARATOR, '', $file->getPathname());
+
+ if (is_link($file)) {
+ $this->symlink($file, $target);
+ } elseif (is_dir($file)) {
+ $this->mkdir($target);
+ } elseif (is_file($file) || ($copyOnWindows && is_link($file))) {
+ $this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
+ } else {
+ throw new \RuntimeException(sprintf('Unable to guess "%s" file type.', $file));
+ }
+ }
+ }
+
+ /**
+ * Returns whether the file path is an absolute path.
+ *
+ * @param string $file A file path
+ *
+ * @return Boolean
+ */
+ public function isAbsolutePath($file)
+ {
+ if ($file[0] == '/' || $file[0] == '\\'
+ || (strlen($file) > 3 && ctype_alpha($file[0])
+ && $file[1] == ':'
+ && ($file[2] == '\\' || $file[2] == '/')
+ )
+ || null !== parse_url($file, PHP_URL_SCHEME)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private function toIterator($files)
+ {
+ if (!$files instanceof \Traversable) {
+ $files = new \ArrayObject(is_array($files) ? $files : array($files));
+ }
+
+ return $files;
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.