1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\HttpFoundation\Session\Storage;
13
14/**
15 * MockFileSessionStorage is used to mock sessions for
16 * functional testing when done in a single PHP process.
17 *
18 * No PHP session is actually started since a session can be initialized
19 * and shutdown only once per PHP execution cycle and this class does
20 * not pollute any session related globals, including session_*() functions
21 * or session.* PHP ini directives.
22 *
23 * @author Drak <drak@zikula.org>
24 */
25class MockFileSessionStorage extends MockArraySessionStorage
26{
27    private $savePath;
28
29    /**
30     * @param string $savePath Path of directory to save session files
31     */
32    public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
33    {
34        if (null === $savePath) {
35            $savePath = sys_get_temp_dir();
36        }
37
38        if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
39            throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $savePath));
40        }
41
42        $this->savePath = $savePath;
43
44        parent::__construct($name, $metaBag);
45    }
46
47    /**
48     * {@inheritdoc}
49     */
50    public function start()
51    {
52        if ($this->started) {
53            return true;
54        }
55
56        if (!$this->id) {
57            $this->id = $this->generateId();
58        }
59
60        $this->read();
61
62        $this->started = true;
63
64        return true;
65    }
66
67    /**
68     * {@inheritdoc}
69     */
70    public function regenerate(bool $destroy = false, int $lifetime = null)
71    {
72        if (!$this->started) {
73            $this->start();
74        }
75
76        if ($destroy) {
77            $this->destroy();
78        }
79
80        return parent::regenerate($destroy, $lifetime);
81    }
82
83    /**
84     * {@inheritdoc}
85     */
86    public function save()
87    {
88        if (!$this->started) {
89            throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.');
90        }
91
92        $data = $this->data;
93
94        foreach ($this->bags as $bag) {
95            if (empty($data[$key = $bag->getStorageKey()])) {
96                unset($data[$key]);
97            }
98        }
99        if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) {
100            unset($data[$key]);
101        }
102
103        try {
104            if ($data) {
105                file_put_contents($this->getFilePath(), serialize($data));
106            } else {
107                $this->destroy();
108            }
109        } finally {
110            $this->data = $data;
111        }
112
113        // this is needed for Silex, where the session object is re-used across requests
114        // in functional tests. In Symfony, the container is rebooted, so we don't have
115        // this issue
116        $this->started = false;
117    }
118
119    /**
120     * Deletes a session from persistent storage.
121     * Deliberately leaves session data in memory intact.
122     */
123    private function destroy(): void
124    {
125        if (is_file($this->getFilePath())) {
126            unlink($this->getFilePath());
127        }
128    }
129
130    /**
131     * Calculate path to file.
132     */
133    private function getFilePath(): string
134    {
135        return $this->savePath.'/'.$this->id.'.mocksess';
136    }
137
138    /**
139     * Reads session from storage and loads session.
140     */
141    private function read(): void
142    {
143        $filePath = $this->getFilePath();
144        $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : [];
145
146        $this->loadSession();
147    }
148}
149