├── .gitignore ├── composer.json ├── license ├── readme-ja.md ├── readme.md ├── src ├── Contracts │ ├── DeferBailableExceptionInterface.php │ ├── DeferrableInterface.php │ └── DeferrableScopeInterface.php ├── Defer.php ├── DeferCallback.php ├── DeferContext.php ├── Deferrable.php ├── Exceptions │ ├── DeferBailException.php │ ├── DeferException.php │ ├── DeferrableException.php │ └── MergedDeferringException.php ├── Functions.php ├── Runtime.php └── Scopes │ ├── AbstractDeferrableScope.php │ ├── DeferBailableScope.php │ ├── DeferContinuableScope.php │ └── DeferrableScopeType.php └── tests ├── DeferCreateContextClassTest.php ├── DeferCreateContextFunctionTest.php ├── DeferDeferrableClassTest.php └── DeferDeferrableFunctionTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "m3m0r7/php-deferrable", 3 | "description": "Deferrable run your code", 4 | "type": "library", 5 | "license": "MIT", 6 | "require": { 7 | "php": ">=7.2" 8 | }, 9 | "autoload": { 10 | "psr-4": { 11 | "PHPDeferrable\\": "src/" 12 | }, 13 | "files": [ 14 | "src/Functions.php" 15 | ] 16 | }, 17 | "autoload-dev": { 18 | "psr-4": { 19 | "PHPDeferrable\\Tests\\": "tests/" 20 | } 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^9.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 memory 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /readme-ja.md: -------------------------------------------------------------------------------- 1 | # PHP-Deferrable - Simple and Powerful deferrable run code library 2 | 3 | `PHP-Deferrable` はシンプルでかつ強力にあなたのコードを遅延処理することができるライブラリです。 4 | このライブラリは Go の defer とよく似ています。 5 | テスト以外に置いて他のライブラリに依存しないため、このライブラリはとてもシンプルなライブラリです。 6 | 7 | ## ドキュメント 8 | - [English](./readme.md) 9 | - 日本語 (現在) 10 | 11 | ## インストール 12 | 13 | Composer を使用する: 14 | ``` 15 | composer require m3m0r7/php-deferrable 16 | ``` 17 | 18 | ## 今までの課題 19 | Go には defer があり、return が返る前に defer の中身を実行することが出来ます。 20 | しかし PHP は defer がないものの、 `try-finally` やデストラクタの破棄タイミングを用いて defer を実現することが可能です。 21 | 22 | ```php 23 | try { 24 | // ... do something 25 | } finally { 26 | // 後処理 27 | } 28 | ``` 29 | 30 | これにはいくつか問題があり、後処理をするコードが煩雑になる可能性と、 `try` 構文が長くなりすぎてしまうと、何を処理するのかわからなくなってしまいます。 31 | そして、不要なインデントに苛まれることでしょう。 32 | 33 | `php-deferrable` はその課題を解決するため、非常にシンプルな関数とクラスを提供することにより、それらすべての問題を解決します。 34 | 35 | 36 | ## クイックスタート 37 | ```php 38 | use function PHPDeferrable\defer; 39 | use function PHPDeferrable\deferrable; 40 | 41 | class MyClass 42 | { 43 | public function doSomething1() 44 | { 45 | defer(function () { 46 | echo "Three!\n"; 47 | }); 48 | 49 | defer(function () { 50 | echo "Two!\n"; 51 | }); 52 | echo "One!\n"; 53 | } 54 | 55 | public function doSomething2() 56 | { 57 | defer(function () { 58 | echo "NyanNyan!\n"; 59 | }); 60 | echo "Wanwan!\n"; 61 | } 62 | } 63 | 64 | /** 65 | * @var MyClass $myClass 66 | */ 67 | $myClass = deferrable(MyClass::class, ...$somethingArguments); 68 | $myClass->doSomething1(); 69 | $myClass->doSomething2(); 70 | ``` 71 | 72 | 上記は下記のように表示されます。 73 | 74 | ``` 75 | One! 76 | Two! 77 | There! 78 | Wanwan! 79 | NyanNyan! 80 | ``` 81 | 82 | ## Deferrable 関数 83 | `deferrable` 関数に callable なパラメータを渡すことも可能です。 84 | 85 | ```php 86 | use function PHPDeferrable\defer; 87 | use function PHPDeferrable\deferrable; 88 | 89 | deferrable(function () { 90 | defer(function () { 91 | echo "0: deferred call\n"; 92 | }); 93 | echo "0: first call\n"; 94 | }); 95 | 96 | deferrable(function () { 97 | defer(function () { 98 | echo "1: deferred call\n"; 99 | }); 100 | echo "1: first call\n"; 101 | })(); 102 | ``` 103 | 104 | 下記のように表示されます。 105 | 106 | ``` 107 | 0: first call 108 | 0: deferred call 109 | 1: first call 110 | 1: deferred call 111 | ``` 112 | 113 | `deferrable` 関数は関数の実行結果を返り値として返すことも可能です。 114 | 115 | ```php 116 | use function PHPDeferrable\defer; 117 | use function PHPDeferrable\deferrable; 118 | 119 | $result = deferrable(function () { 120 | defer(function () { 121 | // do something. 122 | }); 123 | return "Return value\n"; 124 | })(); 125 | 126 | echo $result; 127 | ``` 128 | 129 | 上記は以下のようになります。 130 | ``` 131 | Return value 132 | ``` 133 | 134 | `deferrable` は resource の後処理などにも有用です。 135 | 136 | ```php 137 | use function PHPDeferrable\defer; 138 | use function PHPDeferrable\deferrable; 139 | 140 | deferrable(function () { 141 | $handle = fopen('php://memory', 'r') 142 | defer(function () use ($handle) { 143 | fclose($handle) 144 | }); 145 | // ... do something 146 | })(); 147 | 148 | ``` 149 | 150 | `defer` は可変長引数を用いてパラメータを渡すことも可能です。パラメータはコンテキストに準じてコピーされます。 151 | 152 | 153 | ```php 154 | use function PHPDeferrable\defer; 155 | use function PHPDeferrable\deferrable; 156 | 157 | deferrable(function () { 158 | $message = 'Hello World'; 159 | defer(function ($message) { 160 | echo $message; 161 | }, $message); 162 | // ... do something 163 | })(); 164 | 165 | ``` 166 | 167 | 上記は下記のようになります。 168 | ``` 169 | Hello World 170 | ``` 171 | 172 | また、リファレンスとすることにより、 `defer` 内でパラメータの値を変更させることも可能です。 173 | 174 | ```php 175 | use function PHPDeferrable\defer; 176 | use function PHPDeferrable\deferrable; 177 | 178 | deferrable(function () { 179 | $message = 'Hello World'; 180 | defer(function (&$message) { 181 | echo $message; 182 | }, $message); 183 | 184 | defer(function (&$message) { 185 | $message = 'The cat has big power.'; 186 | }, $message); 187 | // ... do something 188 | })(); 189 | 190 | ``` 191 | 192 | 上記は下記のようになります。 193 | ``` 194 | The cat has big power. 195 | ``` 196 | 197 | ## defer の例外 198 | 通常 php-deferrable は defer 内で例外が投げられてもスタックされた defer の処理は継続して行うように設計されています。 199 | これは Go には例外はなく、しかし PHP に例外があるという矛盾を解決するためです。 200 | 201 | ```php 202 | deferrable(function() { 203 | defer(function () { 204 | throw new Exception('exception 1'); 205 | }); 206 | 207 | defer(function () { 208 | throw new Exception('exception 2'); 209 | }); 210 | 211 | defer(function () { 212 | throw new Exception('exception 3'); 213 | }); 214 | })() 215 | ``` 216 | 217 | 上記のような例の場合、例外はすべて結合され `MergedDeferringException` として返却します。 218 | 219 | しかし、例外が発生した場合止めたい場合もあるでしょう。そのような手段ももちろん用意してあります。 220 | 例外が発生した場合、defer の処理を中断する方法は二通りあります。 221 | 222 | 1 つ目は現在の deferrable スコープそのものを例外が発生した場合返すように `DeferBailableScope::of` を使用します。 223 | 224 | ```php 225 | deferrable( 226 | DeferBailableScope::of(function() { 227 | defer(function () { 228 | throw new ThirdException('exception 1'); 229 | }); 230 | 231 | defer(function () { 232 | throw new SecondException('exception 2'); 233 | }); 234 | 235 | defer(function () { 236 | throw new FirstException('exception 3'); 237 | }); 238 | ) 239 | })() 240 | ``` 241 | 242 | クラスの場合は下記になります。 243 | 244 | ```php 245 | 246 | class MyClassTest 247 | { 248 | public function doSomething() 249 | { 250 | defer(function () { 251 | throw new ThirdException('exception 1'); 252 | }); 253 | 254 | defer(function () { 255 | throw new SecondException('exception 2'); 256 | }); 257 | 258 | defer(function () { 259 | throw new FirstException('exception 3'); 260 | }); 261 | } 262 | } 263 | 264 | $myClass = deferrable( 265 | DeferBailableScope::of( 266 | MyClassTest::class 267 | ) 268 | ); 269 | 270 | $myClass->doSomething(); 271 | ``` 272 | 273 | この場合、 `FirstException` が例外として外のスコープに投げられます。`FirstException` が投げられる理由は、 274 | defer の処理はスタックをポップしていきます。つまり、一番最後に登録された defer から処理をすることになります。 275 | また、 `DeferBailableScope` とは反対に、継続できる例外を明示的に指定したい場合、 `DeferContinuableScope` を使用します。 276 | 277 | 2 つ目は `DeferBailableExceptionInterface` を継承した例外を投げる方法です。 278 | このインタフェースを継承している場合、その時点で例外のマージを止めて、その継承された例外のみを返します。 279 | 280 | 281 | ```php 282 | 283 | class SecondException extends \Exception implements DeferBailableExceptionInterface 284 | { 285 | } 286 | 287 | deferrable(function() { 288 | defer(function () { 289 | throw new ThirdException('exception 1'); 290 | }); 291 | 292 | defer(function () { 293 | throw new SecondException('exception 2'); 294 | }); 295 | 296 | defer(function () { 297 | throw new FirstException('exception 3'); 298 | }); 299 | })() 300 | ``` 301 | 302 | 上記の場合、`SecondException` が投げられます。 303 | 304 | `Defer::createContext` の場合、第一引数にスコープの種類を渡すことにより制御可能です。 305 | 306 | ```php 307 | class Example 308 | { 309 | public function doSomething() 310 | { 311 | $context = Defer::createContext(DeferrableScopeType::BAILABLE); 312 | 313 | $context->defer(function () { 314 | throw new ThirdException('exception 1'); 315 | }); 316 | 317 | $context->defer(function () { 318 | throw new SecondException('exception 2'); 319 | }); 320 | 321 | $context->defer(function () { 322 | throw new FirstException('exception 3'); 323 | }); 324 | } 325 | } 326 | 327 | (new Example())->doSomething(); 328 | ``` 329 | 330 | 上記の場合 `FirstException` が例外として投げられます。 331 | 332 | ## コンテキストマニピュレータ 333 | コンテキストマニピュレータは非常にシンプルな方法で遅延処理を実現しています。 334 | この方法を使うことにより defer と deferrable との実行と比較して PHP における無駄なスタックやメモリ使用量を控えることが出来ます。 335 | これを使うことによりクラス名を `deferrable` でラップする必要がなくなります。 336 | 337 | ```php 338 | class MyClass 339 | { 340 | public function doSomething() 341 | { 342 | $context = Defer::createContext(); 343 | $context->defer(function () { 344 | echo "Two!"; 345 | }); 346 | echo "One!"; 347 | } 348 | } 349 | 350 | $myClass = new MyClass(); 351 | $myClass->doSomething(); 352 | ``` 353 | 354 | ## ライセンス 355 | MIT 356 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PHP-Deferrable - Simple and Powerful deferrable run code library 2 | 3 | The `PHP-Deferrable` is a simple and powerful deferrable run code library. 4 | This library like Golang. 5 | This library is very simple because this is not depending other libraries. 6 | 7 | ## Documents 8 | - English (Current) 9 | - [日本語](./readme-ja.md) 10 | 11 | ## Install 12 | 13 | Use composer: 14 | ``` 15 | composer require m3m0r7/php-deferrable 16 | ``` 17 | 18 | ## Issues to date 19 | Go has a defer, and you can execute the contents of the defer before returning. 20 | However, although PHP does not have a defer, it is possible to achieve defer using `try-finally` or destructor destruction timing. 21 | 22 | ```php 23 | try { 24 | // ... do something 25 | } finally { 26 | // post-processing 27 | } 28 | ``` 29 | 30 | This has some problems: the post-processing code can be cumbersome, and if the `try` syntax gets too long, you won't know what to do. 31 | And you will suffer from unnecessary indentation. 32 | 33 | `php-deferrable` solves all of these problems by providing very simple functions and classes to solve the problem. 34 | 35 | ## Quick Start 36 | ```php 37 | use function PHPDeferrable\defer; 38 | use function PHPDeferrable\deferrable; 39 | 40 | class MyClass 41 | { 42 | public function doSomething1() 43 | { 44 | defer(function () { 45 | echo "Three!\n"; 46 | }); 47 | 48 | defer(function () { 49 | echo "Two!\n"; 50 | }); 51 | echo "One!\n"; 52 | } 53 | 54 | public function doSomething2() 55 | { 56 | defer(function () { 57 | echo "NyanNyan!\n"; 58 | }); 59 | echo "Wanwan!\n"; 60 | } 61 | } 62 | 63 | /** 64 | * @var MyClass $myClass 65 | */ 66 | $myClass = deferrable(MyClass::class, ...$somethingArguments); 67 | $myClass->doSomething1(); 68 | $myClass->doSomething2(); 69 | ``` 70 | 71 | It will show as below: 72 | 73 | ``` 74 | One! 75 | Two! 76 | There! 77 | Wanwan! 78 | NyanNyan! 79 | ``` 80 | 81 | ## Deferrable function 82 | You can pass a function into the deferrable. 83 | 84 | ```php 85 | use function PHPDeferrable\defer; 86 | use function PHPDeferrable\deferrable; 87 | 88 | deferrable(function () { 89 | defer(function () { 90 | echo "0: deferred call\n"; 91 | }); 92 | echo "0: first call\n"; 93 | })(); 94 | 95 | deferrable(function () { 96 | defer(function () { 97 | echo "1: deferred call\n"; 98 | }); 99 | echo "1: first call\n"; 100 | })(); 101 | ``` 102 | 103 | It will show as below: 104 | 105 | ``` 106 | 0: first call 107 | 0: deferred call 108 | 1: first call 109 | 1: deferred call 110 | ``` 111 | 112 | Deferrable function can be return a value. 113 | 114 | ```php 115 | use function PHPDeferrable\defer; 116 | use function PHPDeferrable\deferrable; 117 | 118 | $result = deferrable(function () { 119 | defer(function () { 120 | // do something. 121 | }); 122 | return "Return value\n"; 123 | })(); 124 | 125 | echo $result; 126 | ``` 127 | 128 | It will show as below: 129 | ``` 130 | Return value 131 | ``` 132 | 133 | Deferrable can manipulate resource context. 134 | 135 | ```php 136 | use function PHPDeferrable\defer; 137 | use function PHPDeferrable\deferrable; 138 | 139 | deferrable(function () { 140 | $handle = fopen('php://memory', 'r') 141 | defer(function () use ($handle) { 142 | fclose($handle) 143 | }); 144 | // ... do something 145 | })(); 146 | 147 | ``` 148 | 149 | `defer` can be passed any parameters and it will copy based the context. 150 | 151 | ```php 152 | use function PHPDeferrable\defer; 153 | use function PHPDeferrable\deferrable; 154 | 155 | deferrable(function () { 156 | $message = 'Hello World'; 157 | defer(function ($message) { 158 | echo $message; 159 | }, $message); 160 | // ... do something 161 | })(); 162 | 163 | ``` 164 | 165 | It will show as below: 166 | ``` 167 | Hello World 168 | ``` 169 | 170 | And it can be changed the parameter value in `defer` function with reference. 171 | 172 | ```php 173 | use function PHPDeferrable\defer; 174 | use function PHPDeferrable\deferrable; 175 | 176 | deferrable(function () { 177 | $message = 'Hello World'; 178 | defer(function (&$message) { 179 | echo $message; 180 | }, $message); 181 | 182 | defer(function (&$message) { 183 | $message = 'The cat has big power.'; 184 | }, $message); 185 | // ... do something 186 | })(); 187 | 188 | ``` 189 | 190 | It will show as below: 191 | ``` 192 | The cat has big power. 193 | ``` 194 | 195 | 196 | ## Exception of defer 197 | Normally, php-deferrable is designed so that even if an exception is thrown in the defer, the processing of the stacked defer continues. 198 | This is to resolve the inconsistency that Go has no exceptions, but PHP does. 199 | 200 | ```php 201 | deferrable(function() { 202 | defer(function () { 203 | throw new Exception('exception 1'); 204 | }); 205 | 206 | defer(function () { 207 | throw new Exception('exception 2'); 208 | }); 209 | 210 | defer(function () { 211 | throw new Exception('exception 3'); 212 | }); 213 | })() 214 | ``` 215 | 216 | In the case of the above example, all exceptions are combined and returned as `MergedDeferringException`. 217 | 218 | However, you may want to stop if an exception occurs. Of course, such means are also available. 219 | If an exception occurs, there are two ways to suspend defer processing. 220 | 221 | The first uses `DeferBailableScope :: of` to return the current deferrable scope itself if an exception occurs. 222 | 223 | ```php 224 | deferrable( 225 | DeferBailableScope::of(function() { 226 | defer(function () { 227 | throw new ThirdException('exception 1'); 228 | }); 229 | 230 | defer(function () { 231 | throw new SecondException('exception 2'); 232 | }); 233 | 234 | defer(function () { 235 | throw new FirstException('exception 3'); 236 | }); 237 | ) 238 | })() 239 | ``` 240 | 241 | or to use in class: 242 | 243 | ```php 244 | 245 | class MyClassTest 246 | { 247 | public function doSomething() 248 | { 249 | defer(function () { 250 | throw new ThirdException('exception 1'); 251 | }); 252 | 253 | defer(function () { 254 | throw new SecondException('exception 2'); 255 | }); 256 | 257 | defer(function () { 258 | throw new FirstException('exception 3'); 259 | }); 260 | } 261 | } 262 | 263 | $myClass = deferrable( 264 | DeferBailableScope::of( 265 | MyClassTest::class 266 | ) 267 | ); 268 | 269 | $myClass->doSomething(); 270 | ``` 271 | 272 | In this case, `FirstException` is thrown as an exception to the outer scope. The reason `FirstException` is thrown is 273 | The defer process pops the stack. In other words, the process is started from the last registered defer. 274 | Also, in contrast to `DeferBailableScope`, if you want to explicitly specify an exception that can be continued, use` DeferContinuableScope`. 275 | 276 | The second is to throw an exception that inherits from `DeferBailableExceptionInterface`. 277 | If you inherit from this interface, stop merging exceptions at that point and return only those inherited exceptions. 278 | 279 | ```php 280 | 281 | class SecondException extends \Exception implements DeferBailableExceptionInterface 282 | { 283 | } 284 | 285 | deferrable(function() { 286 | defer(function () { 287 | throw new ThirdException('exception 1'); 288 | }); 289 | 290 | defer(function () { 291 | throw new SecondException('exception 2'); 292 | }); 293 | 294 | defer(function () { 295 | throw new FirstException('exception 3'); 296 | }); 297 | })() 298 | ``` 299 | 300 | In the above case, a `SecondException` is thrown. 301 | In the case of `Defer :: createContext`, it can be controlled by passing the scope type as the first argument. 302 | 303 | ```php 304 | class Example 305 | { 306 | public function doSomething() 307 | { 308 | $context = Defer::createContext(DeferrableScopeType::BAILABLE); 309 | 310 | $context->defer(function () { 311 | throw new ThirdException('exception 1'); 312 | }); 313 | 314 | $context->defer(function () { 315 | throw new SecondException('exception 2'); 316 | }); 317 | 318 | $context->defer(function () { 319 | throw new FirstException('exception 3'); 320 | }); 321 | } 322 | } 323 | 324 | (new Example())->doSomething(); 325 | ``` 326 | 327 | 328 | In the above case, `FirstException` is thrown as an exception. 329 | 330 | 331 | ## Context Manipulator 332 | The context manipulator is very simple deferrable functions manipulator. 333 | You can take possible to decreasing memory usage with using it. 334 | It is not required wrapping with `deferrable` function for you wanting to deferring a class. 335 | 336 | ```php 337 | class MyClass 338 | { 339 | public function doSomething() 340 | { 341 | $context = Defer::createContext(); 342 | $context->defer(function () { 343 | echo "Two!"; 344 | }); 345 | echo "One!"; 346 | } 347 | } 348 | 349 | $myClass = new MyClass(); 350 | $myClass->doSomething(); 351 | ``` 352 | 353 | ## License 354 | MIT 355 | -------------------------------------------------------------------------------- /src/Contracts/DeferBailableExceptionInterface.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 25 | $this->arguments = $arguments; 26 | } 27 | 28 | /** 29 | * @param callable $callback 30 | * @param mixed ...$arguments 31 | * @return static 32 | */ 33 | public static function factory(callable $callback, &...$arguments) 34 | { 35 | return new static($callback, ...$arguments); 36 | } 37 | 38 | public function __invoke() 39 | { 40 | return ($this->callback)(...$this->arguments); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/DeferContext.php: -------------------------------------------------------------------------------- 1 | scopeType = $scopeType; 64 | $this->splStack = $splStack ?? new SplStack(); 65 | } 66 | 67 | public function __destruct() 68 | { 69 | if ($this->consumed) { 70 | return; 71 | } 72 | $this->consume(); 73 | } 74 | 75 | /** 76 | * Run post-processing for defer stacks. 77 | * 78 | */ 79 | public function consume() 80 | { 81 | try { 82 | $this->exceptionStacks = []; 83 | foreach ($this->beforeCallbacks as $callback) { 84 | /** 85 | * @var callable $callback 86 | */ 87 | $callback($this); 88 | } 89 | 90 | while ($callback = $this->pop()) { 91 | /** 92 | * @var DeferCallback $callback 93 | */ 94 | foreach ($this->everyBeforeCallbacks as $everyCallback) { 95 | $everyCallback($this); 96 | } 97 | 98 | try { 99 | $callback(); 100 | } catch (Throwable $e) { 101 | if ($e instanceof DeferBailableExceptionInterface) { 102 | $this->exceptionStacks = []; 103 | throw $e; 104 | } 105 | switch ($this->scopeType) { 106 | case DeferrableScopeType::CONTINUABLE: 107 | $this->exceptionStacks[] = $e; 108 | break; 109 | case DeferrableScopeType::BAILABLE: 110 | $this->exceptionStacks = []; 111 | throw $e; 112 | default: 113 | $this->exceptionStacks = []; 114 | throw new DeferrableException( 115 | 'Specified scope type is invalid' 116 | ); 117 | } 118 | } 119 | foreach ($this->everyAfterCallbacks as $everyCallback) { 120 | $everyCallback($this); 121 | } 122 | } 123 | foreach ($this->afterCallbacks as $callback) { 124 | /** 125 | * @var callable $callback 126 | */ 127 | $callback($this); 128 | } 129 | if (count($this->exceptionStacks) > 0) { 130 | $messages = ''; 131 | foreach ($this->exceptionStacks as $number => $exceptionStack) { 132 | $messages .= '[Exception ' . (++$number) . ']: ' . $exceptionStack->getMessage() . "\n" . $exceptionStack->getTraceAsString() . ' (line: ' . $exceptionStack->getLine() . ', file:' . $exceptionStack->getFile() . ')'; 133 | } 134 | $this->exceptionStacks = []; 135 | throw new MergedDeferringException( 136 | $messages 137 | ); 138 | } 139 | } finally { 140 | $this->consumed = true; 141 | } 142 | } 143 | 144 | /** 145 | * Pop a callback from defer stacks. 146 | * 147 | * @return callable|false 148 | */ 149 | public function pop() 150 | { 151 | if ($this->splStack->count() <= 0) { 152 | return false; 153 | } 154 | return $this->splStack->pop(); 155 | } 156 | 157 | /** 158 | * Register a callback for deferring. 159 | * 160 | * @param callable $callback 161 | * @param mixed ...$arguments 162 | * @return DeferContext 163 | */ 164 | public function defer(callable $callback, &...$arguments): self 165 | { 166 | $this->splStack->push( 167 | DeferCallback::factory( 168 | $callback, 169 | ...$arguments 170 | ) 171 | ); 172 | return $this; 173 | } 174 | 175 | /** 176 | * Run callbacks before calling a defer callback every time. 177 | * 178 | * @param callable $callback 179 | * @return DeferContext 180 | */ 181 | public function everyBefore(callable $callback): self 182 | { 183 | $this->everyBeforeCallbacks[] = $callback; 184 | return $this; 185 | } 186 | 187 | /** 188 | * Run callbacks after calling a defer callback every time. 189 | * 190 | * @param callable $callback 191 | * @return DeferContext 192 | */ 193 | public function everyAfter(callable $callback): self 194 | { 195 | $this->everyAfterCallbacks[] = $callback; 196 | return $this; 197 | } 198 | 199 | /** 200 | * Run callback once before calling a defer callback. 201 | * 202 | * @param callable $callback 203 | * @return DeferContext 204 | */ 205 | public function before(callable $callback): self 206 | { 207 | $this->beforeCallbacks[] = $callback; 208 | return $this; 209 | } 210 | 211 | /** 212 | * Run callback once after calling a defer callback. 213 | * 214 | * @param callable $callback 215 | * @return DeferContext 216 | */ 217 | public function after(callable $callback): self 218 | { 219 | $this->afterCallbacks[] = $callback; 220 | return $this; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/Deferrable.php: -------------------------------------------------------------------------------- 1 | isCallable()) { 66 | return static::makeFunctionContextManipulator( 67 | $scope, 68 | ...$arguments 69 | ); 70 | } 71 | 72 | $reflection = new \ReflectionClass( 73 | $scope->getClassName() 74 | ); 75 | 76 | $body = []; 77 | 78 | foreach ($reflection->getMethods() as $method) { 79 | $methodName = $method->getName(); 80 | if ($method->isAbstract()) { 81 | continue; 82 | } 83 | $body[] = static::makeMethodSignature($method) . ' { ' 84 | . '$deferContext = \\' . __NAMESPACE__ . '\\Deferrable::createDeferContext(' . $scope->getScopeType() . '); ' 85 | . 'try{' 86 | . '$result = parent::' . $methodName . '(...func_get_args()); ' 87 | . '} finally {' 88 | . '\\' . __NAMESPACE__ . '\\Deferrable::consume($deferContext);' 89 | . '}' 90 | . 'return $result; ' 91 | . '}'; 92 | } 93 | 94 | $temporaryClassName = Runtime::DEFER_ANONYMOUS_CLASS_PREFIX . (static::$temporaryClassCounter++); 95 | 96 | eval( 97 | 'class ' . $temporaryClassName . ' extends ' . $scope->getClassName() . ' implements \\' . __NAMESPACE__ . '\\Contracts\\DeferrableInterface' 98 | . '{' 99 | . implode($body) 100 | . '}' 101 | ); 102 | 103 | return new $temporaryClassName(...$arguments); 104 | } 105 | 106 | /** 107 | * @param callable|DeferrableScopeInterface $deferrableFunction 108 | * @param mixed ...$arguments pass parameters into a function 109 | * @return callable 110 | */ 111 | protected static function makeFunctionContextManipulator($deferrableFunction, ...$arguments) 112 | { 113 | return function (array $options = []) use ($deferrableFunction, $arguments) { 114 | $context = static::createDeferContext( 115 | ($deferrableFunction instanceof DeferrableScopeInterface) 116 | ? $deferrableFunction->getScopeType() 117 | : static::$defaultScopeType 118 | ); 119 | try { 120 | $result = $deferrableFunction instanceof DeferrableScopeInterface 121 | ? $deferrableFunction->invokeCallable(...$arguments) 122 | : $deferrableFunction(...$arguments); 123 | } finally { 124 | static::consume($context); 125 | } 126 | return $result; 127 | }; 128 | } 129 | 130 | /** 131 | * @param int $scopeType 132 | * @return DeferContext 133 | */ 134 | public static function createDeferContext(int $scopeType): DeferContext 135 | { 136 | static::$scopeType = $scopeType; 137 | return static::$currentContext = new DeferContext(static::$scopeType); 138 | } 139 | 140 | /** 141 | * Consume deferred stacks. 142 | * 143 | * @param DeferContext $context 144 | */ 145 | public static function consume(DeferContext $context): void 146 | { 147 | static::$scopeType = static::$defaultScopeType; 148 | 149 | try { 150 | $context->consume(); 151 | } finally { 152 | static::removeContext(); 153 | } 154 | } 155 | 156 | /** 157 | * Remove current context. 158 | */ 159 | public static function removeContext() 160 | { 161 | static::$currentContext = null; 162 | } 163 | 164 | /** 165 | * @param ReflectionMethod $method 166 | * @return string 167 | */ 168 | protected static function makeMethodSignature(ReflectionMethod $method): string 169 | { 170 | $modifier = []; 171 | if ($method->isProtected()) { 172 | $modifier[] = 'protected'; 173 | } 174 | 175 | if ($method->isPrivate()) { 176 | $modifier[] = 'private'; 177 | } 178 | 179 | if ($method->isStatic()) { 180 | $modifier[] = 'static'; 181 | } 182 | 183 | if ($method->isPublic()) { 184 | $modifier[] = 'public'; 185 | } 186 | 187 | if ($method->isFinal()) { 188 | throw new DeferrableException( 189 | 'deferrable cannot wrap `' . $method->getName() . '` because it is including `final` modifier. ' . 190 | 'Please remove `final` modifier or use `Defer::createContext` instead of deferrable and defer functions.' 191 | ); 192 | } 193 | 194 | $returnType = ''; 195 | if ($method->getReturnType()) { 196 | $returnTypeObject = $method->getReturnType(); 197 | $returnType = $returnTypeObject->getName(); 198 | if ($returnTypeObject->allowsNull()) { 199 | $returnType = '?' . $returnType; 200 | } 201 | $returnType = ': ' . $returnType; 202 | } 203 | 204 | $parameters = []; 205 | 206 | foreach ($method->getParameters() as $parameter) { 207 | $parameterType = $parameter->getType(); 208 | $parameters[] = ($parameterType->allowsNull() ? '?' : '') 209 | . $parameterType->getName() 210 | . ' $' 211 | . $parameter->getName(); 212 | } 213 | 214 | return implode(' ', $modifier) 215 | . ' function ' 216 | . $method->getName() 217 | . '(' 218 | . implode(',', $parameters) 219 | . ')' 220 | . $returnType; 221 | } 222 | 223 | /** 224 | * Register a callback for deferring. 225 | * 226 | * @param callable $callback 227 | * @param mixed ...$arguments 228 | */ 229 | public static function defer(callable $callback, &...$arguments): void 230 | { 231 | if (static::getCurrentContext() === null) { 232 | throw new DeferException( 233 | 'Defer cannot stack because deferrable context is null. ' 234 | . 'Did you forget to run `Deferrable::createDeferContext`?' 235 | ); 236 | } 237 | 238 | /** 239 | * @var string $currentStackName 240 | */ 241 | static::getCurrentContext() 242 | ->defer( 243 | $callback, 244 | ...$arguments 245 | ); 246 | } 247 | 248 | /** 249 | * @return DeferContext|null 250 | */ 251 | public static function getCurrentContext(): ?DeferContext 252 | { 253 | return static::$currentContext ?? null; 254 | } 255 | } -------------------------------------------------------------------------------- /src/Exceptions/DeferBailException.php: -------------------------------------------------------------------------------- 1 | target = $target; 20 | $this->type = $type; 21 | } 22 | 23 | /** 24 | * @param callable|string $target 25 | * @return static 26 | */ 27 | public static function of($target) 28 | { 29 | if (is_string($target)) { 30 | return new static($target, 'class'); 31 | } 32 | return new static($target, 'callable'); 33 | } 34 | 35 | public function getClassName(): string 36 | { 37 | if (!$this->isClass()) { 38 | throw new DeferrableException('The scope is not a class.'); 39 | } 40 | return $this->target; 41 | } 42 | 43 | public function isClass(): bool 44 | { 45 | return $this->type === 'class'; 46 | } 47 | 48 | abstract public function getScopeType(): int; 49 | 50 | public function invokeCallable(...$arguments) 51 | { 52 | if (!$this->isCallable()) { 53 | throw new DeferrableException('The scope is not a callable.'); 54 | } 55 | return ($this->target)(...$arguments); 56 | } 57 | 58 | public function isCallable(): bool 59 | { 60 | return $this->type === 'callable'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Scopes/DeferBailableScope.php: -------------------------------------------------------------------------------- 1 | defer(function () { 27 | echo "Two!\n"; 28 | }); 29 | 30 | $context->defer(function () { 31 | echo "Three!\n"; 32 | }); 33 | echo "One!\n"; 34 | } 35 | 36 | public function doSomething2() 37 | { 38 | $context = Defer::createContext(); 39 | $context->defer(function () { 40 | echo "NyanNyan!\n"; 41 | }); 42 | echo "Wanwan!\n"; 43 | } 44 | 45 | public function doSomething3() 46 | { 47 | $context = Defer::createContext(); 48 | $handle = fopen('php://memory', 'r'); 49 | $context->defer(function () use ($handle) { 50 | fclose($handle); 51 | }); 52 | return 'Return value'; 53 | } 54 | 55 | public function doSomething4() 56 | { 57 | $context = Defer::createContext(DeferrableScopeType::BAILABLE); 58 | $context->defer(function () { 59 | throw new \Exception('exception 2'); 60 | }); 61 | 62 | $context->defer(function () { 63 | throw new TestingContextException('exception 2'); 64 | }); 65 | return 'Return value'; 66 | } 67 | 68 | public function doSomething5() 69 | { 70 | $context = Defer::createContext(); 71 | $context->defer(function () { 72 | throw new \Exception('exception 2'); 73 | }); 74 | 75 | $context->defer(function () { 76 | throw new TestingContextException('exception 2'); 77 | }); 78 | return 'Return value'; 79 | } 80 | 81 | public function doSomething6() 82 | { 83 | $context = Defer::createContext(); 84 | $context->defer(function () { 85 | throw new \Exception('exception 2'); 86 | }); 87 | 88 | $context->defer(function () { 89 | throw new BailableTestingContextException('exception 2'); 90 | }); 91 | return 'Return value'; 92 | } 93 | } 94 | 95 | class DeferCreateContextClassTest extends TestCase 96 | { 97 | public function testDeferPattern1() 98 | { 99 | ob_start(); 100 | $myClass = new DeferCreateContextClassTestTestMyClass(); 101 | $myClass->doSomething1(); 102 | $myClass->doSomething2(); 103 | $result = ob_get_clean(); 104 | 105 | $this->assertSame( 106 | "One!\nThree!\nTwo!\nWanwan!\nNyanNyan!\n", 107 | $result 108 | ); 109 | } 110 | 111 | public function testDeferPattern2() 112 | { 113 | ob_start(); 114 | $myClass = new DeferCreateContextClassTestTestMyClass(); 115 | $myClass->doSomething2(); 116 | $result = ob_get_clean(); 117 | 118 | $this->assertSame( 119 | "Wanwan!\nNyanNyan!\n", 120 | $result 121 | ); 122 | } 123 | 124 | public function testDeferPattern3() 125 | { 126 | ob_start(); 127 | $myClass = new DeferCreateContextClassTestTestMyClass(); 128 | $myClass->doSomething1(); 129 | $result = ob_get_clean(); 130 | 131 | $this->assertSame( 132 | "One!\nThree!\nTwo!\n", 133 | $result 134 | ); 135 | } 136 | 137 | public function testDeferPattern4() 138 | { 139 | $myClass = new DeferCreateContextClassTestTestMyClass(); 140 | $result = $myClass->doSomething3(); 141 | 142 | $this->assertSame( 143 | "Return value", 144 | $result 145 | ); 146 | } 147 | 148 | public function testDeferPattern5() 149 | { 150 | $myClass = new DeferCreateContextClassTestTestMyClass(); 151 | $this->expectException(TestingContextException::class); 152 | $myClass->doSomething4(); 153 | } 154 | 155 | public function testDeferPattern6() 156 | { 157 | $myClass = new DeferCreateContextClassTestTestMyClass(); 158 | $this->expectException(MergedDeferringException::class); 159 | $myClass->doSomething5(); 160 | } 161 | 162 | public function testDeferPattern7() 163 | { 164 | $myClass = new DeferCreateContextClassTestTestMyClass(); 165 | $this->expectException(BailableTestingContextException::class); 166 | $myClass->doSomething6(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /tests/DeferCreateContextFunctionTest.php: -------------------------------------------------------------------------------- 1 | defer(function () { 16 | echo "0: deferred call\n"; 17 | }); 18 | echo "0: first call\n"; 19 | }; 20 | 21 | $b = function () { 22 | $context = Defer::createContext(); 23 | $context->defer(function () { 24 | echo "1: deferred call\n"; 25 | }); 26 | echo "1: first call\n"; 27 | }; 28 | 29 | $a(); 30 | $b(); 31 | 32 | $result = ob_get_clean(); 33 | 34 | $this->assertSame( 35 | "0: first call\n0: deferred call\n1: first call\n1: deferred call\n", 36 | $result 37 | ); 38 | } 39 | 40 | public function testDeferPattern2() 41 | { 42 | ob_start(); 43 | $a = function () { 44 | $context = Defer::createContext(); 45 | $context->defer(function () { 46 | echo "0: deferred call\n"; 47 | }); 48 | echo "0: first call\n"; 49 | }; 50 | 51 | $a(); 52 | $result = ob_get_clean(); 53 | 54 | $this->assertSame( 55 | "0: first call\n0: deferred call\n", 56 | $result 57 | ); 58 | } 59 | 60 | public function testDeferPattern3() 61 | { 62 | ob_start(); 63 | $b = function () { 64 | $context = Defer::createContext(); 65 | $context->defer(function () { 66 | echo "1: deferred call\n"; 67 | }); 68 | echo "1: first call\n"; 69 | }; 70 | $b(); 71 | 72 | $result = ob_get_clean(); 73 | 74 | $this->assertSame( 75 | "1: first call\n1: deferred call\n", 76 | $result 77 | ); 78 | } 79 | 80 | public function testDeferPattern4() 81 | { 82 | ob_start(); 83 | $b = function () { 84 | $context = Defer::createContext(); 85 | $context->defer(function () { 86 | echo "1: deferred call\n"; 87 | }); 88 | $context->defer(function () { 89 | echo "1: deferred call2\n"; 90 | }); 91 | echo "1: first call\n"; 92 | }; 93 | $b(); 94 | 95 | $result = ob_get_clean(); 96 | 97 | 98 | $this->assertSame( 99 | "1: first call\n1: deferred call2\n1: deferred call\n", 100 | $result 101 | ); 102 | } 103 | 104 | public function testDeferPattern5() 105 | { 106 | $a = function () { 107 | $context = Defer::createContext(); 108 | 109 | $handle = fopen('php://memory', 'r'); 110 | $context->defer(function () use ($handle) { 111 | fclose($handle); 112 | }); 113 | 114 | return 'Return value'; 115 | }; 116 | $this->assertSame( 117 | "Return value", 118 | $a() 119 | ); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /tests/DeferDeferrableClassTest.php: -------------------------------------------------------------------------------- 1 | doSomething1(); 156 | $myClass->doSomething2(); 157 | $result = ob_get_clean(); 158 | 159 | $this->assertSame( 160 | "One!\nThree!\nTwo!\nWanwan!\nNyanNyan!\n", 161 | $result 162 | ); 163 | } 164 | 165 | public function testDeferPattern2() 166 | { 167 | ob_start(); 168 | /** 169 | * @var DeferDeferrableClassTestTestMyClass $myClass 170 | */ 171 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 172 | $myClass->doSomething2(); 173 | $result = ob_get_clean(); 174 | 175 | $this->assertSame( 176 | "Wanwan!\nNyanNyan!\n", 177 | $result 178 | ); 179 | } 180 | 181 | public function testDeferPattern3() 182 | { 183 | ob_start(); 184 | /** 185 | * @var DeferDeferrableClassTestTestMyClass $myClass 186 | */ 187 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 188 | $myClass->doSomething1(); 189 | $result = ob_get_clean(); 190 | 191 | $this->assertSame( 192 | "One!\nThree!\nTwo!\n", 193 | $result 194 | ); 195 | } 196 | 197 | public function testDeferPattern4() 198 | { 199 | /** 200 | * @var DeferDeferrableClassTestTestMyClass $myClass 201 | */ 202 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 203 | $result = $myClass->doSomething3(); 204 | 205 | $this->assertSame( 206 | "Return value", 207 | $result 208 | ); 209 | } 210 | 211 | public function testDeferPattern5() 212 | { 213 | /** 214 | * @var DeferDeferrableClassTestTestMyClass $myClass 215 | */ 216 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 217 | $result = $myClass->doSomething4(); 218 | 219 | $this->assertSame( 220 | "Return value", 221 | $result 222 | ); 223 | } 224 | 225 | public function testDeferPattern6() 226 | { 227 | /** 228 | * @var DeferDeferrableClassTestTestMyClass $myClass 229 | */ 230 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 231 | $result = $myClass->doSomething6(); 232 | 233 | $this->assertSame( 234 | "Test", 235 | $result 236 | ); 237 | } 238 | 239 | public function testDeferPattern7() 240 | { 241 | /** 242 | * @var DeferDeferrableClassTestTestMyClass $myClass 243 | */ 244 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 245 | 246 | ob_start(); 247 | $myClass->doSomething7(); 248 | $result = ob_get_clean(); 249 | 250 | $this->assertSame( 251 | "Test2", 252 | $result 253 | ); 254 | } 255 | 256 | public function testDeferPattern8() 257 | { 258 | /** 259 | * @var DeferDeferrableClassTestTestMyClass $myClass 260 | */ 261 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 262 | 263 | ob_start(); 264 | $myClass->doSomething8(); 265 | $result = ob_get_clean(); 266 | 267 | $this->assertSame( 268 | "Test2", 269 | $result 270 | ); 271 | } 272 | 273 | public function testDeferPattern9() 274 | { 275 | /** 276 | * @var DeferDeferrableClassTestTestMyClass $myClass 277 | */ 278 | $myClass = deferrable(DeferDeferrableClassTestTestMyClass::class); 279 | 280 | ob_start(); 281 | $myClass->doSomething9(); 282 | $result = ob_get_clean(); 283 | 284 | $this->assertSame( 285 | "Test", 286 | $result 287 | ); 288 | } 289 | 290 | public function testDeferPattern10() 291 | { 292 | /** 293 | * @var DeferDeferrableClassTestTestMyClass $myClass 294 | */ 295 | $myClass = deferrable( 296 | DeferBailableScope::of( 297 | DeferDeferrableClassTestTestMyClass::class 298 | ) 299 | ); 300 | 301 | $this->expectException(TestingException::class); 302 | 303 | $myClass->doSomething10(); 304 | } 305 | 306 | public function testDeferPattern11() 307 | { 308 | /** 309 | * @var DeferDeferrableClassTestTestMyClass $myClass 310 | */ 311 | $myClass = deferrable( 312 | DeferDeferrableClassTestTestMyClass::class 313 | ); 314 | 315 | $this->expectException(MergedDeferringException::class); 316 | 317 | $myClass->doSomething10(); 318 | } 319 | 320 | public function testDeferPattern12() 321 | { 322 | /** 323 | * @var DeferDeferrableClassTestTestMyClass $myClass 324 | */ 325 | $myClass = deferrable( 326 | DeferDeferrableClassTestTestMyClass::class 327 | ); 328 | 329 | ob_start(); 330 | $myClass->doSomething11('Hello', 'World'); 331 | $result = ob_get_clean(); 332 | 333 | $this->assertSame( 334 | "HelloWorld", 335 | $result 336 | ); 337 | } 338 | 339 | public function testDeferPattern13() 340 | { 341 | /** 342 | * @var DeferDeferrableClassTestTestMyClass $myClass 343 | */ 344 | $myClass = deferrable( 345 | DeferDeferrableClassTestTestMyClass::class 346 | ); 347 | 348 | $this->expectException(BailableTestingException::class); 349 | 350 | $myClass->doSomething12(); 351 | } 352 | 353 | } 354 | -------------------------------------------------------------------------------- /tests/DeferDeferrableFunctionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 45 | "0: first call\n0: deferred call\n1: first call\n1: deferred call\n", 46 | $result 47 | ); 48 | } 49 | 50 | public function testDeferPattern2() 51 | { 52 | ob_start(); 53 | deferrable(function () { 54 | defer(function () { 55 | echo "0: deferred call\n"; 56 | }); 57 | echo "0: first call\n"; 58 | })(); 59 | 60 | $result = ob_get_clean(); 61 | 62 | $this->assertSame( 63 | "0: first call\n0: deferred call\n", 64 | $result 65 | ); 66 | } 67 | 68 | public function testDeferPattern3() 69 | { 70 | ob_start(); 71 | deferrable(function () { 72 | defer(function () { 73 | echo "1: deferred call\n"; 74 | }); 75 | echo "1: first call\n"; 76 | })(); 77 | 78 | $result = ob_get_clean(); 79 | 80 | $this->assertSame( 81 | "1: first call\n1: deferred call\n", 82 | $result 83 | ); 84 | } 85 | 86 | public function testDeferPattern4() 87 | { 88 | ob_start(); 89 | deferrable(function () { 90 | defer(function () { 91 | echo "1: deferred call\n"; 92 | }); 93 | defer(function () { 94 | echo "1: deferred call2\n"; 95 | }); 96 | echo "1: first call\n"; 97 | })(); 98 | 99 | $result = ob_get_clean(); 100 | 101 | $this->assertSame( 102 | "1: first call\n1: deferred call2\n1: deferred call\n", 103 | $result 104 | ); 105 | } 106 | 107 | public function testDeferPattern5() 108 | { 109 | $result = deferrable(function () { 110 | defer(function () { 111 | // do something 112 | }); 113 | return 'Return value'; 114 | })(); 115 | 116 | $this->assertSame( 117 | "Return value", 118 | $result 119 | ); 120 | } 121 | 122 | public function testDeferPattern6() 123 | { 124 | $result = deferrable(function () { 125 | $handle = fopen('php://memory', 'r'); 126 | defer(function () use ($handle) { 127 | fclose($handle); 128 | }); 129 | return 'Return value'; 130 | })(); 131 | 132 | $this->assertSame( 133 | "Return value", 134 | $result 135 | ); 136 | } 137 | 138 | public function testDeferPattern7() 139 | { 140 | $this->expectException(DeferrableFunctionTestingException::class); 141 | $result = deferrable( 142 | DeferBailableScope::of(function () { 143 | defer(function () { 144 | throw new \Exception('exception 2'); 145 | }); 146 | 147 | defer(function () { 148 | throw new DeferrableFunctionTestingException('exception 1'); 149 | }); 150 | }) 151 | )(); 152 | } 153 | 154 | public function testDeferPattern10() 155 | { 156 | $this->expectException(MergedDeferringException::class); 157 | $result = deferrable(function () { 158 | defer(function () { 159 | throw new \Exception('exception 2'); 160 | }); 161 | 162 | defer(function () { 163 | throw new DeferrableFunctionTestingException('exception 1'); 164 | }); 165 | })(); 166 | } 167 | 168 | public function testDeferPattern9() 169 | { 170 | $this->expectException(BailableDeferrableFunctionTestingException::class); 171 | $result = deferrable(function () { 172 | defer(function () { 173 | throw new \Exception('exception 3'); 174 | }); 175 | 176 | defer(function () { 177 | throw new BailableDeferrableFunctionTestingException('exception 2'); 178 | }); 179 | 180 | defer(function () { 181 | throw new DeferrableFunctionTestingException('exception 1'); 182 | }); 183 | })(); 184 | } 185 | } 186 | --------------------------------------------------------------------------------