Hacker News new | ask | show | jobs
by some_furry 778 days ago
> Can I now get EEXISTS and such out of fopen()? Looking at the docs it still just returns false and emits an uncatchable E_WARNING that can't be examined programmatically.

Sure, the most straightforward way is to first use the "x" flag for write-only, and "x+" for read/write access.

  $fp = fopen('file.txt', 'x+');
From the PHP docs:

"If the file already exists, the fopen() call will fail by returning false and generating an error of level E_WARNING. If the file does not exist, attempt to create it. This is equivalent to specifying O_EXCL|O_CREAT flags for the underlying open(2) system call."

And finally to convert E_WARNING errors to exceptions, use you can use set_error_handler() to throw an exception (and even write business logic to filter them): https://www.php.net/manual/en/language.exceptions.php

They're not unhandleable.

But if you want a single function that throws a specific exception when a file exists, specific to the behavior you want, I got you:

  <?php
  declare(strict_types=1);
  
  namespace Argp242;
  use function fopen as php_fopen;
  
  function fopen(
      string $filename,
      string $mode,
      bool $use_include_path = false,
      $context = null
  ) {
    if (file_exists($filename)) {
      throw new \RuntimeException('E_EXISTS');
    }
    return php_fopen($filename, $mode, $use_include_path, $context);
  }

To run this code from outside a namespace, simply:

  try {
    $fp = Argp242\fopen($filename, $mode);
  } catch (RuntimeException $ex) {
    // Handle $ex
  }
Throw it in a library somewhere, having written it once, and now you can just use that whenever you want that behavior in your PHP code.
3 comments

There are tons of reasons an fopen() call can fail and sometimes you really want to know why; these pokemon exceptions don't give me that. And your code is racy as files can be created in-between the file_exists() and fopen() calls. Yes, this happens, and in some cases it's a security problem (which is why you need to use mkstemp and not mktemp in C).
The core of PHP is very close to the libc function it offers, and so you will do that in a similar way that you do that in C : call error_get_last(), which gives you the details of the last error.
this code will run into problems with race conditions if the file is created after file_exists is called but before fopen is called
If you expect PHP to ever win a race against another process, I don't know what to tell you.

Trying to make PHP work for a job it's not suited for and failing is not a reason to hate PHP. It's a reason to ask yourself "why".

This absolutely will happen, and most likely the other process is also PHP. The most obvious example is caching anything to a file. I don't hate PHP though, I just don't feel the need to convince myself it couldn't be improved.
A lot of PHP usage is inherently multi-process; 100 users accessing your PHP app are essentially 100 processes, often with a few of them running in parallel. Doing any sort of file i/o really does require thinking about these sort of things, and it's something I've run into.

And this is not exactly a huge ask, or a very obscure function call. You can kind of work around all of this if you're careful, but it's not exactly brilliant is it? Literally every other mainstream language can do this. This is really a "only in PHP" type of thing.

Just say "yeah, this is still a downside of PHP; hopefully it will get improve one day!" Fine. Fair enough. No language is perfect and I'm not here to bash PHP. I consider this to be a major pain point, but other people do other things and maybe it's less of an issue for them. Also fine. What I don't get are these contortionist tricks – I already knew how this would go when I posted it: first there would be the "but you can do this", and then when mentioned that's not really sufficient the "yes but you don't need that anyway".

We ran several quite popular (millions of monthly users) PHP websites for almost two decades and never ran into any issues of this nature, including a lot of local on-server file I/O. I don't recall doing anything specific or particularly advanced to deal with these issues, just some basic encapsulation was enough and it worked seamlessly.
If it's a pain point, write an RFC. Make the language better. Don't just shrug and say "it sucks"
Eh, you can constructively critique things without spending tons of effort on fixing it.

What a painful and unpleasant conversation this is turning out to be.

> What a painful and unpleasant conversation this is turning out to be.

Finally, something we both agree on

If you change the behavior of fopen() by installing an error handler that throws an exception, won't that mess up library code that doesn't expect that behavior? Seems hacky and error prone to me. (Also the race condition others already mentioned!)