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