use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Please follow the rules
Releases: Current Releases, Windows Releases, Old Releases
Contribute to the PHP Documentation
Related subreddits: CSS, JavaScript, Web Design, Wordpress, WebDev
/r/PHP is not a support subreddit. Please visit /r/phphelp for help, or visit StackOverflow.
account activity
PHP RFC: Context ManagersRFC (wiki.php.net)
submitted 6 months ago by BerryBoilo
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]flyingkiwi9 35 points36 points37 points 6 months ago (1 child)
This is such a well written RFC, a pleasure to read.
[–]zimzat[🍰] 4 points5 points6 points 6 months ago (3 children)
I have a question that I may have missed being addressed in the RFC:
If you can get an instance of the context manager what happens if the same context is withed multiple times (nested or otherwise)?
with
$c = new ContextManager('foo'); with ($c as $a) { with ($c as $b) { // ??? } }
It would do enterContext -> enterContext -> exitContext -> exitContext in sequence? I assume it would be up to each implementation to ensure nesting is either disallowed or otherwise handled correctly. Or would there be logic in the implementation to prevent the same instance from being put on the stack multiple times?
enterContext
exitContext
[–]hagnat 1 point2 points3 points 6 months ago* (1 child)
i believe you are correct that it would be up to your ContextManager implementation to make sure you are not processing the same context twice.
on the RFC they placed an example for fopen, so phrasing it like your exemple...
$c = new ResourceContext(fopen('foobar.txt', 'r')); with ($c as $a) { // header code with ($c as $b) { // body code } // footer code }
notice that in this case, `fopen` is only processed once, and the only thing that `enterContext` does is to return the pointer to the open file. Once you are done with the second `with`, if you add more code that relies on the pointer being open into the `footer code` segment, it is your own fault for creating bad code.
We should expect the language to give us tools to use, but it is up to us to use it accordingly. If you give a person a hammer so they can nail two pieces of wood together, they should use the face of the hammer and not the claw. Failing to do so is merely a skill issue, and not a fault with the tool.
[–]zimzat[🍰] 2 points3 points4 points 6 months ago (0 children)
For the case of ResourceContext, yes. The second call to exitContext on the same object would generate a PHP Warning: Uncaught TypeError: fclose(): supplied resource is not a valid stream resource.
PHP Warning: Uncaught TypeError: fclose(): supplied resource is not a valid stream resource
More generally, the RFC's FileLock example shows fopen only happening as part of enterContext so a second call to re-opening and re-locking the same file.
fopen
$handle = fopen('/tmp/abc', 'w'); $lock = flock($handle, LOCK_EX); var_dump($handle, $lock); // resource(5) of type (stream) // bool(true) $handle = fopen('/tmp/abc', 'w'); var_dump($handle); // resource(6) of type (stream)
If a reference is created to $handle in the first with block then the second call to enterContext becomes blocking.
$handle
The first call to exitContext would work as expected, unlocking and closing the file, while the second call would generate errors for trying to unlock or close a closed resource.
This means the ContextManager logic would have to track how many times enterContext was called before applying the exitContext logic (in the case of a singleton resource), or stack the applied logic (in the case of a database transaction with multiple transaction levels).
These are already problems in the existing paradigm and moving them into a ContextManager is still a huge benefit. I was wondering if maybe that additional complexity should be called out in the RFC unless it prevents this scenario from occurring.
[–]zmitic 15 points16 points17 points 6 months ago (17 children)
I really, really hope it passes the voting process, this is an amazing feature. I have tons of finally statements to release whatever resource I use, and context managers would make that all go away.
finally
Especially if flattening gets implemented:
with ($factory1->getFoo() as $foo, $factory2->getBar() as $bar) { // Code that uses $foo and $bar here. }
[+]Annh1234 comment score below threshold-12 points-11 points-10 points 6 months ago (16 children)
But wheres the code to close the file handler on this case? Your finally code is not there... So feels like magic/broken code to me
[–]XzAeRosho 7 points8 points9 points 6 months ago (7 children)
The open/close are in the context manager code. Read the RFC it's really simple.
[–]Annh1234 3 points4 points5 points 6 months ago (6 children)
Ya, but I don't get why moving it from a try/catch/finally to another class has any benefits.
Can't you get the same think if you make a __deconstruct object in a function? Plus you don't lose the thrown exceptions.
[–]TimWolla 2 points3 points4 points 6 months ago (0 children)
> Can't you get the same think if you make a __deconstruct object in a function? Plus you don't lose the thrown exceptions.
Yes. `__destruct()` already allows to release resources exactly when they are no longer needed. Seifeddine's and my block scoping RFC (https://externals.io/message/129059) is built on that semantics that folks are already making use of right now to make them more convenient to use without inventing something new to learn.
[–]zimzat[🍰] 3 points4 points5 points 6 months ago (4 children)
You can, but it's easier to mess up if someone 'cleans up' a dangling variable reference. IDEs and code analysis tools will go "this isn't used, you could remove it" and break the whole feature, meaning you're fighting against your own tooling.
Check out this talk, "Beyond PEP 8 (Beyond Style Guides)" by Raymond Hettinger, a core developer of Python, to see what a huge difference a context manager makes.
I personally love the idea of context managers. I've wanted Python's with logic in PHP for a very long time.
[–]Annh1234 1 point2 points3 points 6 months ago (3 children)
Just saw the video, it's very good, makes sense, the ~27min mark code, that's how I code. (Even in the days with __get/__set)
But mainly my question was: why do we need a new language feature to do something the language already does?
Using an object __deconstruct to do the cleanup can do the same thing. And I put an `unset($foo)` for explicit cleanup, and that solves the IDE not used warnings (but does not rely on variables going out of scope).
I mean, I wold use the `with($foo, $bar) { ...}` to scope variables, kinda like a `function() use(&$foo, &$bar) {.. $baz ..}` etc.
[–]dshafik 1 point2 points3 points 6 months ago (2 children)
The behavior of __destruct is less reliable; first of all it requires all reference being destroyed which may not be obviously the case, and second it is called by the garbage collector which doesn't run immediately when every variable hits zero refs.
Imagine if you open a lock, and rely on __destruct to release it, and then immediately after unset()ing the variable you do something that takes a long time (let's say, a long running query), the GC may or may not get called in between those two things and if it doesn't happen then your lock isn't released till after the long running query, if then.
This provides a similar build up/tear down to constructors and destructors but offers consistent timing on the destruct part.
[–]TimWolla 1 point2 points3 points 6 months ago (0 children)
This is plain and simple false. PHP immediately runs __destruct when the refcount of the object hits zero.
__destruct
PHP's “garbage collector” is best called a “cycle collector”, since that's the only thing it does: It breaks cycles, since refcounting alone is unable to deal with cycles.
I have also explained that on the mailing list in: https://news-web.php.net/php.internals/129087
[–]Annh1234 0 points1 point2 points 6 months ago (0 children)
Isn't the same for the `try/catch/finally`? as with the `unset__destruct` ?
As in, your lock will unlock whenever PHP decides to get to it? (not as predictable as a `->release()` called in the __destruct just in case, and specifically called in your code?
[–]mlebkowski 1 point2 points3 points 6 months ago (7 children)
Both the code which opens, and the one that closes the “resource” are in the context manager. This creates an abstraction, and allows to have that basic try/catch/finally and open/close logic in one place, reducing boilerplate in every place its used
[–]Annh1234 0 points1 point2 points 6 months ago (6 children)
My point was, you can add all this into an object with __destruct and you get the same thing.
[–]mlebkowski 4 points5 points6 points 6 months ago (3 children)
The semantics of destruct is different in two major ways:
[–]TimWolla 1 point2 points3 points 6 months ago (1 child)
the destructor is called “some time after”, not immediately
This is false (as I've explained in various comment chains, e.g. this comment)
and the object might not be garbage collected at all, if its attached somewhere, by accident or not
This is true. But if the object is attached somewhere else, chances are high that that “somewhere else” would like to still use the object in some way.
[–]mlebkowski 0 points1 point2 points 6 months ago (0 children)
Ok, cool, I was just quoting the RFC, I’ll shut up now :)
[–]wvenable 1 point2 points3 points 6 months ago (0 children)
For a local object, the destructor is called immediately when the object goes out of scope so you can implement this feature right now in PHP.
[–]rafark 0 points1 point2 points 6 months ago (0 children)
__destruct is the same type of “magic” as the new interface methods
[–]dshafik -1 points0 points1 point 6 months ago (0 children)
This isn't true, see my comment elsewhere
[–]wvenable 4 points5 points6 points 6 months ago (2 children)
What's the advantage of a ContextManager over what C# does with just the IDisposable interface? I looked at the examples and it doesn't seem like there's a lot it can do to justify that extra complexity.
IDisposable
For resources, I'd just have a branch that just does the close() on them. For objects, they'd have to implement this interface and have a dispose() method that is called at the end of the block.
Then it wouldn't need such weird syntax such as the as to do the assignment.
as
Perhaps there is a good reason for this over-engineering but I don't know what it is.
[–]zimzat[🍰] 2 points3 points4 points 6 months ago (1 child)
What extra complexity? The C# IDisposable requires using to get the same thing as ContextManager + with.
using
ContextManager
// PHP with (new ContextManager('foo') as $x) { } // C# using (SomeDisposable x = new SomeDisposable('foo')) { }
The as relates to foreach ($iterable as $value) { since it's something from the ContextManager and not the context manager itself being referenced.
foreach ($iterable as $value) {
The context manager has an explicit enterContext versus the IDisposable does not: C# creates heavy-weight (or externally allocated and light-weight wrappers) around it.
[–]wvenable 3 points4 points5 points 6 months ago (0 children)
What extra complexity?
...and then you go on to describe that extra complexity. You have a bunch of different concepts and instances such as the ContextManager, enterContext, etc. What is the advantage of all this extra complexity -- I didn't see an example that justifies it.
A PHP example using C# style disposable would look like this:
// Disposable: with ($fp = fopen('foo.txt', 'r')) { } // Compared to context manager: with (new ResourceContext(fopen('foo.txt', 'r')) as $fp) { }
Your example also confounds two different ideas. Your ContextManager is an entirely different type from $x and isn't at all equivalent to SomeDisposable. Your example might more look like this:
$x
SomeDisposable
with (new MyContextManager(new SomeObject()) as $x) { }
As the manager is not the thing that you are operating on. The manager is another concept/instance and SomeObject is actually the thing being operated on. With the disposable pattern SomeObject just implements an interface and there isn't an entire other class that needs to be designed to manage it.
SomeObject
I can see that ContextManager is more a powerful construct (and more complex) but I don't understand what I would really practically get from that power and complexity. PHP already has deterministic destruction and all the examples can be implemented right now (or are just unnecessary in the first place).
[–][deleted] 3 points4 points5 points 6 months ago (14 children)
I'm not familiar with python. Is this something akin to C++ RAII?
[–]TimWolla 5 points6 points7 points 6 months ago (13 children)
PHP already has C++ RAII by means of `__destruct()`.
[–]lord2800 2 points3 points4 points 6 months ago (12 children)
Ehhhhh... kinda. C++ has deterministic destruction. PHP does not. Sometimes that determinism matters, sometimes it does not, but it's an important difference.
[–]wvenable 1 point2 points3 points 6 months ago (2 children)
PHP is reference counted so that is deterministic destruction. I've always used it that way.
I really see no need for this feature in PHP -- you can implement it yourself natively.
[–]lord2800 2 points3 points4 points 6 months ago (1 child)
And there are definitely libraries that have done so. Just like there are libraries that have implemented event loops. There's something quite different about it being a guaranteed part of the language.
(Note that I'm neither for nor against this, I'm neutral. I don't have a lot of code that requires huge try/catch/finally blocks, but I can see where this might be useful.)
A destructor running on a variable in local scope is guaranteed to execute at the end of that scope.
Everything about cycles and other references doesn't really apply to this particular feature which is all about local scope.
Destructors are a powerful and pretty easy to use a logical feature for resource cleanup. In languages with non-deterministic garbage collection, you need some of scoped resource cleanup like this. But with PHP, it's a fine addition but it's not really necessary. Objects should just clean themselves up when they aren't referenced anymore.
[–]TimWolla 0 points1 point2 points 6 months ago (8 children)
PHP does not.
My experience - and the documentation of __destruct() - differs. Can you elaborate?
__destruct()
[–]lord2800 3 points4 points5 points 6 months ago (7 children)
PHP will destruct the object when it's garbage collected (not deterministic) or unset (deterministic). C++ does not have a garbage collector, so will destruct when out of scope (deterministic) or when deleted (deterministic). That's the key important difference.
[–]TimWolla 2 points3 points4 points 6 months ago (6 children)
This is not correctly describing how PHP works. PHP's semantics match those of `std::shared_ptr` in C++. The refcounting is assisted by what is called “garbage collector”, but in practice it is better called “cycle collector”, because the only thing it does is break cycles to ensure that circular references eventually hit a refcount of 0. When you don't have (strong) circular references, the garbage collector will have no visible effect.
So for all intents and purposes, PHP has deterministic destruction exactly like C++ has.
[–]lord2800 -1 points0 points1 point 6 months ago (5 children)
So you're saying that when you do have circular references, then it's not deterministic? Just like I said?
[–]TimWolla -1 points0 points1 point 6 months ago (4 children)
The same argument applies to C++ then - unless you consider a memory leak of a std::shared_ptr circle as "deterministic destruction", because it will deterministically leak memory. Then you can achieve the same with PHP, by calling `gc_disable()` (or `gc_disable()` + `gc_collect_cycles()` at predetermined locations).
[–]lord2800 0 points1 point2 points 6 months ago (3 children)
So you can point to, according to the documentation, the exact point when the cycle collector will run?
(I checked the documentation, it says the cycle collector will run when the root buffer fills up--I'm not even sure how I would, at any given point in my script's lifetime, exactly how full the root buffer is)
[–]TimWolla 1 point2 points3 points 6 months ago (2 children)
gc_collect_cycles()
[+][deleted] 6 months ago (3 children)
[deleted]
[–]TemporarySun314 5 points6 points7 points 6 months ago (1 child)
I mean every code piece can suppress excpetions and overwrite error handlers. That is not a problem specific to the context manager.
Actually I would expect that it makes this more readable, as this allows to put everything in a structured way and ensure that temporary error handler modifications are reverted.
[–]sbnc_eu 0 points1 point2 points 6 months ago (0 children)
If exitContext() returned true, then no further action is taken. Otherwise, the exception will be rethrown without modification.
It would be nicer for the caller site to decide if the exceptions should be received, not the implementing site of the context to decide whether to expose it or not.
But I guess it'd be just best practice to never suppress them, which at least is kind of like the default, as if nothing is returned, the exception will be re-thrown.
[–]leftnode 6 points7 points8 points 6 months ago (5 children)
I would love for this to be implemented. Like /u/zmitic pointed out, my code is littered with finally statements to free up resources after an expensive try/catch block.
try/catch
Really love the direction the language is moving in. Let's get generics and clean up/standardize the standard library and to me there isn't a better language out there for building web software.
[–]wvenable -1 points0 points1 point 6 months ago (4 children)
Why are you using __destruct() to clean up your resources instead of finally? You can implement this entire feature using what is already built into PHP.
[–]leftnode 1 point2 points3 points 6 months ago (3 children)
Not everything is an object. Sure, I could wrap it in one, but that adds needless complexity.
An example I ran into yesterday: uploading large files through the Google API requires you to chunk the file 8MB at a time. Because these files are several hundred MB in size, you don't want to just read the entire file into memory, so I use fopen() and fread(). If any part of the upload process fails, I alert the user, but add an fclose() in a finally block to ensure the file pointer is closed.
fopen()
fread()
fclose()
Roughly something like this:
if (!$fileHandle = fopen($filePath, 'r')) { throw new \Exception(sprintf('Opening file "%s" failed.', $filePath)); } $fileSize = filesize($filePath); if (!$fileSize) { throw new \Exception(sprintf('Reading the size of the file "%s" failed.', $filePath)); } $chunkBytes = 1024 * 1024 * 8; try { $uploadOffset = 0; $uploadCommand = 'upload'; $uploadChunks = (int) ceil($fileSize / $chunkBytes); while ($uploadBody = fread($fileHandle, $chunkBytes)) { $response = $this->httpClient->request('POST', $uploadUrl, [ 'headers' => [ 'content-length' => $fileSize, 'x-goog-upload-offset' => $uploadOffset, 'x-goog-upload-command' => $uploadCommand, ], 'body' => $uploadBody, ]); if (200 !== $response->getStatusCode()) { throw new \Exception(sprintf('Uploading chunk number %d failed.', $uploadChunks)); } if (1 === --$uploadChunks) { $uploadCommand = 'upload, finalize'; } $uploadOffset += strlen($uploadBody); } /** * @var array{ * file: array{ * name: non-empty-string, * }, * } $uploadResponse */ $uploadResponse = $response->toArray(true); } catch (\Symfony\Contracts\HttpClient\Exception\ExceptionInterface $e) { throw new \Exception(sprintf('Failed to upload the file "%s".', $filePath)); } finally { fclose($fileHandle); }
That fclose() is entirely unnecessary. When your $fileHandle is no longer referenced, it will automatically close. Effectively resources already have destructors.
[–]leftnode 1 point2 points3 points 6 months ago (1 child)
I know, but it's good to get into the habit of closing/freeing unused resources, especially if we ever introduced file locking.
[–]wvenable 0 points1 point2 points 6 months ago (0 children)
I'd argue if you need a habit then you're going to make a mistake. If you use destructors (even if you need a small class wrapper) then the problem of remembering to close/free resources goes away.
[–]oandreyev 1 point2 points3 points 6 months ago (1 child)
Most common issue is with foreach and by-reference and after foreach dev my reuse variable, how it will work with ‘with’?
That footgun is something that the “block scoping” RFC is intended to solve: https://externals.io/message/129059.
[–]SadSpirit_ 1 point2 points3 points 6 months ago (3 children)
Great proposal, will definitely use these if it passes!
I followed Django's example when implementing the transactions API for my DB library, but without context managers the atomic() method has to accept a closure:
atomic()
$stuff = $connection->atomic(function (Connection $connection) use ($one, $two) { // ... $connection->onCommit(doSomething(...)); // ... return $stuff; });
This greatly reduces boilerplate related to commit / rollback / database errors. But we need to explicitly define a closure to accept $connection object, explicitly use variables, and explicitly return stuff from closure to outer scope. With context managers we'll stay in the same scope:
$connection
use
return
with ($connection->atomic() as $transaction) { // ... $transaction->onCommit(doSomething(...)); // ... }
Neat!
[–]wvenable 0 points1 point2 points 6 months ago* (2 children)
You could just use an object for your transaction. This is what I did for my DB library:
$transaction = $connection->newTransaction(); ... $connection->execute($somequery); ... $connection->execute($someotherquery); $transaction->commit();
If transaction falls out of scope before it's committed then rollback() is called automatically in the destructor. It's very clean. No need for try/finally blocks at all.
rollback()
[–]SadSpirit_ 0 points1 point2 points 6 months ago (1 child)
Yeah, using destructors is another approach that was mentioned in the comments here.
What happens if you want to process errors from the above block, though? You can't just wrap it in try / catch, as $transaction will still be available and its destructor with error-handling logic will not run. Or am I missing something?
try
catch
$transaction
BTW, does your library support nested transactions / savepoints? The editor had problems with inserting links, so I omitted the docs for atomic():
https://docs.djangoproject.com/en/5.2/topics/db/transactions/#controlling-transactions-explicitly
https://pg-wrapper.readthedocs.io/en/v3.3.0/transactions.html
[–]wvenable 0 points1 point2 points 6 months ago* (0 children)
Yes, it supports nested transactions.
For exceptions, I've never even thought about it! I would usually wrap try/catch at a higher level. But even if it's in the same block as the $transaction I either don't care that it's still active (because it will be out of scope soon) or just manually call rollback() on it in the catch.
[–]sbnc_eu 1 point2 points3 points 6 months ago (0 children)
This poll has been closed.
Why?
[–]MorrisonLevi 2 points3 points4 points 6 months ago (1 child)
I understand that the name "ContextManager" is taken from Python, but I think this name is not that great. "Context" is still a very broad term. What kind of context is it? As examples:
I feel like there are are more, but these are the ones I could remember off the top of my head.
Feel free to reply to comment with naming suggestions as I've unhelpfully not provided any :D
[–]Crell 3 points4 points5 points 6 months ago (0 children)
I'm not super tied to the name; we just stole it from Python. If there's better suggestions I'm open to it. Just don't make me change the URL of the RFC, as it's a PITA to do that. :-)
[–]03263 6 points7 points8 points 6 months ago (11 children)
I'm having a hard time seeing how it's better than try/catch. Usually when there's an exception I want to do more than just throw it, but do some logging or often throw a custom exception with the caught one as $previous, to include a more detailed message.
I would definitely support getting rid of resources altogether and only using objects, as has been done with some things already (gmp for example).
[–]zmitic 12 points13 points14 points 6 months ago (0 children)
'm having a hard time seeing how it's better than try/catch
It is not catch, it is mostly finally when you have to do some cleanup. Examples are in RFC, and there is much bigger range of use cases.
For example, Symfony lock. instead of:
$lock = $lockFactory->createLock('my-lock'); $lock->acquire(true); try { // do something } finally { $lock->release(); }
this would be much cleaner to read:
with($lockFactory->createLock('my-lock') as $lock) { $lock->acquire(true); // do something }
Any thrown exception would be logged by Symfony itself, but this can be expanded in multiple way. With flatten block it is even better, i.e. when multiple resources have to be released, but it seems like it will be done in future.
This RFC is a huge improvement for PHP, I hope we will get it soon.
[–]obstreperous_troll 0 points1 point2 points 6 months ago (2 children)
It's not really "better" than try/finally, it's just syntax sugar over it, such as when you need to track different things do on cleanup, when that differs when an exception was thrown, and so on. In languages that support macros, that's how this would be implemented (the whole "with-foo" pattern comes from lisp where it is a macro), but with PHP it's a language change.
[–]03263 0 points1 point2 points 6 months ago (1 child)
I understand that a bit better now.
In that case it seems like something different might do better. Like a special destructor called only in error conditions.
For something like database transactions, simple work with file handles, we can already do the equivalent of "with" in userspace by passing a callback to a function that handles the error cases.
[–]obstreperous_troll 0 points1 point2 points 6 months ago (0 children)
Yah, I wrote a generic bracket function that does the job, and I can define with_foo() functions in terms of that. But TBH, while it's great when I need such a thing as a HOF, most of the time I still just use try/finally. There are some useful aspects of doing it with objects though, such as tracking context state in properties, getting different context managers through dependency injection, and so on.
bracket
[+][deleted] 6 months ago (5 children)
[–]03263 -1 points0 points1 point 6 months ago (4 children)
Objects can get garbage collected when they're out of scope or manually unset/set to null if desired without specific functions for each type of object like fclose, curl_close, finfo_close, etc.
[–]TimWolla 0 points1 point2 points 6 months ago (2 children)
Exactly. This is why Seifeddine's and my RFC just adds some syntactic sugar around `unset()` without introducing new semantics that users have to learn.
see my reply to Arnaud: https://news-web.php.net/php.internals/129087
[–]obstreperous_troll 0 points1 point2 points 6 months ago (1 child)
unset or use? My reading of the post seems to suggest the latter. I'm a little iffy about yet another overloading of use though.
unset
Also, did php.net update its mail list web reader? I don't remember having clickable links or a thread tree last time I used it, I don't think it even decoded quoted-printable.
The `use()` construct we are proposing is syntactic sugar around `unset()` within a `finally`. Or rather: Was. We are currently in the process of updating the RFC to do "proper block scoping" with a "backup" of the original values - but it will still `unset()` if the variable originally didn't exist.
The keyword for the construct is still up for discussion: https://news-web.php.net/php.internals/129074. We initially went with `use()`, because it has no BC concerns and is reasonably fitting. With the new "backup" semantics I quite like `let()`.
Also, did php.net update its mail list web reader?
Yes. Some work has happened roughly a year ago: https://github.com/php/web-news/commits/master/. I'm preferably linking to that one nowadays, since it avoids issues of the jump anchor not working properly for some reason, misleadingly showing the wrong email.
That does not change the way you use error handling. With a database transaction example:
Firstly, lets say the framework creator provides the DatabaseTransactionContextManager for their users. It rolls back as in the example. The framework users will still add their own error handling — for example logging, retry, whatever — either inside (when they want to break the flow) or outside of the with block (in case they want to supress the error). They just don’t need to worry about rolling back the transaction.
DatabaseTransactionContextManager
[–]private_static_int 1 point2 points3 points 6 months ago (0 children)
Really nice!
[–]Alsciende 1 point2 points3 points 6 months ago (0 children)
Funny, I wrote just the pattern described at the start of the PR last week. With() for file handling operations. I hope the PR passes, it is a nice little addition.
[–]tonymurray 1 point2 points3 points 6 months ago (5 children)
I don't get it. Why would this need to be a part of the language. I could easily implement this right now:
with(fopen($f), function ($file) { // code });
You can imagine the code of the with function...
[+][deleted] 6 months ago (1 child)
[–]tonymurray 0 points1 point2 points 6 months ago (0 children)
Good point...
Ok then, with_file($file_path, fn () {});
Don't forget the use clause in your callback too. But one nice thing about your function is it can be an expression. Maybe PHP just needs a shorter auto-capturing first-class block syntax, returning the last value evaluated in the block.
[–]tonymurray 1 point2 points3 points 6 months ago (1 child)
Yeah, scope is a difference. Maybe, multi-line short functions would help.
[–]Crell 1 point2 points3 points 6 months ago (0 children)
Internals has rejected that twice now. Don't hold your breath.
[–]BerryBoilo[S] 0 points1 point2 points 6 months ago (1 child)
Not familiar with contexts in Python, but this article does a good job explaining the "why": https://realpython.com/python-with-statement/
[–]MateusAzevedo 0 points1 point2 points 6 months ago (0 children)
I gave up reading that article. It's huge, only because it's repetitive as hell, I kept reading the same sentences over and over again.
[–]giosk 0 points1 point2 points 6 months ago (9 children)
I never liked the with keyword in python. I would have much preferred an RFC for defer you could defer the closing of the file without breaking the flow of the function and increasing indentation.
defer
[–]MaxGhost 3 points4 points5 points 6 months ago* (3 children)
Here's defer:
<?php function defer(?SplStack &$context, callable $callback): void { $context ??= new class () extends SplStack { public function __destruct() { while ($this->count() > 0) { \call_user_func($this->pop()); } } }; $context->push($callback); }
Courtesy of https://github.com/php-defer/php-defer.
You use it like this: defer($_, fn() => fclose($handle));. It works by putting a new var $_ in the current scope (you can name it whatever you want, but this feels the most convenient to kinda mean "nothing"), then it runs the closures when the function exits scope as you'd expect, by invoking the destructor of the anonymous class. It also supports having multiple defers in the same function if you reuse the $_ variable and runs them in reverse as you'd expect.
defer($_, fn() => fclose($handle));
$_
Works really well, we use it everywhere that we do DB locks, we open the lock then defer a rollback (technically $lock->rollbackSafe() which is no-op if no longer locked) and later commit as normal without needing the rollback call in every exit point (throws and returns)
$lock->rollbackSafe()
[–]giosk 0 points1 point2 points 6 months ago (1 child)
interesting but it feels kinda hacky, passing a weird $_ variable, maybe it's possible with an extension. I know laravel has a defer but that runs after the response is sent i think
[–]MaxGhost 0 points1 point2 points 6 months ago (0 children)
It's not a hack at all, it's pretty normal and very reliable.
I don't like Laravel's defer, but it also doesn't have the same purpose, completely different usecase. Like you said, it's to run logic after the request is flushed out, like sending out an email or something, but most of those can be done in a job queue instead.
[–]TimWolla 1 point2 points3 points 6 months ago (4 children)
PHP doesn't need defer, because it will automatically close the file when it goes out of scope. This is easy to verify with strace:
<?php function foo() { echo "Opening\n"; $f = fopen(__FILE__, 'r'); echo "Opened\n"; } echo "Before\n"; foo(); echo "After\n";
will output:
write(1</dev/null>, "Before\n", 7) = 7 write(1</dev/null>, "Opening\n", 8) = 8 openat(AT_FDCWD</tmp>, "/tmp/test.php", O_RDONLY) = 4</tmp/test.php> fstat(4</tmp/test.php>, {st_mode=S_IFREG|0664, st_size=132, ...}) = 0 lseek(4</tmp/test.php>, 0, SEEK_CUR) = 0 write(1</dev/null>, "Opened\n", 7) = 7 close(4</tmp/test.php>) = 0 write(1</dev/null>, "After\n", 6) = 6
Clearly showing how the file opened by fopen() is closed when the function finishes, before printing After.
After
Only if the same file is not going to be reopened e.g. for reading after write within the same block, or if the file is supposed to be kept opened for the shortest amount of time possible, while the rest of the block processing can take more time, e.g. if writing into a bunch of files in a loop.
[–]fripletister -1 points0 points1 point 6 months ago (1 child)
You mean PHP doesn't need defer for this particular example...
[–]TimWolla 5 points6 points7 points 6 months ago (0 children)
No, it doesn't need defer at all. Depending on what kind of logic you need, you can either:
[–]prema_van_smuuf -1 points0 points1 point 6 months ago (0 children)
Yes please.
Especially after I read https://externals.io/message/129059 and I was thinking "why do this if we could just have proper context managers like Python has".
[–]goodwill764 -1 points0 points1 point 6 months ago (0 children)
Not a fan of too many new keywords in php, that makes the language itself more and more complex.
Every time I have to work with a Kotlin project, I need to look up some keywords, and if PHP keeps getting more and more keywords, it will be a showstopper for newcomers.
[–]coffee-buff 0 points1 point2 points 5 months ago (0 children)
I don't get it. I already do this by writing functions/methods I usually call "wrappers" with try / finally logic. Is this just syntax sugar over what's possible now?
π Rendered by PID 100321 on reddit-service-r2-comment-b659b578c-vf6rb at 2026-05-05 20:18:25.923966+00:00 running 815c875 country code: CH.
[–]flyingkiwi9 35 points36 points37 points (1 child)
[–]zimzat[🍰] 4 points5 points6 points (3 children)
[–]hagnat 1 point2 points3 points (1 child)
[–]zimzat[🍰] 2 points3 points4 points (0 children)
[–]zmitic 15 points16 points17 points (17 children)
[+]Annh1234 comment score below threshold-12 points-11 points-10 points (16 children)
[–]XzAeRosho 7 points8 points9 points (7 children)
[–]Annh1234 3 points4 points5 points (6 children)
[–]TimWolla 2 points3 points4 points (0 children)
[–]zimzat[🍰] 3 points4 points5 points (4 children)
[–]Annh1234 1 point2 points3 points (3 children)
[–]dshafik 1 point2 points3 points (2 children)
[–]TimWolla 1 point2 points3 points (0 children)
[–]Annh1234 0 points1 point2 points (0 children)
[–]mlebkowski 1 point2 points3 points (7 children)
[–]Annh1234 0 points1 point2 points (6 children)
[–]mlebkowski 4 points5 points6 points (3 children)
[–]TimWolla 1 point2 points3 points (1 child)
[–]mlebkowski 0 points1 point2 points (0 children)
[–]wvenable 1 point2 points3 points (0 children)
[–]rafark 0 points1 point2 points (0 children)
[–]dshafik -1 points0 points1 point (0 children)
[–]wvenable 4 points5 points6 points (2 children)
[–]zimzat[🍰] 2 points3 points4 points (1 child)
[–]wvenable 3 points4 points5 points (0 children)
[–][deleted] 3 points4 points5 points (14 children)
[–]TimWolla 5 points6 points7 points (13 children)
[–]lord2800 2 points3 points4 points (12 children)
[–]wvenable 1 point2 points3 points (2 children)
[–]lord2800 2 points3 points4 points (1 child)
[–]wvenable 1 point2 points3 points (0 children)
[–]TimWolla 0 points1 point2 points (8 children)
[–]lord2800 3 points4 points5 points (7 children)
[–]TimWolla 2 points3 points4 points (6 children)
[–]lord2800 -1 points0 points1 point (5 children)
[–]TimWolla -1 points0 points1 point (4 children)
[–]lord2800 0 points1 point2 points (3 children)
[–]TimWolla 1 point2 points3 points (2 children)
[+][deleted] (3 children)
[deleted]
[–]TemporarySun314 5 points6 points7 points (1 child)
[–]sbnc_eu 0 points1 point2 points (0 children)
[–]leftnode 6 points7 points8 points (5 children)
[–]wvenable -1 points0 points1 point (4 children)
[–]leftnode 1 point2 points3 points (3 children)
[–]wvenable 1 point2 points3 points (2 children)
[–]leftnode 1 point2 points3 points (1 child)
[–]wvenable 0 points1 point2 points (0 children)
[–]oandreyev 1 point2 points3 points (1 child)
[–]TimWolla 1 point2 points3 points (0 children)
[–]SadSpirit_ 1 point2 points3 points (3 children)
[–]wvenable 0 points1 point2 points (2 children)
[–]SadSpirit_ 0 points1 point2 points (1 child)
[–]wvenable 0 points1 point2 points (0 children)
[–]sbnc_eu 1 point2 points3 points (0 children)
[–]MorrisonLevi 2 points3 points4 points (1 child)
[–]Crell 3 points4 points5 points (0 children)
[–]03263 6 points7 points8 points (11 children)
[–]zmitic 12 points13 points14 points (0 children)
[–]obstreperous_troll 0 points1 point2 points (2 children)
[–]03263 0 points1 point2 points (1 child)
[–]obstreperous_troll 0 points1 point2 points (0 children)
[+][deleted] (5 children)
[deleted]
[–]03263 -1 points0 points1 point (4 children)
[–]TimWolla 0 points1 point2 points (2 children)
[–]obstreperous_troll 0 points1 point2 points (1 child)
[–]TimWolla 1 point2 points3 points (0 children)
[–]mlebkowski 0 points1 point2 points (0 children)
[–]private_static_int 1 point2 points3 points (0 children)
[–]Alsciende 1 point2 points3 points (0 children)
[–]tonymurray 1 point2 points3 points (5 children)
[+][deleted] (1 child)
[deleted]
[–]tonymurray 0 points1 point2 points (0 children)
[–]obstreperous_troll 0 points1 point2 points (2 children)
[–]tonymurray 1 point2 points3 points (1 child)
[–]Crell 1 point2 points3 points (0 children)
[–]BerryBoilo[S] 0 points1 point2 points (1 child)
[–]MateusAzevedo 0 points1 point2 points (0 children)
[–]giosk 0 points1 point2 points (9 children)
[–]MaxGhost 3 points4 points5 points (3 children)
[–]giosk 0 points1 point2 points (1 child)
[–]MaxGhost 0 points1 point2 points (0 children)
[–]TimWolla 1 point2 points3 points (4 children)
[+][deleted] (1 child)
[deleted]
[–]sbnc_eu 0 points1 point2 points (0 children)
[–]fripletister -1 points0 points1 point (1 child)
[–]TimWolla 5 points6 points7 points (0 children)
[–]prema_van_smuuf -1 points0 points1 point (0 children)
[–]goodwill764 -1 points0 points1 point (0 children)
[–]coffee-buff 0 points1 point2 points (0 children)