Completed
Push — master ( 978d70...a938d7 )
by Joshua
33s
created

TaskManager::filterResponse()   C

Complexity

Conditions 8
Paths 10

Size

Total Lines 43
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 23
nc 10
nop 1
1
<?php
2
3
namespace As3\Bundle\PostProcessBundle\Task;
4
5
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
6
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
7
use Symfony\Component\HttpFoundation\Response;
8
use As3\Bundle\PostProcessBundle\Plugins\PluginInterface;
9
10
/**
11
 * The task manager is responsible for running PHP code after a response is sent to the browser.
12
 * This facilitates executing resource and/or time itensive processes without the user having to wait for completion.
13
 * Code is executed via TaskInterface implementations, and can be prioritized.
14
 *
15
 * @author Jacob Bare <[email protected]>
16
 * @author Josh Worden <[email protected]>
17
 */
18
class TaskManager
19
{
20
    /**
21
     * All registered tasks to run on shutdown, after the response is sent.
22
     *
23
     * @see TaskManager::addTask() To add a TaskInterface class
24
     * @var TaskInterface[]
25
     */
26
    private $tasks = [];
27
28
    /**
29
     * All enabled plugins to be executed on kernel.response and kernel.terminate events.
30
     *
31
     * @see TaskManager::addPlugin()
32
     * @var PluginInterface[]
33
     */
34
    private $plugins = [];
35
36
    /**
37
     * @var boolean
38
     */
39
    private $masterRequest = true;
40
41
    /**
42
     * Determines whether Tasks should be executed
43
     *
44
     * @var bool
45
     */
46
    private $enabled = true;
47
48
    /**
49
     * Adds a registered task.
50
     * Tasks can be added by:
51
     * 1. Tagging TaskInterface services through DI.
52
     * 2. Implementing a TaskInterface class and mannually adding it
53
     *
54
     * All tasks must be registered before Response::send() is called by Symfony.
55
     *
56
     * @param  TaskInterface    $task       The task to run
57
     * @param  int              $priority   The priority: a higher value runs first. Default 0.
58
     * @return self
59
     */
60
    public function addTask(TaskInterface $task, $priority = 0)
61
    {
62
        $prioritized = [
63
            'object'    => $task,
64
            'priority'  => (Integer) $priority,
65
        ];
66
        $this->tasks[get_class($task)] = $prioritized;
67
68
        $sortFunc = function ($a, $b) {
69
            return $a['priority'] > $b['priority'] ? -1 : 1;
70
        };
71
        uasort($this->tasks, $sortFunc);
72
        return $this;
73
    }
74
75
    /**
76
     * Adds an enabled plugin to be utilized in relevant methods below.
77
     *
78
     * @see TaskManager::execute()
79
     * @see TaskManager::filterResponse()
80
     *
81
     * @param PluginInterface   $plugin     The plugin to add
82
     * @return self
83
     */
84
    public function addPlugin(PluginInterface $plugin)
85
    {
86
        $this->plugins[] = $plugin;
87
        return $this;
88
    }
89
90
    /**
91
     * Gets all registered tasks
92
     *
93
     * @return TaskInterface[]
94
     */
95
    public function getTasks()
96
    {
97
        $tasks = [];
98
        foreach ($this->tasks as $task) {
99
            $tasks[] = $task['object'];
100
        }
101
        return $tasks;
102
    }
103
104
    /**
105
     * Gets all registered tasks
106
     *
107
     * @return PluginInterface[]
108
     */
109
    public function getPlugins()
110
    {
111
        return $this->plugins;
112
    }
113
114
    /**
115
     * Determines if tasks are registered
116
     *
117
     * @return bool
118
     */
119
    public function hasTasks()
120
    {
121
        $tasks = $this->getTasks();
122
        return !empty($tasks);
123
    }
124
125
    /**
126
     * Determines if plugins are registered
127
     *
128
     * @return bool
129
     */
130
    public function hasPlugins()
131
    {
132
        $plugins = $this->getPlugins();
133
        return !empty($plugins);
134
    }
135
136
    /**
137
     * Enables the TaskManager for executing Tasks
138
     *
139
     * @return self
140
     */
141
    public function enable()
142
    {
143
        $this->enabled = true;
144
        return $this;
145
    }
146
147
    /**
148
     * Disables the TaskManager from executing Tasks
149
     *
150
     * @return self
151
     */
152
    public function disable()
153
    {
154
        $this->enabled = false;
155
        return $this;
156
    }
157
158
    /**
159
     * Determines if the TaskManager is enabled for executing Tasks
160
     *
161
     * @return bool
162
     */
163
    public function isEnabled()
164
    {
165
        return true === $this->enabled;
166
    }
167
168
    /**
169
     * Executes the post-response Tasks.
170
     * Is called via Symfony's kernel.terminate event.
171
     *
172
     * @param   PostResponseEvent   $event
173
     */
174
    public function execute(PostResponseEvent $event)
175
    {
176
        if ($this->isEnabled() && $this->masterRequest) {
177
            session_write_close();
178
179
            // Allow any loaded plugins to fire
180
            foreach ($this->getPlugins() as $plugin) {
181
                $plugin->execute($event);
182
            }
183
184
            foreach ($this->getTasks() as $task) {
185
                $task->run();
186
            }
187
        }
188
    }
189
190
    /**
191
     * Sets the HTTP headers required to execute post-response Tasks.
192
     * Is called via Symfony's kernel.response event.
193
     *
194
     * @param   FilterResponseEvent     $event
195
     * @return  Response
196
     */
197
    public function filterResponse(FilterResponseEvent $event)
198
    {
199
        $response = $event->getResponse();
200
201
        if (!$event->isMasterRequest()) {
202
            // Return the regular response
203
            $this->masterRequest = false;
204
            return $response;
205
        }
206
        $this->masterRequest = true;
207
208
        if ((!$this->hasTasks() && !$this->hasPlugins()) || !$this->isEnabled()) {
209
            // Nothing to process. Return response.
210
            return $response;
211
        }
212
213
        ignore_user_abort(true);
214
215
        // Allow any loaded plugins to fire
216
        foreach ($this->getPlugins() as $plugin) {
217
            $plugin->filterResponse($response);
218
        }
219
220
        $content = $response->getContent();
221
        $length = strlen($response->getContent());
222
223
        $headers = [
224
            'connection'        => 'close',
225
            'content-length'    => $length,
226
        ];
227
228
        // Ensures minimum output has been set, otherwise PHP will not flush properly and tasks will hang the browser.
229
        $minOutputLen = PHP_INT_SIZE * 1024;
230
        if ($length < $minOutputLen) {
231
            $padding = $minOutputLen - $length + 1;
232
            $response->setContent($content.str_pad('', $padding));
233
        }
234
235
        foreach ($headers as $key => $value) {
236
            $response->headers->set($key, $value);
237
        }
238
        return $response;
239
    }
240
}
241