2011-09-28 22:48:17 +00:00
< ? php
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2012-02-09 17:45:28 +00:00
namespace Composer\Util ;
2012-01-03 16:03:17 +00:00
2012-11-19 09:29:32 +00:00
use RecursiveDirectoryIterator ;
use RecursiveIteratorIterator ;
2011-09-28 22:48:17 +00:00
/**
* @ author Jordi Boggiano < j . boggiano @ seld . be >
2012-07-23 15:30:11 +00:00
* @ author Johannes M . Schmitt < schmittjoh @ gmail . com >
2011-09-28 22:48:17 +00:00
*/
class Filesystem
{
2012-08-10 08:14:02 +00:00
private $processExecutor ;
public function __construct ( ProcessExecutor $executor = null )
{
$this -> processExecutor = $executor ? : new ProcessExecutor ();
}
2012-09-07 18:25:07 +00:00
public function remove ( $file )
{
if ( is_dir ( $file )) {
return $this -> removeDirectory ( $file );
}
if ( file_exists ( $file )) {
return unlink ( $file );
}
return false ;
}
2013-09-26 09:38:33 +00:00
/**
* Checks if a directory is empty
*
2013-10-11 23:12:02 +00:00
* @ param string $dir
2013-09-26 09:38:33 +00:00
* @ return bool
*/
public function isDirEmpty ( $dir )
{
$dir = rtrim ( $dir , '/\\' );
return count ( glob ( $dir . '/*' ) ? : array ()) === 0 && count ( glob ( $dir . '/.*' ) ? : array ()) === 2 ;
}
2012-11-19 09:29:32 +00:00
/**
* Recursively remove a directory
*
* Uses the process component if proc_open is enabled on the PHP
* installation .
*
2013-01-05 19:01:58 +00:00
* @ param string $directory
2012-11-19 09:29:32 +00:00
* @ return bool
*/
2011-12-03 14:39:06 +00:00
public function removeDirectory ( $directory )
2011-09-28 22:48:17 +00:00
{
2012-03-07 23:12:38 +00:00
if ( ! is_dir ( $directory )) {
return true ;
}
2013-11-14 20:21:08 +00:00
if ( preg_match ( '{^(?:[a-z]:)?[/\\\\]+$}i' , $directory )) {
throw new \RuntimeException ( 'Aborting an attempted deletion of ' . $directory . ', this was probably not intended, if it is a real use case please report it.' );
}
2012-11-19 09:29:32 +00:00
if ( ! function_exists ( 'proc_open' )) {
return $this -> removeDirectoryPhp ( $directory );
}
2011-09-28 22:48:17 +00:00
if ( defined ( 'PHP_WINDOWS_VERSION_BUILD' )) {
2012-01-18 07:56:35 +00:00
$cmd = sprintf ( 'rmdir /S /Q %s' , escapeshellarg ( realpath ( $directory )));
2011-09-28 22:48:17 +00:00
} else {
2012-01-18 07:56:35 +00:00
$cmd = sprintf ( 'rm -rf %s' , escapeshellarg ( $directory ));
2011-09-28 22:48:17 +00:00
}
2012-01-18 07:56:35 +00:00
2013-02-11 21:52:06 +00:00
$result = $this -> getProcess () -> execute ( $cmd , $output ) === 0 ;
2012-03-07 23:12:38 +00:00
2014-05-12 21:12:57 +00:00
if ( $result ) {
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache ();
2012-03-07 23:12:38 +00:00
2014-05-12 21:12:57 +00:00
return ! is_dir ( $directory );
}
return $this -> removeDirectoryPhp ( $directory );
2011-09-28 22:48:17 +00:00
}
2011-12-03 14:39:06 +00:00
2012-11-19 09:29:32 +00:00
/**
* Recursively delete directory using PHP iterators .
*
* Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
* before directories , creating a single non - recursive loop
* to delete files / directories in the correct order .
*
2013-01-05 19:01:58 +00:00
* @ param string $directory
2012-11-19 09:29:32 +00:00
* @ return bool
*/
public function removeDirectoryPhp ( $directory )
{
2012-11-19 10:21:41 +00:00
$it = new RecursiveDirectoryIterator ( $directory , RecursiveDirectoryIterator :: SKIP_DOTS );
2012-11-19 09:29:32 +00:00
$ri = new RecursiveIteratorIterator ( $it , RecursiveIteratorIterator :: CHILD_FIRST );
foreach ( $ri as $file ) {
if ( $file -> isDir ()) {
rmdir ( $file -> getPathname ());
} else {
unlink ( $file -> getPathname ());
}
}
return rmdir ( $directory );
}
2011-12-03 14:39:06 +00:00
public function ensureDirectoryExists ( $directory )
{
if ( ! is_dir ( $directory )) {
if ( file_exists ( $directory )) {
throw new \RuntimeException (
$directory . ' exists and is not a directory.'
);
}
2013-02-20 13:51:15 +00:00
if ( !@ mkdir ( $directory , 0777 , true )) {
2011-12-03 14:39:06 +00:00
throw new \RuntimeException (
$directory . ' does not exist and could not be created.'
);
}
}
}
2011-12-03 19:44:00 +00:00
2012-11-19 10:21:41 +00:00
/**
* Copy then delete is a non - atomic version of { @ link rename } .
*
2013-01-26 18:43:01 +00:00
* Some systems can 't rename and also don' t have proc_open ,
2012-11-19 10:21:41 +00:00
* which requires this solution .
*
* @ param string $source
* @ param string $target
*/
public function copyThenRemove ( $source , $target )
{
$it = new RecursiveDirectoryIterator ( $source , RecursiveDirectoryIterator :: SKIP_DOTS );
$ri = new RecursiveIteratorIterator ( $it , RecursiveIteratorIterator :: SELF_FIRST );
2013-09-20 03:02:06 +00:00
$this -> ensureDirectoryExists ( $target );
2012-11-19 10:21:41 +00:00
foreach ( $ri as $file ) {
$targetPath = $target . DIRECTORY_SEPARATOR . $ri -> getSubPathName ();
if ( $file -> isDir ()) {
2013-09-20 03:02:06 +00:00
$this -> ensureDirectoryExists ( $targetPath );
2012-11-19 10:21:41 +00:00
} else {
copy ( $file -> getPathname (), $targetPath );
}
}
$this -> removeDirectoryPhp ( $source );
}
2012-07-23 15:30:11 +00:00
public function rename ( $source , $target )
{
2012-09-07 18:25:07 +00:00
if ( true === @ rename ( $source , $target )) {
2012-07-23 15:30:11 +00:00
return ;
}
2012-11-19 12:51:24 +00:00
if ( ! function_exists ( 'proc_open' )) {
return $this -> copyThenRemove ( $source , $target );
}
2012-11-19 10:21:41 +00:00
2012-11-19 12:51:24 +00:00
if ( defined ( 'PHP_WINDOWS_VERSION_BUILD' )) {
2012-09-07 18:25:07 +00:00
// Try to copy & delete - this is a workaround for random "Access denied" errors.
$command = sprintf ( 'xcopy %s %s /E /I /Q' , escapeshellarg ( $source ), escapeshellarg ( $target ));
2013-03-21 14:21:54 +00:00
$result = $this -> processExecutor -> execute ( $command , $output );
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache ();
if ( 0 === $result ) {
2012-09-07 18:25:07 +00:00
$this -> remove ( $source );
return ;
}
} else {
// We do not use PHP's "rename" function here since it does not support
// the case where $source, and $target are located on different partitions.
$command = sprintf ( 'mv %s %s' , escapeshellarg ( $source ), escapeshellarg ( $target ));
2013-03-21 14:21:54 +00:00
$result = $this -> processExecutor -> execute ( $command , $output );
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache ();
if ( 0 === $result ) {
2012-08-10 08:14:02 +00:00
return ;
}
2012-07-23 15:30:11 +00:00
}
2012-09-07 18:25:07 +00:00
2013-07-25 16:02:01 +00:00
return $this -> copyThenRemove ( $source , $target );
2012-07-23 15:30:11 +00:00
}
2011-12-03 19:44:00 +00:00
/**
* Returns the shortest path from $from to $to
*
2013-06-13 11:28:24 +00:00
* @ param string $from
* @ param string $to
* @ param bool $directories if true , the source / target are considered to be directories
2013-06-13 00:05:44 +00:00
* @ throws \InvalidArgumentException
2011-12-03 19:44:00 +00:00
* @ return string
*/
2012-03-23 11:49:29 +00:00
public function findShortestPath ( $from , $to , $directories = false )
2011-12-03 19:44:00 +00:00
{
if ( ! $this -> isAbsolutePath ( $from ) || ! $this -> isAbsolutePath ( $to )) {
2012-07-23 19:58:47 +00:00
throw new \InvalidArgumentException ( sprintf ( '$from (%s) and $to (%s) must be absolute paths.' , $from , $to ));
2011-12-03 19:44:00 +00:00
}
2013-03-13 21:13:32 +00:00
$from = lcfirst ( $this -> normalizePath ( $from ));
$to = lcfirst ( $this -> normalizePath ( $to ));
2012-03-10 01:14:40 +00:00
2012-03-23 11:49:29 +00:00
if ( $directories ) {
2012-03-22 09:11:48 +00:00
$from .= '/dummy_file' ;
}
2011-12-03 19:44:00 +00:00
if ( dirname ( $from ) === dirname ( $to )) {
return './' . basename ( $to );
}
2013-09-26 12:34:41 +00:00
$commonPath = $to ;
2013-10-28 10:01:17 +00:00
while ( strpos ( $from . '/' , $commonPath . '/' ) !== 0 && '/' !== $commonPath && ! preg_match ( '{^[a-z]:/?$}i' , $commonPath )) {
$commonPath = strtr ( dirname ( $commonPath ), '\\' , '/' );
2011-12-03 19:44:00 +00:00
}
2013-10-28 10:01:17 +00:00
if ( 0 !== strpos ( $from , $commonPath ) || '/' === $commonPath ) {
2011-12-03 19:44:00 +00:00
return $to ;
}
$commonPath = rtrim ( $commonPath , '/' ) . '/' ;
$sourcePathDepth = substr_count ( substr ( $from , strlen ( $commonPath )), '/' );
$commonPathCode = str_repeat ( '../' , $sourcePathDepth );
2012-05-22 10:07:08 +00:00
2011-12-04 21:40:30 +00:00
return ( $commonPathCode . substr ( $to , strlen ( $commonPath ))) ? : './' ;
2011-12-03 19:44:00 +00:00
}
/**
* Returns PHP code that , when executed in $from , will return the path to $to
*
2013-06-13 11:28:24 +00:00
* @ param string $from
* @ param string $to
* @ param bool $directories if true , the source / target are considered to be directories
2013-06-13 00:05:44 +00:00
* @ throws \InvalidArgumentException
2011-12-03 19:44:00 +00:00
* @ return string
*/
2011-12-03 22:21:10 +00:00
public function findShortestPathCode ( $from , $to , $directories = false )
2011-12-03 19:44:00 +00:00
{
if ( ! $this -> isAbsolutePath ( $from ) || ! $this -> isAbsolutePath ( $to )) {
2012-07-23 19:58:47 +00:00
throw new \InvalidArgumentException ( sprintf ( '$from (%s) and $to (%s) must be absolute paths.' , $from , $to ));
2011-12-03 19:44:00 +00:00
}
2013-03-13 21:13:32 +00:00
$from = lcfirst ( $this -> normalizePath ( $from ));
$to = lcfirst ( $this -> normalizePath ( $to ));
2012-03-10 01:14:40 +00:00
2011-12-03 19:44:00 +00:00
if ( $from === $to ) {
2011-12-03 22:21:10 +00:00
return $directories ? '__DIR__' : '__FILE__' ;
2011-12-03 19:44:00 +00:00
}
2013-09-26 12:34:41 +00:00
$commonPath = $to ;
2013-10-17 09:10:16 +00:00
while ( strpos ( $from . '/' , $commonPath . '/' ) !== 0 && '/' !== $commonPath && ! preg_match ( '{^[a-z]:/?$}i' , $commonPath ) && '.' !== $commonPath ) {
2013-10-28 10:01:17 +00:00
$commonPath = strtr ( dirname ( $commonPath ), '\\' , '/' );
2011-12-03 19:44:00 +00:00
}
2013-10-17 09:10:16 +00:00
if ( 0 !== strpos ( $from , $commonPath ) || '/' === $commonPath || '.' === $commonPath ) {
2011-12-03 19:44:00 +00:00
return var_export ( $to , true );
}
$commonPath = rtrim ( $commonPath , '/' ) . '/' ;
2011-12-04 21:40:30 +00:00
if ( strpos ( $to , $from . '/' ) === 0 ) {
2011-12-03 22:21:10 +00:00
return '__DIR__ . ' . var_export ( substr ( $to , strlen ( $from )), true );
}
$sourcePathDepth = substr_count ( substr ( $from , strlen ( $commonPath )), '/' ) + $directories ;
2011-12-03 19:44:00 +00:00
$commonPathCode = str_repeat ( 'dirname(' , $sourcePathDepth ) . '__DIR__' . str_repeat ( ')' , $sourcePathDepth );
2011-12-04 21:40:30 +00:00
$relTarget = substr ( $to , strlen ( $commonPath ));
2012-05-22 10:07:08 +00:00
2011-12-04 21:40:30 +00:00
return $commonPathCode . ( strlen ( $relTarget ) ? '.' . var_export ( '/' . $relTarget , true ) : '' );
2011-12-03 19:44:00 +00:00
}
/**
* Checks if the given path is absolute
*
2012-06-23 09:58:18 +00:00
* @ param string $path
* @ return bool
2011-12-03 19:44:00 +00:00
*/
public function isAbsolutePath ( $path )
{
return substr ( $path , 0 , 1 ) === '/' || substr ( $path , 1 , 1 ) === ':' ;
}
2012-01-18 07:56:35 +00:00
2012-12-16 19:04:39 +00:00
/**
* Returns size of a file or directory specified by path . If a directory is
* given , it ' s size will be computed recursively .
*
2013-06-13 11:28:24 +00:00
* @ param string $path Path to the file or directory
2013-06-13 00:05:44 +00:00
* @ throws \RuntimeException
2012-12-16 19:04:39 +00:00
* @ return int
*/
public function size ( $path )
{
if ( ! file_exists ( $path )) {
throw new \RuntimeException ( " $path does not exist. " );
}
if ( is_dir ( $path )) {
return $this -> directorySize ( $path );
}
2013-01-05 19:01:58 +00:00
2012-12-16 19:04:39 +00:00
return filesize ( $path );
}
2013-03-13 21:13:32 +00:00
/**
* Normalize a path . This replaces backslashes with slashes , removes ending
* slash and collapses redundant separators and up - level references .
*
2013-06-13 11:28:24 +00:00
* @ param string $path Path to the file or directory
2013-03-13 21:13:32 +00:00
* @ return string
*/
public function normalizePath ( $path )
{
$parts = array ();
$path = strtr ( $path , '\\' , '/' );
$prefix = '' ;
2013-04-05 04:41:14 +00:00
$absolute = false ;
2013-03-13 21:13:32 +00:00
2013-04-05 04:41:14 +00:00
if ( preg_match ( '{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i' , $path , $match )) {
2013-03-13 21:13:32 +00:00
$prefix = $match [ 1 ];
$path = substr ( $path , strlen ( $prefix ));
}
2013-04-05 04:41:14 +00:00
if ( substr ( $path , 0 , 1 ) === '/' ) {
$absolute = true ;
$path = substr ( $path , 1 );
}
$up = false ;
2013-03-13 21:13:32 +00:00
foreach ( explode ( '/' , $path ) as $chunk ) {
2013-04-05 04:41:14 +00:00
if ( '..' === $chunk && ( $absolute || $up )) {
2013-03-13 21:13:32 +00:00
array_pop ( $parts );
2013-04-05 04:41:14 +00:00
$up = ! ( empty ( $parts ) || '..' === end ( $parts ));
2013-03-13 21:13:32 +00:00
} elseif ( '.' !== $chunk && '' !== $chunk ) {
$parts [] = $chunk ;
2013-04-05 04:41:14 +00:00
$up = '..' !== $chunk ;
2013-03-13 21:13:32 +00:00
}
}
2013-04-05 04:41:14 +00:00
return $prefix . ( $absolute ? '/' : '' ) . implode ( '/' , $parts );
2013-03-13 21:13:32 +00:00
}
2012-12-16 19:04:39 +00:00
protected function directorySize ( $directory )
{
$it = new RecursiveDirectoryIterator ( $directory , RecursiveDirectoryIterator :: SKIP_DOTS );
$ri = new RecursiveIteratorIterator ( $it , RecursiveIteratorIterator :: CHILD_FIRST );
$size = 0 ;
foreach ( $ri as $file ) {
if ( $file -> isFile ()) {
$size += $file -> getSize ();
}
}
2013-01-05 19:01:58 +00:00
2012-12-16 19:04:39 +00:00
return $size ;
}
2012-01-18 07:56:35 +00:00
protected function getProcess ()
{
return new ProcessExecutor ;
}
2012-03-22 09:11:48 +00:00
}