Skip to content

[Filesystem] Add appendToFile() #20612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 8, 2017

Conversation

chalasr
Copy link
Member

@chalasr chalasr commented Nov 23, 2016

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets n/a
License MIT
Doc PR todo

So we could append content to a file:

(new Filesystem)->appendToFile($file, 'bar');

instead of doing it in two steps:

if (false === $content = @file_get_contents($file)) {
    throw new \Exception();
}

(new Filesystem)->dumpFile($file, $content.'bar');

Doing it opt-in using dumpFile(..., $append = false) would have been enough but not possible for BC.

@ro0NL
Copy link
Contributor

ro0NL commented Nov 26, 2016

We could also go with readFile / writeFile maybe? Where writeFile allows some tweaking to append, prepend or set/dump/overwrite/replace.

@chalasr
Copy link
Member Author

chalasr commented Nov 26, 2016

@ro0NL We could and it would make sense imho. Though I'm comfortable with what is proposed here because it's something I actually needed a lot of times, I have concrete use cases for it.
Feel free to try your approach, I would be 👍 . Thanks for sharing

@nicolas-grekas nicolas-grekas added this to the 3.x milestone Dec 6, 2016
@nicolas-grekas
Copy link
Member

The implementation looks very fragile to me: it's not atomic at all; why should it fail when the file doesn't exist?
I think that this should behave more like fopen+a mode.
But then why should we need a method to do that when fopen+a mode is really fine?

@chalasr
Copy link
Member Author

chalasr commented Dec 12, 2016

why should it fail when the file doesn't exist?

You're right, it should not fail by default, but I think it should be opt-in for cases where the content must not be appended if the file doesn't already exist.

But then why should we need a method to do that when fopen+a mode is really fine?

To ease error handling as dumpFile() does, so we could catch a specific exception instead of having to mute an eventual warning and to check the return of fopen in user-land code.

Given the whole Filesystem api eases performing such common tasks on the filesystem, I think it's relevant to add this one too, avoiding the need to write this logic yourself and mixing the use of this api with native functions

@chalasr chalasr force-pushed the filesystem/append_to_file branch 2 times, most recently from 5bef77b to 35d33fa Compare December 16, 2016 08:49
@chalasr
Copy link
Member Author

chalasr commented Dec 16, 2016

Removed the exception when the file doesn't exist. Now this behaves like fopen +a but writing to the file and throwing exceptions on failure.

@chalasr chalasr force-pushed the filesystem/append_to_file branch 2 times, most recently from 88a308c to fc87aa0 Compare December 17, 2016 10:17

$tmpFile = $this->tempnam(dirname($filename), basename($filename));

if (false === $originalContent = @file_get_contents($filename)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about forwarding to dumpFile instead in this case? So above checks can be removed..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, done 👍

@chalasr chalasr force-pushed the filesystem/append_to_file branch from fc87aa0 to 35eb1cb Compare December 18, 2016 11:51
public function appendToFile($filename, $content)
{
if (false !== $originalContent = @file_get_contents($filename)) {
$originalContent = '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$content = $originalContent.$content;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! thanks @ro0NL

$originalContent = '';
}

return $this->dumpFile($filename, $originalContent.$content);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$filename, $content

@chalasr chalasr force-pushed the filesystem/append_to_file branch 2 times, most recently from c185ecd to ec7c144 Compare December 18, 2016 12:12
@chalasr
Copy link
Member Author

chalasr commented Dec 18, 2016

Travis failure is unrelated

*/
public function appendToFile($filename, $content)
{
if (false !== $originalContent = @file_get_contents($filename)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now loads the current file content into memory (which doesn't look like an atomic append to me). Wouldn't it be better to rely on the FILE_APPEND flag of file_put_contents instead?
You could refactor a bit the logic inside dumpFile in order to extract it in a private method adding the flag or not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which doesn't look like an atomic append to me

The write is atomic, using file_put_contents for the read operation would not make it more atomic to me, do I miss something?

You could refactor a bit the logic inside dumpFile in order to extract it in a private method adding the flag or not.

It was my first intention, but would you avoid creating a tmpfile for append? Otherwise we still have to use file_get_contents or doing two file_put_contents,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do FILE_APPEND|LOCK_EX. Basically creating the tmp file is about forcing an exclusive lock right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do FILE_APPEND|LOCK_EX

Yeah, I was about to propose it :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not an filesystem expert.. but the tmp file in the middle feels like a poor man solution to me.. so perhaps LOCK_EX is the way to go indeed for dumpeFile as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using file_put_contents for the read operation would not make it more atomic to me

Never told you to do so ^^'

We could do FILE_APPEND|LOCK_EX

That's basically what I meant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what about a public writeFile($filename, $content[, $append = false]) then? Using LOCK_EX by design and let dumpFile forward? That to me would be the simplest implementation.

But a private function in the middle allowing this API (appendToFile and dumpFile) is also fine 👍 i guess.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, using LOCK_EX doesn't make file_put_contents atomic, so in both cases (using file_get_contents/file_put_contents or file_put_contents with flags) the append would not be atomic. Considering this I prefer to let dumpFile() as it is since the behavior is better (maybe not really atomic, but more than using file_put_contents directly).
Refs:
#10018 (comment)
http://stackoverflow.com/questions/4899737/should-lock-ex-on-both-read-write-be-atomic

About appendToFile, I'm still not sure what is better between using file_get_contents or file_put_contents with FILE_APPEND, but I tend to prefer using file_get_contents to avoid having file_put_contents breaks in the middle of the write..

@@ -624,7 +624,7 @@ public function tempnam($dir, $prefix)
* @param string $filename The file to be written to
* @param string $content The data to write into the file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For using file_put_contents we technically allow string|array|resource.

Copy link
Member Author

@chalasr chalasr Dec 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, updated on the two newly added methods.
Since this targets master, would you mind to open a PR against 2.8 (it doesn't handle streams in 2.7, see 0fc513e) for changing this docblock? I think it's worth it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree.

@chalasr chalasr force-pushed the filesystem/append_to_file branch from ec7c144 to 9908df7 Compare December 18, 2016 13:29
@ogizanagi
Copy link
Contributor

ogizanagi commented Dec 18, 2016

👍

We just need to validate there isn't any downside about using LOCK_EX instead of the previous implementation (what were the reasons behind the original implementation).

Status: Reviewed

@chalasr chalasr force-pushed the filesystem/append_to_file branch from 5681f39 to 4e737b4 Compare January 6, 2017 18:31
$dir = dirname($filename);

if (!is_dir($dir)) {
$this->mkdir($dir);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have to check here too that the directory is writable after we created it (the directory might have been created by another process)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, done. The same could be applied on dumpFile() I think

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think you are right. Should be done on 2.7 then as bug fix IMO.

@chalasr chalasr force-pushed the filesystem/append_to_file branch from 4e737b4 to 9fb4050 Compare January 8, 2017 12:38
@@ -649,6 +649,31 @@ public function dumpFile($filename, $content)
}

/**
* Appends content to an existing file.
*
* @param string $filename The file for which to append content
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file to which to append content

@xabbuh
Copy link
Member

xabbuh commented Jan 8, 2017

👍

@chalasr chalasr force-pushed the filesystem/append_to_file branch from 9fb4050 to 0e52144 Compare January 8, 2017 12:53
fabpot added a commit that referenced this pull request Jan 8, 2017
…d it in dumpFile() (chalasr)

This PR was merged into the 2.7 branch.

Discussion
----------

[Filesystem] Check that directory is writable after created it in dumpFile()

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #20612 (comment)
| License       | MIT
| Doc PR        | n/a

In case permissions have been changed meanwhile

Commits
-------

dbc4148 [Filesystem] Check that the directory is writable after created it in dumpFile()
* @param string $filename The file to which to append content
* @param string $content The content to append
*
* @throws IOException If the file is not writable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you remove the capital letter above, you should do the same here, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted the removal of the capital letter

@chalasr chalasr force-pushed the filesystem/append_to_file branch from 0e52144 to 9fb5293 Compare January 8, 2017 19:22
@fabpot
Copy link
Member

fabpot commented Jan 8, 2017

Can you add a note in the CHANGELOG?

@chalasr chalasr force-pushed the filesystem/append_to_file branch from 9fb5293 to 1f7b753 Compare January 8, 2017 20:07
@chalasr
Copy link
Member Author

chalasr commented Jan 8, 2017

CHANGELOG updated

@fabpot
Copy link
Member

fabpot commented Jan 8, 2017

Thank you @chalasr.

@fabpot fabpot merged commit 1f7b753 into symfony:master Jan 8, 2017
fabpot added a commit that referenced this pull request Jan 8, 2017
This PR was merged into the 3.3-dev branch.

Discussion
----------

[Filesystem] Add appendToFile()

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | todo

So we could append content to a file:

```php
(new Filesystem)->appendToFile($file, 'bar');
```

instead of doing it in two steps:

```php
if (false === $content = @file_get_contents($file)) {
    throw new \Exception();
}

(new Filesystem)->dumpFile($file, $content.'bar');
```

Doing it opt-in using `dumpFile(..., $append = false)` would have been enough but not possible for BC.

Commits
-------

9fb5293 [Filesystem] Add appendToFile()
fabpot added a commit that referenced this pull request Jan 8, 2017
This PR was merged into the 3.3-dev branch.

Discussion
----------

[Filesystem] Add appendToFile()

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | todo

So we could append content to a file:

```php
(new Filesystem)->appendToFile($file, 'bar');
```

instead of doing it in two steps:

```php
if (false === $content = @file_get_contents($file)) {
    throw new \Exception();
}

(new Filesystem)->dumpFile($file, $content.'bar');
```

Doing it opt-in using `dumpFile(..., $append = false)` would have been enough but not possible for BC.

Commits
-------

1f7b753 [Filesystem] Add appendToFile()
@javiereguiluz
Copy link
Member

I'm super late to this but I wonder if this method should be called append() instead of appendToFile() ?

@chalasr chalasr deleted the filesystem/append_to_file branch January 8, 2017 20:30
@ogizanagi
Copy link
Contributor

I'm fine with the appendToFile name. First because of the dumpFile method. Secondly because this method is on the Filesystem class, and you're not appending anything to the filesystem.

But that's only my 2 cents :)

@chalasr
Copy link
Member Author

chalasr commented Jan 8, 2017

@javiereguiluz I hesitated for append() and I think it could be misleading e.g. append files to a directory. appendToFile() is clear, at least :)

@chalasr
Copy link
Member Author

chalasr commented Jan 8, 2017

@fabpot I think there is something wrong with your git tool, commits are merged twice, see the master log

@ogizanagi
Copy link
Contributor

ogizanagi commented Jan 8, 2017

Commits' contents aren't even the same... 😅
First merged one is missing your CHANGELOG update, like if it the old commit reference was used. I bet on a missing fetch (somehow, but I don't know how the tool works behind the scene) :)

Also, the merge commit date is before the merge itself 😆

@nicolas-grekas nicolas-grekas modified the milestones: 3.x, 3.3 Mar 24, 2017
@fabpot fabpot mentioned this pull request May 1, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants