12

When I try this:

$phar = new PharData('./phar.tar', 0, null, Phar::TAR);
$phar->addEmptyDir('test');

file_put_contents('phar://./phar.tar/test/foo.txt', 'bar');

I get the following error:

Warning: file_put_contents(phar://./phar.tar/test/foo.txt): failed to open stream: Cannot create phar './phar.tar', file extension (or combination) not recognised or the directory does not exist

I'm able to use file_get_contents but shouldn't file_put_contents work too?


To make things even weirder, I tried the idea @hakre suggested in his comment and it works!

$phar = new PharData('./phar.tar', 0, null, Phar::TAR);
$phar->addEmptyDir('test');

if (file_exists('./phar.phar') !== true)
{
    symlink('./phar.tar', './phar.phar');
}

file_put_contents('phar://./phar.phar/test/foo.txt', 'bar');
var_dump(file_get_contents('phar://./phar.tar/test/foo.txt')); // string(3) "bar"
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • Might it be a _permission_ problem? – BlitZ May 22 '13 at 06:57
  • can you add a link to the `phar.tar` you are trying to access so that i can try an replicate the issue – Baba May 22 '13 at 09:45
  • @Baba, just use this code. It's all you need. – sectus May 22 '13 at 13:17
  • @CORRUPT: I was running the code on Windows at the time, so I doubt it. – Alix Axel May 22 '13 at 13:33
  • @Baba: I'm creating the Tar file on-the-fly with the PharData constructor. – Alix Axel May 22 '13 at 13:34
  • @sectus: Thanks for the green smoke. =) – Alix Axel May 22 '13 at 13:36
  • @AlixAxel i was able to replicate the issue .. its pretty simple you can not use `file_put_contents` directly because the file format is `Phar::TAR` you need to add the valid `context` .. it works file with standard phar file that has no compression or format specified – Baba May 22 '13 at 16:12
  • @Baba: But the `phar://` is a registered built-in context, and the PharData documentation states that the appropriate compressing algorithm is used depending on the extension. Also, it works wonderfully if you do `file_get_contents('phar://./phar.tar/test/foo.txt')`. It could be because [the `Tar` format compresses the whole archive instead of individual files](http://php.net/manual/en/phar.fileformat.comparison.php) but I get exactly the same error if I use `Phar::ZIP` and a `.zip` extension. – Alix Axel May 22 '13 at 17:21
  • @AlixAxel you are right ... I would do some investigations and get back to you ..... – Baba May 22 '13 at 17:33
  • are you trying to use `file_put_contents` because of flexibility ? I think i have an idea if not .. have not been able to make it work with `Phar::TAR` without `content` using `stream_context_create` ... I think it make a lot of sense why it should work that way ... – Baba May 23 '13 at 16:44
  • @Baba: I was trying to use `file_put_contents` because I wasn't passing the PharData object around, I can't remember the exact reason why anymore. Please enlight me though, I find it odd that it doesn't work just with the stream wrapper. – Alix Axel May 23 '13 at 20:40
  • @sectus Have you tried creating a symlink named `test.phar` linking to `phar.tar` and then using the `.phar` filename instead? – hakre May 24 '13 at 10:59
  • @hakre: No, I haven't. But that's a good idea. I'll do that (just for the sake of figuring this out) and I'll post the results here. – Alix Axel May 24 '13 at 17:47
  • 1
    @hakre: Tried what you said and the symlink idea works! I must say, I really wasn't expecting that. Sorry it took me a while to get back, I've been a bit busy. – Alix Axel May 27 '13 at 15:59
  • It does not depend on one day I'd say so thanks a lot for the additional feedback. – hakre May 28 '13 at 07:04

3 Answers3

5

Introduction

I think this basically has to do with the fact that you are working with Phar::TAR files because

file_put_contents("phar://./test.tar/foo.txt", "hello world");
                                  ^
                                  |+---- Note this tar

Returns

Warning: file_put_contents(phar://./test.tar/foo.txt): failed to open stream: Cannot create phar './test.tar', file extension (or combination) not recognized or the directory does not exist

While

file_put_contents("phar://./test.phar/foo.txt", "hello world"); // Works file
                                  ^
                                  |+---- Note This phar

Returns

//No Errors

Know Issue

If you look at a detail example in the PHP doc , this has been a known issue for the past 2 years and the example given are as follows

Example

$p = new PharData(dirname(__FILE__) . '/phartest.zip', 0, 'phartest', Phar::ZIP);
$p->addFromString('testfile.txt', 'this is just some test text');

// This works
echo file_get_contents('phar://phartest.zip/testfile.txt');

// This Fails
file_put_contents('phar://phartest.zip/testfile.txt', 'Thist is text for testfile.txt');

$context = stream_context_create(array(
        'phar' => array(
                'compress' => Phar::ZIP
        )
));

// This Fails
file_put_contents('phar://phartest.zip/testfile.txt', 'Thist is text for testfile.txt', 0, $context);

// This works but only with 'r' readonly mode.
$f = fopen('phar://C:\\Inetpub\\wwwroot\\PACT\\test\\phartest.zip\\testfile.txt', 'r');

Working Alternative

Don't use .tar or .zip has your file extension but set compression as demonstrated in the PHP DOC

What do i mean ?

$context = stream_context_create(array(
        'phar' => array(
                'compress' => Phar::GZ //set compression
        )
));

file_put_contents('phar://sample.phar/somefile.txt', "Sample Data", 0, $context);
                                   ^
                                   |= Use phar not tar

Advice

I really think you should stick with PharData is realizable and proven to work. The only way to make

I must use file_put_contents

Then write your own wrapper and register it with stream_wrapper_register here is a simple Prof of Concept

stream_wrapper_register("alixphar", "AlixPhar");
file_put_contents('alixphar://phar.tar/test/foo.txt', 'hey'); 
                         ^
                         |+-- Write with alixphar

echo file_get_contents('alixphar://phar.tar/test/foo.txt'),PHP_EOL;
echo file_get_contents('phar://phar.tar/test/foo.txt'),PHP_EOL;

Output

hey     <----- alixphar
hey     <----- phar

Note: This class should not be used in production ... It just an Example and would fail some test cases

class AlixPhar {
    private $pos;
    private $stream;
    private $types;
    private $dir;
    private $pharContainer, $pharType, $pharFile, $pharDir = null;
    private $fp;

    function __construct() {
        $this->types = array(
                "tar" => Phar::TAR,
                "zip" => Phar::ZIP,
                "phar" => Phar::PHAR
        );
        // your phar dir to help avoid using path before the phar file
        $this->dir = __DIR__;
    }

    private function parsePath($path) {
        $url = parse_url($path);
        $this->pharContainer = $url['host'];
        $this->pharType = $this->types[pathinfo($this->pharContainer, PATHINFO_EXTENSION)];
        if (strpos($url['path'], ".") !== false) {
            list($this->pharDir, $this->pharFile, , ) = array_values(pathinfo($url['path']));
        } else {
            $this->pharDir = $url['path'];
        }
    }

    public function stream_open($path, $mode, $options, &$opened_path) {
        $this->parsePath($path);
        $this->stream = new PharData($this->dir . "/" . $this->pharContainer, 0, null, $this->pharType);
        if (! empty($this->pharDir))
            $this->stream->addEmptyDir($this->pharDir);

        if ($mode == "rb") {
            $this->fp = fopen("phar://" . $this->pharContainer . "/" . $this->pharDir . "/" . $this->pharFile, $mode);
        }
        return true;
    }

    public function stream_write($data) {
        $this->pos += strlen($data);
        $this->stream->addFromString($this->pharDir . "/" . $this->pharFile, $data);
        return $this->pos;
    }

    public function stream_read($count) {
        return fread($this->fp, $count);
    }

    public function stream_tell() {
        return ftell($this->fp);
    }

    public function stream_eof() {
        return feof($this->fp);
    }

    public function stream_stat() {
        return fstat($this->fp);
    }
}
Baba
  • 94,024
  • 28
  • 166
  • 217
1

I have some answers for you - hopefully this will help you out.

First of all, with file_put_contents(), I've noticed some weirdness. First, what I did is follow the example of the link you posted. I was able to add the $context variable, but still had the error. What worked well for me was changing the file name to phar.phar. Perhaps the phar:// handler can't understand a .tar extension?

Anyway, this appears to be working code - but I don't recommend it

$context = stream_context_create(array('phar' =>
                                array('compress' => Phar::GZ)),
                                array('metadata' => array('user' => 'cellog')));

file_put_contents('phar://./phar.phar/test/foo.txt', 'bar', 0, $context);

Instead, use this.

Use me - I'm recommended

$phar = new PharData('./phar.tar', 0, null, Phar::TAR);
$phar->addEmptyDir('test');
$phar->addFile('./bar', 'foo.txt');

If you really need to use file_put_contents() instead, perhaps there's a different problem that exists that maybe we can help with. Thanks and good luck!

Aaron Saray
  • 1,178
  • 6
  • 19
  • Try to create phar with .phar extension. – sectus May 22 '13 at 14:11
  • Strangely enough, if you use the first approach and you do `$phar = new PharData('./phar.phar', 0, null, Phar::TAR); $phar->addEmptyDir('test');` before, you'll get the exact same error. Without it (and only with the `.phar` extension) it works. Not defining the `$context` at all, also works (however I'm not sure about the compression method). I know that the second option is the "recommended" one, but for reasons that I don't remember anymore (I came with a different solution for my problem) , I only had the `phar://...` path to work with. – Alix Axel May 22 '13 at 17:35
  • Oh, and one other thing that bugs me is that `file_get_contents` works out of the box with `.phar`, `.zip` and `.tar` extension files, as long as they are prefixed with the `phar://` stream wrapper, you don't even have to specify `$context`! I would expect `file_put_contents()` to be consistent and behave in the same way, but no. :( – Alix Axel May 22 '13 at 17:36
0

I've got a couple other alternative approaches which I think are a bit tidier. They avoid custom wrappers, symlinks or creating extra files in order to use the addFile function.

Option A

Use the addFromString function which works similarly to file_put_contents. The code below creates a TAR archive, adds a file to it and compresses it too.

$phar = new PharData("./archive.tar", 0, null, Phar::TAR);
$phar->addFromString('out/fileA.txt','The quick brown fox jumped over the lazy dog');
$phar->compress(Phar::GZ); # Creates archive.tar.gz

Option B

If you really need to use file_put_contents for some reason. You can create a PHAR archive using it, then reopen it and convert it to another archive type.

file_put_contents('phar://archive2.phar/out/fileB.txt', "Colourless green ideas sleep furiously");
$tarphar = new Phar('archive2.phar');
$tar = $tarphar->convertToData(Phar::TAR); # Creates archive2.tar
$zip = $tarphar->convertToData(Phar::ZIP); # Creates archive2.zip
$tgz = $tarphar->convertToData(Phar::TAR, Phar::GZ, '.tar.gz'); # Creates archive2.tar.gz