Completed
Push — master ( 508240...149fba )
by Vasily
03:46
created

Sessions::sessionStartNew()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 20
rs 9.4286
cc 2
eloc 16
nc 1
nop 1
1
<?php
2
namespace PHPDaemon\Traits;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Debug;
6
use PHPDaemon\Core\CallbackWrapper;
7
use PHPDaemon\FS\File;
8
use PHPDaemon\FS\FileSystem;
9
10
/**
11
 * Sessions
12
 * @package PHPDaemon\Traits
13
 * @author  Zorin Vasily <[email protected]>
14
 */
15
trait Sessions {
16
	/**
17
	 * @var string Session ID
18
	 */
19
	protected $sessionId;
20
21
	/**
22
	 * @var integer
23
	 */
24
	protected $sessionStartTimeout = 10;
25
26
	/**
27
	 * @var boolean
28
	 */
29
	protected $sessionStarted = false;
30
	
31
	/**
32
	 * @var boolean
33
	 */
34
	protected $sessionFlushing = false;
35
	
36
	/**
37
	 * @var resource
38
	 */
39
	protected $sessionFp;
40
41
	/**
42
	 * @var string
43
	 */
44
	protected $sessionPrefix = 'sess_';
45
46
	/**
47
	 * Is session started?
48
	 * @return boolean
49
	 */
50
	public function sessionStarted() {
51
		return $this->sessionStarted;
52
	}
53
54
	/**
55
	 * Deferred event 'onSessionStart'
56
	 * @return callable
57
	 */
58
	public function onSessionStartEvent() {
59
		return function ($sessionStartEvent) {
60
			/** @var \PHPDaemon\Core\DeferredEvent $sessionStartEvent */
61
			$name = ini_get('session.name');
62
			$sid = $this->getCookieStr($name);
0 ignored issues
show
Bug introduced by
It seems like getCookieStr() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
63
			if ($sid === '') {
64
				$this->sessionStartNew(function ($success) use ($sessionStartEvent) {
65
					$sessionStartEvent->setResult($success);
66
				});
67
				return;
68
			}
69
			$this->onSessionRead(function ($session) use ($sessionStartEvent) {
0 ignored issues
show
Bug introduced by
The method onSessionRead() does not exist on PHPDaemon\Traits\Sessions. Did you maybe mean onSessionReadEvent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Unused Code introduced by
The parameter $session is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
70
				if ($this->getSessionState() === null) {
71
					$this->sessionStartNew(function ($success) use ($sessionStartEvent) {
72
						$sessionStartEvent->setResult($success);
73
					});
74
					return;
75
				}
76
				$sessionStartEvent->setResult(true);
77
			});
78
		};
79
	}
80
81
	/**
82
	 * Deferred event 'onSessionRead'
83
	 * @return callable
84
	 */
85
	public function onSessionReadEvent() {
86
		return function ($sessionEvent) {
87
			/** @var \PHPDaemon\Core\DeferredEvent $sessionEvent */
88
			$name = ini_get('session.name');
89
			$sid  = $this->getCookieStr($name);
0 ignored issues
show
Bug introduced by
It seems like getCookieStr() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
90
			if ($sid === '') {
91
				$sessionEvent->setResult(false);
92
				return;
93
			}
94
			if ($this->getSessionState() !== null) {
95
				$sessionEvent->setResult(true);
96
				return;
97
			}
98
			$this->sessionRead($sid, function ($data) use ($sessionEvent, $sid) {
99
				$canDecode = $data !== false && $this->sessionDecode($data);
100
				$sessionEvent->setResult($canDecode);
101
			});
102
		};
103
	}
104
105
	/**
106
	 * Reads session data
107
	 * @param  string   $sid Session ID
108
	 * @param  callable $cb  Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
109
	 * @return void
110
	 */
111
	public function sessionRead($sid, $cb = null) {
112
		FileSystem::open(FileSystem::genRndTempnamPrefix(session_save_path(), $this->sessionPrefix) . basename($sid), 'r+!', function ($fp) use ($cb) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 145 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
113
			if (!$fp) {
114
				call_user_func($cb, false);
115
				return;
116
			}
117
			$fp->readAll(function ($fp, $data) use ($cb) {
118
				$this->sessionFp = $fp;
119
				call_user_func($cb, $data);
120
			});
121
		});
122
	}
123
124
	/**
125
	 * Commmit session data
126
	 * @param  callable $cb Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
127
	 * @return void
128
	 */
129
	public function sessionCommit($cb = null) {
130
		if (!$this->sessionFp || $this->sessionFlushing) {
131
			if ($cb) {
132
				call_user_func($cb, false);
133
			}
134
			return;
135
		}
136
		$this->sessionFlushing = true;
137
		$data                  = $this->sessionEncode();
138
		$l                     = strlen($data);
139
		$cb = CallbackWrapper::wrap($cb);
0 ignored issues
show
Bug introduced by
It seems like $cb defined by \PHPDaemon\Core\CallbackWrapper::wrap($cb) on line 139 can also be of type null; however, PHPDaemon\Core\CallbackWrapper::wrap() does only seem to accept callable, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
140
		$this->sessionFp->write($data, function ($file, $result) use ($l, $cb) {
0 ignored issues
show
Unused Code introduced by
The parameter $result is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
141
			$file->truncate($l, function ($file, $result) use ($cb) {
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $result is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
142
				$this->sessionFlushing = false;
143
				if ($cb) {
144
					call_user_func($cb, true);
145
				}
146
			});
147
		});
148
	}
149
150
	/**
151
	 * Session start
152
	 * @param  boolean $force_start
153
	 * @return void
154
	 */
155
	protected function sessionStart($force_start = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $force_start is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
156
		if ($this->sessionStarted) {
157
			return;
158
		}
159
		$this->sessionStarted = true;
160
		if (!$this instanceof \PHPDaemon\HTTPRequest\Generic) {
161
			Daemon::log('Called ' . get_class($this). '(trait \PHPDaemon\Traits\Sessions)->sessionStart() outside of Request. You should use onSessionStart.');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 150 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
162
			return;
163
		}
164
		$f = true; // hack to avoid a sort of "race condition"
165
		$this->onSessionStart(function ($event) use (&$f) {
0 ignored issues
show
Bug introduced by
The method onSessionStart() does not exist on PHPDaemon\HTTPRequest\Generic. Did you maybe mean onSessionStartEvent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
166
			$f = false;
167
			$this->wakeup();
0 ignored issues
show
Bug introduced by
It seems like wakeup() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
168
		});
169
		if ($f) {
170
			$this->sleep($this->sessionStartTimeout);
0 ignored issues
show
Documentation introduced by
The property $sessionStartTimeout is declared protected in PHPDaemon\Traits\Sessions. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
171
		}
172
	}
173
174
	/**
175
	 * Start new session
176
	 * @param  callable $cb Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
177
	 * @return void
178
	 */
179
	protected function sessionStartNew($cb = null) {
180
		FileSystem::tempnam(session_save_path(), $this->sessionPrefix, function ($fp) use ($cb) {
181
			if (!$fp) {
182
				call_user_func($cb, false);
183
				return;
184
			}
185
			$this->sessionFp = $fp;
186
			$this->sessionId = substr(basename($fp->path), strlen($this->sessionPrefix));
187
			$this->setcookie(
0 ignored issues
show
Bug introduced by
It seems like setcookie() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
188
				ini_get('session.name')
189
				, $this->sessionId
190
				, ini_get('session.cookie_lifetime')
191
				, ini_get('session.cookie_path')
192
				, ini_get('session.cookie_domain')
193
				, ini_get('session.cookie_secure')
194
				, ini_get('session.cookie_httponly')
195
			);
196
			call_user_func($cb, true);
197
		});
198
	}
199
200
	/**
201
	 * Encodes session data
202
	 * @return string|false
203
	 */
204
	protected function sessionEncode() {
205
		$type = ini_get('session.serialize_handler');
206
		if ($type === 'php') {
207
			return $this->serialize_php($this->getSessionState());
208
		}
209
		if ($type === 'php_binary') {
210
			return igbinary_serialize($this->getSessionState());
211
		}
212
		return false;
213
	}
214
	
215
	/**
216
	 * Set session state
217
	 * @param mixed $var
218
	 * @return void
219
	 */
220
	protected function setSessionState($var) {
221
		$this->attrs->session = $var;
0 ignored issues
show
Bug introduced by
The property attrs does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
222
	}
223
224
	/**
225
	 * Get session state
226
	 * @return mixed
227
	 */
228
	protected function getSessionState() {
229
		return $this->attrs->session;
230
	}
231
232
	/**
233
	 * Decodes session data
234
	 * @param  string  $str Data
235
	 * @return boolean
236
	 */
237
	protected function sessionDecode($str) {
238
		$type = ini_get('session.serialize_handler');
239
		if ($type === 'php') {
240
			$this->setSessionState($this->unserialize_php($str));
241
			return true;
242
		}
243
		if ($type === 'php_binary') {
244
			$this->setSessionState(igbinary_unserialize($str));
245
			return true;
246
		}
247
		return false;
248
	}
249
250
	/**
251
	 * session_encode() - clone, which not require session_start()
252
	 * @see    http://www.php.net/manual/en/function.session-encode.php
253
	 * @param  array  $array
254
	 * @return string
255
	 */
256
	public function serialize_php($array) {
257
		$raw = '';
258
		$line = 0;
259
		$keys = array_keys($array);
260
		foreach ($keys as $key) {
261
			$value = $array[$key];
262
			$line++;
263
			$raw .= $key . '|';
264
			if (is_array($value) && isset($value['huge_recursion_blocker_we_hope'])) {
265
				$raw .= 'R:' . $value['huge_recursion_blocker_we_hope'] . ';';
266
			} else {
267
				$raw .= serialize($value);
268
			}
269
			$array[$key] = array('huge_recursion_blocker_we_hope' => $line);
270
		}
271
272
		return $raw;
273
	}
274
275
	/**
276
	 * session_decode() - clone, which not require session_start()
277
	 * @see    http://www.php.net/manual/en/function.session-decode.php#108037
278
	 * @param  string $session_data
279
	 * @return array
280
	 */
281
	protected function unserialize_php($session_data) {
282
		$return_data = array();
283
		$offset = 0;
284
		while ($offset < strlen($session_data)) {
285
			if (!strstr(substr($session_data, $offset), "|")) {
286
				return $return_data;
287
				//throw new \Exception("invalid session data, remaining: " . substr($session_data, $offset));
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
288
			}
289
			$pos = strpos($session_data, "|", $offset);
290
			$num = $pos - $offset;
291
			$varname = substr($session_data, $offset, $num);
292
			$offset += $num + 1;
293
			$data = unserialize(substr($session_data, $offset));
294
			$return_data[$varname] = $data;
295
			$offset += strlen(serialize($data));
296
		}
297
		return $return_data;
298
	}
299
}
300