FilesystemStack.php 4.8 KB
Newer Older
Ketan's avatar
Ketan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
<?php
namespace Robo\Task\Filesystem;

use Robo\Task\StackBasedTask;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
use Symfony\Component\Filesystem\Exception\IOException;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;

/**
 * Wrapper for [Symfony Filesystem](http://symfony.com/doc/current/components/filesystem.html) Component.
 * Comands are executed in stack and can be stopped on first fail with `stopOnFail` option.
 *
 * ``` php
 * <?php
 * $this->taskFilesystemStack()
 *      ->mkdir('logs')
 *      ->touch('logs/.gitignore')
 *      ->chgrp('www', 'www-data')
 *      ->symlink('/var/log/nginx/error.log', 'logs/error.log')
 *      ->run();
 *
 * // one line
 * $this->_touch('.gitignore');
 * $this->_mkdir('logs');
 *
 * ?>
 * ```
 *
 * @method $this mkdir(string|array|\Traversable $dir, int $mode = 0777)
 * @method $this touch(string|array|\Traversable $file, int $time = null, int $atime = null)
 * @method $this copy(string $from, string $to, bool $force = false)
 * @method $this chmod(string|array|\Traversable $file, int $permissions, int $umask = 0000, bool $recursive = false)
 * @method $this chgrp(string|array|\Traversable $file, string $group, bool $recursive = false)
 * @method $this chown(string|array|\Traversable $file, string $user, bool $recursive = false)
 * @method $this remove(string|array|\Traversable $file)
 * @method $this rename(string $from, string $to, bool $force = false)
 * @method $this symlink(string $from, string $to, bool $copyOnWindows = false)
 * @method $this mirror(string $from, string $to, \Traversable $iterator = null, array $options = [])
 */
class FilesystemStack extends StackBasedTask implements BuilderAwareInterface
{
    use BuilderAwareTrait;

    /**
     * @var \Symfony\Component\Filesystem\Filesystem
     */
    protected $fs;

    public function __construct()
    {
        $this->fs = new sfFilesystem();
    }

    /**
     * @return \Symfony\Component\Filesystem\Filesystem
     */
    protected function getDelegate()
    {
        return $this->fs;
    }

    /**
     * @param string $from
     * @param string $to
     * @param bool $force
     */
    protected function _copy($from, $to, $force = false)
    {
        $this->fs->copy($from, $to, $force);
    }

    /**
     * @param string|string[]|\Traversable $file
     * @param int $permissions
     * @param int $umask
     * @param bool $recursive
     */
    protected function _chmod($file, $permissions, $umask = 0000, $recursive = false)
    {
        $this->fs->chmod($file, $permissions, $umask, $recursive);
    }

    /**
     * @param string|string[]|\Traversable $file
     * @param string $group
     * @param bool $recursive
     */
    protected function _chgrp($file, $group, $recursive = null)
    {
        $this->fs->chgrp($file, $group, $recursive);
    }

    /**
     * @param string|string[]|\Traversable $file
     * @param string $user
     * @param bool $recursive
     */
    protected function _chown($file, $user, $recursive = null)
    {
        $this->fs->chown($file, $user, $recursive);
    }

    /**
     * @param string $origin
     * @param string $target
     * @param bool $overwrite
     *
     * @return null|true|\Robo\Result
     */
    protected function _rename($origin, $target, $overwrite = false)
    {
        // we check that target does not exist
        if ((!$overwrite && is_readable($target)) || (file_exists($target) && !is_writable($target))) {
            throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
        }

        // Due to a bug (limitation) in PHP, cross-volume renames do not work.
        // See: https://bugs.php.net/bug.php?id=54097
        if (true !== @rename($origin, $target)) {
            return $this->crossVolumeRename($origin, $target);
        }
        return true;
    }

    /**
     * @param string $origin
     * @param string $target
     *
     * @return null|\Robo\Result
     */
    protected function crossVolumeRename($origin, $target)
    {
        // First step is to try to get rid of the target. If there
        // is a single, deletable file, then we will just unlink it.
        if (is_file($target)) {
            unlink($target);
        }
        // If the target still exists, we will try to delete it.
        // TODO: Note that if this fails partway through, then we cannot
        // adequately rollback.  Perhaps we need to preflight the operation
        // and determine if everything inside of $target is writable.
        if (file_exists($target)) {
            $this->fs->remove($target);
        }

        /** @var \Robo\Result $result */
        $result = $this->collectionBuilder()->taskCopyDir([$origin => $target])->run();
        if (!$result->wasSuccessful()) {
            return $result;
        }
        $this->fs->remove($origin);
    }
}