Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ScopedCallback
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
6 / 6
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 consume
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cancel
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 newScopedIgnoreUserAbort
n/a
0 / 0
n/a
0 / 0
2
 __destruct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 __sleep
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __wakeup
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * This file deals with RAII style scoped callbacks.
4 *
5 * Copyright (C) 2016 Aaron Schulz <aschulz@wikimedia.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 *
22 * @file
23 */
24
25namespace Wikimedia;
26
27use InvalidArgumentException;
28use UnexpectedValueException;
29
30/**
31 * Make a callback run when a dummy object leaves the scope.
32 */
33class ScopedCallback {
34    /** @var callable|null */
35    protected $callback;
36    /** @var array */
37    protected $params;
38
39    /**
40     * @param callable|null $callback
41     * @param array $params Callback arguments (since 1.0.0, MediaWiki 1.25)
42     */
43    public function __construct( $callback, array $params = [] ) {
44        if ( $callback !== null && !is_callable( $callback ) ) {
45            throw new InvalidArgumentException( 'Provided callback is not valid.' );
46        }
47        $this->callback = $callback;
48        $this->params = $params;
49    }
50
51    /**
52     * Trigger a scoped callback and destroy it.
53     * This is the same as just setting it to null.
54     *
55     * @param ScopedCallback|null &$sc
56     */
57    public static function consume( ScopedCallback &$sc = null ) {
58        $sc = null;
59    }
60
61    /**
62     * Destroy a scoped callback without triggering it.
63     *
64     * @param ScopedCallback|null &$sc
65     */
66    public static function cancel( ScopedCallback &$sc = null ) {
67        if ( $sc ) {
68            $sc->callback = null;
69        }
70        $sc = null;
71    }
72
73    /**
74     * Make PHP ignore user aborts/disconnects until the returned
75     * value leaves scope. This returns null and does nothing in CLI mode.
76     *
77     * @since 3.0.0
78     * @return ScopedCallback|null
79     *
80     * @codeCoverageIgnore CI is only run via CLI, so this will never be exercised.
81     * Also no benefit testing a function just returns null.
82     */
83    public static function newScopedIgnoreUserAbort() {
84        // ignore_user_abort previously caused an infinite loop on CLI
85        // https://bugs.php.net/bug.php?id=47540
86        if ( PHP_SAPI != 'cli' ) {
87            // avoid half-finished operations
88            $old = ignore_user_abort( true );
89            return new ScopedCallback( static function () use ( $old ) {
90                ignore_user_abort( (bool)$old );
91            } );
92        }
93
94        return null;
95    }
96
97    /**
98     * Trigger the callback when it leaves scope.
99     */
100    function __destruct() {
101        if ( $this->callback !== null ) {
102            call_user_func_array( $this->callback, $this->params );
103        }
104    }
105
106    /**
107     * Do not allow this class to be serialized
108     * @return never
109     */
110    function __sleep() {
111        throw new UnexpectedValueException( __CLASS__ . ' cannot be serialized' );
112    }
113
114    /**
115     * Protect the caller against arbitrary code execution
116     * @return never
117     */
118    function __wakeup() {
119        $this->callback = null;
120        throw new UnexpectedValueException( __CLASS__ . ' cannot be unserialized' );
121    }
122}