2011-03-26 07:37:25 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2011-12-22 18:36:46 +00:00
namespace Symfony\Component\Filesystem ;
2011-03-26 07:37:25 +00:00
2012-06-18 11:41:52 +01:00
use Symfony\Component\Filesystem\Exception\IOException ;
2011-03-26 07:37:25 +00:00
/**
* Provides basic utility to manipulate the file system .
*
* @ 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 .
*
2012-07-28 17:07:17 +01:00
* @ param string $originFile The original filename
* @ param string $targetFile The target filename
* @ param boolean $override Whether to override an existing file or not
2012-05-18 15:19:51 +01:00
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When copy fails
2011-03-26 07:37:25 +00:00
*/
public function copy ( $originFile , $targetFile , $override = false )
{
2013-05-06 10:20:17 +01:00
if ( stream_is_local ( $originFile ) && ! is_file ( $originFile )) {
2013-04-28 16:32:44 +01:00
throw new IOException ( sprintf ( 'Failed to copy %s because file not exists' , $originFile ));
}
2011-03-26 07:37:25 +00:00
$this -> mkdir ( dirname ( $targetFile ));
2013-12-30 23:30:14 +00:00
if ( ! $override && is_file ( $targetFile ) && null === parse_url ( $originFile , PHP_URL_HOST )) {
2011-08-26 09:34:23 +01:00
$doCopy = filemtime ( $originFile ) > filemtime ( $targetFile );
} else {
$doCopy = true ;
2011-03-26 07:37:25 +00:00
}
2011-08-26 09:34:23 +01:00
if ( $doCopy ) {
2013-04-28 16:32:44 +01:00
// https://bugs.php.net/bug.php?id=64634
$source = fopen ( $originFile , 'r' );
2013-12-16 11:23:38 +00:00
$target = fopen ( $targetFile , 'w' );
2013-04-28 16:32:44 +01:00
stream_copy_to_stream ( $source , $target );
fclose ( $source );
fclose ( $target );
unset ( $source , $target );
if ( ! is_file ( $targetFile )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to copy %s to %s' , $originFile , $targetFile ));
2012-05-18 15:19:51 +01:00
}
2011-03-26 07:37:25 +00:00
}
}
/**
* Creates a directory recursively .
*
2012-05-15 21:19:31 +01:00
* @ param string | array | \Traversable $dirs The directory path
2012-05-18 15:19:51 +01:00
* @ param integer $mode The directory mode
2011-03-26 07:37:25 +00:00
*
2012-06-18 11:41:52 +01:00
* @ throws IOException On any directory creation failure
2011-03-26 07:37:25 +00:00
*/
public function mkdir ( $dirs , $mode = 0777 )
{
foreach ( $this -> toIterator ( $dirs ) as $dir ) {
if ( is_dir ( $dir )) {
continue ;
}
2012-05-18 15:19:51 +01:00
if ( true !== @ mkdir ( $dir , $mode , true )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to create %s' , $dir ));
2012-05-18 15:19:51 +01:00
}
2011-03-26 07:37:25 +00:00
}
}
2012-06-15 17:09:23 +01:00
/**
* Checks the existence of files or directories .
*
* @ param string | array | \Traversable $files A filename , an array of files , or a \Traversable instance to check
*
* @ return Boolean true if the file exists , false otherwise
*/
public function exists ( $files )
{
foreach ( $this -> toIterator ( $files ) as $file ) {
if ( ! file_exists ( $file )) {
return false ;
}
}
return true ;
}
2011-03-26 07:37:25 +00:00
/**
2012-05-18 15:19:51 +01:00
* Sets access and modification time of file .
2011-03-26 07:37:25 +00:00
*
2012-04-07 00:05:37 +01:00
* @ param string | array | \Traversable $files A filename , an array of files , or a \Traversable instance to create
2013-12-27 15:08:19 +00:00
* @ param integer $time The touch time as a Unix timestamp
* @ param integer $atime The access time as a Unix timestamp
2012-05-18 15:19:51 +01:00
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When touch fails
2011-03-26 07:37:25 +00:00
*/
2012-05-18 15:19:51 +01:00
public function touch ( $files , $time = null , $atime = null )
2011-03-26 07:37:25 +00:00
{
foreach ( $this -> toIterator ( $files ) as $file ) {
2013-01-04 11:28:25 +00:00
$touch = $time ? @ touch ( $file , $time , $atime ) : @ touch ( $file );
if ( true !== $touch ) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to touch %s' , $file ));
2012-05-18 15:19:51 +01:00
}
2011-03-26 07:37:25 +00:00
}
}
/**
* Removes files or directories .
*
* @ param string | array | \Traversable $files A filename , an array of files , or a \Traversable instance to remove
2012-05-18 15:19:51 +01:00
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When removal fails
2011-03-26 07:37:25 +00:00
*/
public function remove ( $files )
{
$files = iterator_to_array ( $this -> toIterator ( $files ));
$files = array_reverse ( $files );
foreach ( $files as $file ) {
2012-04-07 00:01:32 +01:00
if ( ! file_exists ( $file ) && ! is_link ( $file )) {
2011-03-26 07:37:25 +00:00
continue ;
}
if ( is_dir ( $file ) && ! is_link ( $file )) {
$this -> remove ( new \FilesystemIterator ( $file ));
2012-05-18 15:19:51 +01:00
if ( true !== @ rmdir ( $file )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to remove directory %s' , $file ));
2012-05-18 15:19:51 +01:00
}
2011-03-26 07:37:25 +00:00
} else {
2012-06-13 15:31:03 +01:00
// https://bugs.php.net/bug.php?id=52176
if ( defined ( 'PHP_WINDOWS_VERSION_MAJOR' ) && is_dir ( $file )) {
2012-05-18 15:19:51 +01:00
if ( true !== @ rmdir ( $file )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to remove file %s' , $file ));
2012-05-18 15:19:51 +01:00
}
2012-06-13 15:31:03 +01:00
} else {
2012-05-18 15:19:51 +01:00
if ( true !== @ unlink ( $file )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to remove file %s' , $file ));
2012-05-18 15:19:51 +01:00
}
2012-06-13 15:31:03 +01:00
}
2011-03-26 07:37:25 +00:00
}
}
}
/**
* Change mode for an array of files or directories .
*
2012-05-18 15:19:51 +01:00
* @ param string | array | \Traversable $files A filename , an array of files , or a \Traversable instance to change mode
* @ param integer $mode The new mode ( octal )
* @ param integer $umask The mode mask ( octal )
* @ param Boolean $recursive Whether change the mod recursively or not
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When the change fail
2011-03-26 07:37:25 +00:00
*/
2012-05-18 15:19:51 +01:00
public function chmod ( $files , $mode , $umask = 0000 , $recursive = false )
2011-03-26 07:37:25 +00:00
{
2011-03-26 11:54:13 +00:00
foreach ( $this -> toIterator ( $files ) as $file ) {
2012-05-18 15:19:51 +01:00
if ( $recursive && is_dir ( $file ) && ! is_link ( $file )) {
$this -> chmod ( new \FilesystemIterator ( $file ), $mode , $umask , true );
}
if ( true !== @ chmod ( $file , $mode & ~ $umask )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to chmod file %s' , $file ));
2012-05-18 15:19:51 +01:00
}
}
}
/**
* Change the owner of an array of files or directories
*
* @ param string | array | \Traversable $files A filename , an array of files , or a \Traversable instance to change owner
* @ param string $user The new owner user name
* @ param Boolean $recursive Whether change the owner recursively or not
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When the change fail
2012-05-18 15:19:51 +01:00
*/
public function chown ( $files , $user , $recursive = false )
{
foreach ( $this -> toIterator ( $files ) as $file ) {
if ( $recursive && is_dir ( $file ) && ! is_link ( $file )) {
$this -> chown ( new \FilesystemIterator ( $file ), $user , true );
}
if ( is_link ( $file ) && function_exists ( 'lchown' )) {
if ( true !== @ lchown ( $file , $user )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to chown file %s' , $file ));
2012-05-18 15:19:51 +01:00
}
} else {
if ( true !== @ chown ( $file , $user )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to chown file %s' , $file ));
2012-05-18 15:19:51 +01:00
}
}
}
}
/**
* Change the group of an array of files or directories
*
* @ param string | array | \Traversable $files A filename , an array of files , or a \Traversable instance to change group
* @ param string $group The group name
* @ param Boolean $recursive Whether change the group recursively or not
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When the change fail
2012-05-18 15:19:51 +01:00
*/
public function chgrp ( $files , $group , $recursive = false )
{
foreach ( $this -> toIterator ( $files ) as $file ) {
if ( $recursive && is_dir ( $file ) && ! is_link ( $file )) {
$this -> chgrp ( new \FilesystemIterator ( $file ), $group , true );
}
if ( is_link ( $file ) && function_exists ( 'lchgrp' )) {
if ( true !== @ lchgrp ( $file , $group )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to chgrp file %s' , $file ));
2012-05-18 15:19:51 +01:00
}
} else {
if ( true !== @ chgrp ( $file , $group )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to chgrp file %s' , $file ));
2012-05-18 15:19:51 +01:00
}
}
2011-03-26 07:37:25 +00:00
}
}
/**
2013-06-03 13:55:30 +01:00
* Renames a file or a directory .
2011-03-26 07:37:25 +00:00
*
2013-06-03 13:55:30 +01:00
* @ param string $origin The origin filename or directory
* @ param string $target The new filename or directory
2013-04-23 21:32:37 +01:00
* @ param Boolean $overwrite Whether to overwrite the target if it already exists
2011-03-26 07:37:25 +00:00
*
2013-06-03 13:55:30 +01:00
* @ throws IOException When target file or directory already exists
2012-06-18 11:41:52 +01:00
* @ throws IOException When origin cannot be renamed
2011-03-26 07:37:25 +00:00
*/
2013-04-23 21:32:37 +01:00
public function rename ( $origin , $target , $overwrite = false )
2011-03-26 07:37:25 +00:00
{
// we check that target does not exist
2013-04-23 21:32:37 +01:00
if ( ! $overwrite && is_readable ( $target )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Cannot rename because the target "%s" already exist.' , $target ));
2011-03-26 07:37:25 +00:00
}
2012-05-18 15:19:51 +01:00
if ( true !== @ rename ( $origin , $target )) {
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Cannot rename "%s" to "%s".' , $origin , $target ));
2012-04-09 19:56:50 +01:00
}
2011-03-26 07:37:25 +00:00
}
/**
* Creates a symbolic link or copy a directory .
*
2011-04-23 16:05:44 +01:00
* @ param string $originDir The origin directory path
* @ param string $targetDir The symbolic link name
2011-08-26 09:34:23 +01:00
* @ param Boolean $copyOnWindows Whether to copy files if on Windows
2012-05-18 15:19:51 +01:00
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When symlink fails
2011-03-26 07:37:25 +00:00
*/
public function symlink ( $originDir , $targetDir , $copyOnWindows = false )
{
if ( ! function_exists ( 'symlink' ) && $copyOnWindows ) {
$this -> mirror ( $originDir , $targetDir );
return ;
}
2012-04-19 18:29:30 +01:00
$this -> mkdir ( dirname ( $targetDir ));
2011-03-26 07:37:25 +00:00
$ok = false ;
if ( is_link ( $targetDir )) {
if ( readlink ( $targetDir ) != $originDir ) {
2012-05-18 15:19:51 +01:00
$this -> remove ( $targetDir );
2011-03-26 07:37:25 +00:00
} else {
$ok = true ;
}
}
if ( ! $ok ) {
2012-05-18 15:19:51 +01:00
if ( true !== @ symlink ( $originDir , $targetDir )) {
2012-06-18 09:34:56 +01:00
$report = error_get_last ();
if ( is_array ( $report )) {
if ( defined ( 'PHP_WINDOWS_VERSION_MAJOR' ) && false !== strpos ( $report [ 'message' ], 'error code(1314)' )) {
throw new IOException ( 'Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?' );
}
}
2012-06-18 11:41:52 +01:00
throw new IOException ( sprintf ( 'Failed to create symbolic link from %s to %s' , $originDir , $targetDir ));
2012-05-18 15:19:51 +01:00
}
2011-03-26 07:37:25 +00:00
}
}
2011-09-28 16:31:08 +01:00
/**
* Given an existing path , convert it to a path relative to a given starting path
*
2012-04-07 00:05:37 +01:00
* @ param string $endPath Absolute path of target
* @ param string $startPath Absolute path where traversal begins
2011-09-28 16:31:08 +01:00
*
* @ return string Path of target relative to starting path
*/
public function makePathRelative ( $endPath , $startPath )
{
2013-12-27 15:08:19 +00:00
// Normalize separators on Windows
2012-04-19 11:37:15 +01:00
if ( defined ( 'PHP_WINDOWS_VERSION_MAJOR' )) {
$endPath = strtr ( $endPath , '\\' , '/' );
$startPath = strtr ( $startPath , '\\' , '/' );
}
2012-07-10 23:22:37 +01:00
// Split the paths into arrays
$startPathArr = explode ( '/' , trim ( $startPath , '/' ));
$endPathArr = explode ( '/' , trim ( $endPath , '/' ));
// Find for which directory the common path stops
$index = 0 ;
while ( isset ( $startPathArr [ $index ]) && isset ( $endPathArr [ $index ]) && $startPathArr [ $index ] === $endPathArr [ $index ]) {
$index ++ ;
2011-09-28 16:31:08 +01:00
}
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
2012-07-10 23:22:37 +01:00
$depth = count ( $startPathArr ) - $index ;
2011-09-28 16:31:08 +01:00
// Repeated "../" for each level need to reach the common path
$traverser = str_repeat ( '../' , $depth );
2012-07-10 23:22:37 +01:00
$endPathRemainder = implode ( '/' , array_slice ( $endPathArr , $index ));
2011-09-28 16:31:08 +01:00
// Construct $endPath from traversing to the common path, then to the remaining $endPath
2013-04-02 10:39:57 +01:00
$relativePath = $traverser . ( strlen ( $endPathRemainder ) > 0 ? $endPathRemainder . '/' : '' );
2012-07-10 23:22:37 +01:00
return ( strlen ( $relativePath ) === 0 ) ? './' : $relativePath ;
2011-09-28 16:31:08 +01:00
}
2011-03-26 07:37:25 +00:00
/**
* Mirrors a directory to another .
*
2012-05-15 21:19:31 +01:00
* @ 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
2011-08-26 09:47:18 +01:00
* 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 ())
2012-12-11 12:57:29 +00:00
* - $options [ 'delete' ] Whether to delete files that are not in the source directory ( defaults to false )
2011-03-26 07:37:25 +00:00
*
2012-06-18 11:41:52 +01:00
* @ throws IOException When file type is unknown
2011-03-26 07:37:25 +00:00
*/
public function mirror ( $originDir , $targetDir , \Traversable $iterator = null , $options = array ())
{
2012-11-14 21:58:01 +00:00
$targetDir = rtrim ( $targetDir , '/\\' );
$originDir = rtrim ( $originDir , '/\\' );
// Iterate in destination folder to remove obsolete entries
if ( $this -> exists ( $targetDir ) && isset ( $options [ 'delete' ]) && $options [ 'delete' ]) {
$deleteIterator = $iterator ;
if ( null === $deleteIterator ) {
$flags = \FilesystemIterator :: SKIP_DOTS ;
$deleteIterator = new \RecursiveIteratorIterator ( new \RecursiveDirectoryIterator ( $targetDir , $flags ), \RecursiveIteratorIterator :: CHILD_FIRST );
}
foreach ( $deleteIterator as $file ) {
$origin = str_replace ( $targetDir , $originDir , $file -> getPathname ());
if ( ! $this -> exists ( $origin )) {
$this -> remove ( $file );
}
}
}
2011-08-26 09:47:18 +01:00
$copyOnWindows = false ;
if ( isset ( $options [ 'copy_on_windows' ]) && ! function_exists ( 'symlink' )) {
$copyOnWindows = $options [ 'copy_on_windows' ];
}
2011-03-26 07:37:25 +00:00
if ( null === $iterator ) {
2011-08-26 09:47:18 +01:00
$flags = $copyOnWindows ? \FilesystemIterator :: SKIP_DOTS | \FilesystemIterator :: FOLLOW_SYMLINKS : \FilesystemIterator :: SKIP_DOTS ;
$iterator = new \RecursiveIteratorIterator ( new \RecursiveDirectoryIterator ( $originDir , $flags ), \RecursiveIteratorIterator :: SELF_FIRST );
2011-03-26 07:37:25 +00:00
}
foreach ( $iterator as $file ) {
2012-04-09 15:14:36 +01:00
$target = str_replace ( $originDir , $targetDir , $file -> getPathname ());
2011-03-26 07:37:25 +00:00
2012-11-19 21:14:30 +00:00
if ( $copyOnWindows ) {
if ( is_link ( $file ) || is_file ( $file )) {
$this -> copy ( $file , $target , isset ( $options [ 'override' ]) ? $options [ 'override' ] : false );
2012-12-11 10:40:22 +00:00
} elseif ( is_dir ( $file )) {
2012-11-19 21:14:30 +00:00
$this -> mkdir ( $target );
} else {
throw new IOException ( sprintf ( 'Unable to guess "%s" file type.' , $file ));
}
2011-03-26 07:37:25 +00:00
} else {
2012-11-19 21:14:30 +00:00
if ( is_link ( $file )) {
2013-12-27 13:13:43 +00:00
$this -> symlink ( $file -> getLinkTarget (), $target );
2012-12-11 10:40:22 +00:00
} elseif ( is_dir ( $file )) {
2012-11-19 21:14:30 +00:00
$this -> mkdir ( $target );
2012-12-11 10:40:22 +00:00
} elseif ( is_file ( $file )) {
2012-11-19 21:14:30 +00:00
$this -> copy ( $file , $target , isset ( $options [ 'override' ]) ? $options [ 'override' ] : false );
} else {
throw new IOException ( sprintf ( 'Unable to guess "%s" file type.' , $file ));
}
2011-03-26 07:37:25 +00:00
}
}
}
2011-05-31 10:04:23 +01:00
/**
* Returns whether the file path is an absolute path .
*
* @ param string $file A file path
*
* @ return Boolean
*/
public function isAbsolutePath ( $file )
{
2012-06-13 13:39:17 +01:00
if ( strspn ( $file , '/\\' , 0 , 1 )
2011-05-31 10:04:23 +01:00
|| ( strlen ( $file ) > 3 && ctype_alpha ( $file [ 0 ])
2012-06-13 13:39:17 +01:00
&& substr ( $file , 1 , 1 ) === ':'
&& ( strspn ( $file , '/\\' , 2 , 1 ))
2011-05-31 10:04:23 +01:00
)
2011-12-08 12:44:49 +00:00
|| null !== parse_url ( $file , PHP_URL_SCHEME )
2011-05-31 10:04:23 +01:00
) {
return true ;
}
return false ;
}
2012-04-06 17:48:01 +01:00
/**
* @ param mixed $files
*
* @ return \Traversable
*/
2011-03-26 07:37:25 +00:00
private function toIterator ( $files )
{
if ( ! $files instanceof \Traversable ) {
$files = new \ArrayObject ( is_array ( $files ) ? $files : array ( $files ));
}
return $files ;
}
2013-04-21 08:24:34 +01:00
/**
* Atomically dumps content into a file .
*
* @ param string $filename The file to be written to .
* @ param string $content The data to write into the file .
* @ param integer $mode The file mode ( octal ) .
* @ throws IOException If the file cannot be written to .
*/
public function dumpFile ( $filename , $content , $mode = 0666 )
{
$dir = dirname ( $filename );
if ( ! is_dir ( $dir )) {
$this -> mkdir ( $dir );
} elseif ( ! is_writable ( $dir )) {
throw new IOException ( sprintf ( 'Unable to write in the %s directory\n' , $dir ));
}
$tmpFile = tempnam ( $dir , basename ( $filename ));
if ( false === @ file_put_contents ( $tmpFile , $content )) {
throw new IOException ( sprintf ( 'Failed to write file "%s".' , $filename ));
}
2013-04-23 21:32:37 +01:00
$this -> rename ( $tmpFile , $filename , true );
2013-04-21 08:24:34 +01:00
$this -> chmod ( $filename , $mode );
}
2011-03-26 07:37:25 +00:00
}