Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/1_Bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: "Bug report"
about: "Report something that's broken. Please ensure your Laravel version is still supported: https://laravel.com/docs/releases#support-policy"
---

<!-- DO NOT THROW THIS AWAY -->
<!-- Fill out the FULL versions with patch versions -->

- Serializable Closure Version: #.#.#
- Laravel Version: #.#.#
- PHP Version: #.#.#
- Database Driver & Version:

### Description:


### Steps To Reproduce:
4 changes: 4 additions & 0 deletions .github/ISSUE_TEMPLATE/2_Feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
name: "Feature request"
about: 'For ideas or feature requests: please make a pull request or open an issue'
---
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Support Questions & Other
url: https://laravel.com/docs/contributions#support-questions
about: 'This repository is only for reporting bugs. If you have a question or need help using the library, click:'
- name: Documentation issue
url: https://github.com/laravel/docs
about: For documentation issues, open a pull request at the laravel/docs repository
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@

Laravel Serializable Closure provides an easy and secure way to **serialize closures in PHP**.

## Installation / Usage
## Official Documentation

### Installation

> **Requires [PHP 7.4+](https://php.net/releases/)**

First, install Laravel Serializable Closure via the [Composer](https://getcomposer.org/) package manager:

```bash
composer require laravel/serializable-closure --dev
composer require laravel/serializable-closure
```

Then, you may serialize a closure this way:
### Usage

You may serialize a closure this way:

```php
use Laravel\SerializableClosure\SerializableClosure;
Expand All @@ -45,10 +49,22 @@ $closure = unserialize($serialized)->getClosure();
echo $closure(); // james;
```

## Caveats
### Caveats

Creating **anonymous classes** within closures is not supported.

## Contributing

Thank you for considering contributing to BrowserKit Testing! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).

## Code of Conduct

In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).

## Security Vulnerabilities

- Creating **anonymous classes** within closures is not supported.
Please review [our security policy](https://github.com/laravel/browser-kit-testing/security/policy) on how to report security vulnerabilities.

## License

Seriazable Closure is open-sourced software licensed under the [MIT license](LICENSE.md).
Serializable Closure is open-sourced software licensed under the [MIT license](LICENSE.md).
2 changes: 0 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ parameters:
level: max
paths:
- src

checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
reportUnmatchedIgnoredErrors: true

excludePaths:
- 'src/Support/ReflectionClosure'
- 'src/Serializers/Native'
55 changes: 50 additions & 5 deletions src/Serializers/Native.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Native implements Serializable
public function __construct(Closure $closure)
{
$this->closure = $closure;

if (static::$context !== null) {
$this->scope = static::$context->scope;
$this->scope->toSerialize++;
Expand Down Expand Up @@ -150,11 +151,11 @@ public function __serialize()
$this->mapByReference($use);

$data = [
'use' => $use,
'use' => $use,
'function' => $code,
'scope' => $scope,
'this' => $object,
'self' => $this->reference,
'scope' => $scope,
'this' => $object,
'self' => $this->reference,
];

if (! --$this->scope->serializations && ! --$this->scope->toSerialize) {
Expand Down Expand Up @@ -186,7 +187,9 @@ public function __unserialize($data)
}

$this->mapPointers($this->code['use']);

extract($this->code['use'], EXTR_OVERWRITE | EXTR_REFS);

$this->scope = null;
}

Expand Down Expand Up @@ -226,13 +229,16 @@ public static function wrapClosures(&$data, $storage = null)
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}

$data[self::ARRAY_RECURSIVE_KEY] = true;

foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
}
static::wrapClosures($value, $storage);
}

unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
Expand All @@ -241,42 +247,54 @@ public static function wrapClosures(&$data, $storage = null)

return;
}

$data = $storage[$data] = clone $data;

foreach ($data as &$value) {
static::wrapClosures($value, $storage);
}

unset($value);
} elseif (is_object($data) && ! $data instanceof static) {
if (isset($storage[$data])) {
$data = $storage[$data];

return;
}

$instance = $data;
$reflection = new ReflectionObject($instance);

if (! $reflection->isUserDefined()) {
$storage[$instance] = $data;

return;
}

$storage[$instance] = $data = $reflection->newInstanceWithoutConstructor();

do {
if (! $reflection->isUserDefined()) {
break;
}

foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
continue;
}

$property->setAccessible(true);

if (PHP_VERSION >= 7.4 && ! $property->isInitialized($instance)) {
continue;
}

$value = $property->getValue($instance);

if (is_array($value) || is_object($value)) {
static::wrapClosures($value, $storage);
}

$property->setValue($data, $value);
}
} while ($reflection = $reflection->getParentClass());
Expand Down Expand Up @@ -314,7 +332,9 @@ protected function mapPointers(&$data)
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}

$data[self::ARRAY_RECURSIVE_KEY] = true;

foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
Expand All @@ -326,45 +346,56 @@ protected function mapPointers(&$data)
$this->mapPointers($value);
}
}

unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
if (isset($scope[$data])) {
return;
}

$scope[$data] = true;

foreach ($data as $key => &$value) {
if ($value instanceof SelfReference && $value->hash === $this->code['self']) {
$data->{$key} = &$this->closure;
} elseif (is_array($value) || is_object($value)) {
$this->mapPointers($value);
}
}

unset($value);
} elseif (is_object($data) && ! ($data instanceof Closure)) {
if (isset($scope[$data])) {
return;
}

$scope[$data] = true;
$reflection = new ReflectionObject($data);

do {
if (! $reflection->isUserDefined()) {
break;
}

foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
continue;
}

$property->setAccessible(true);

if (PHP_VERSION >= 7.4 && ! $property->isInitialized($data)) {
continue;
}

$item = $property->getValue($data);

if ($item instanceof SerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) {
$this->code['objects'][] = [
'instance' => $data,
'property' => $property,
'object' => $item instanceof SelfReference ? $this : $item,
'object' => $item instanceof SelfReference ? $this : $item,
];
} elseif (is_array($item) || is_object($item)) {
$this->mapPointers($item);
Expand Down Expand Up @@ -408,13 +439,17 @@ protected function mapByReference(&$data)
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}

$data[self::ARRAY_RECURSIVE_KEY] = true;

foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
}

$this->mapByReference($value);
}

unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
Expand All @@ -423,12 +458,14 @@ protected function mapByReference(&$data)

return;
}

$instance = $data;
$this->scope[$instance] = $data = clone $data;

foreach ($data as &$value) {
$this->mapByReference($value);
}

unset($value);
} elseif (is_object($data) && ! $data instanceof SerializableClosure) {
if (isset($this->scope[$data])) {
Expand All @@ -439,29 +476,37 @@ protected function mapByReference(&$data)

$instance = $data;
$reflection = new ReflectionObject($data);

if (! $reflection->isUserDefined()) {
$this->scope[$instance] = $data;

return;
}

$this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor();

do {
if (! $reflection->isUserDefined()) {
break;
}

foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
continue;
}

$property->setAccessible(true);

if (PHP_VERSION >= 7.4 && ! $property->isInitialized($instance)) {
continue;
}

$value = $property->getValue($instance);

if (is_array($value) || is_object($value)) {
$this->mapByReference($value);
}

$property->setValue($data, $value);
}
} while ($reflection = $reflection->getParentClass());
Expand Down
2 changes: 1 addition & 1 deletion src/Signers/Hmac.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function sign($serialized)
{
return [
'serializable' => $serialized,
'hash' => base64_encode(hash_hmac('sha256', $serialized, $this->secret, true)),
'hash' => base64_encode(hash_hmac('sha256', $serialized, $this->secret, true)),
];
}

Expand Down
1 change: 1 addition & 0 deletions src/Support/ClosureStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public function stream_open($path, $mode, $options, &$opened_path)
public function stream_read($count)
{
$value = substr($this->content, $this->pointer, $count);

$this->pointer += $count;

return $value;
Expand Down
3 changes: 3 additions & 0 deletions src/Support/ReflectionClosure.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ public function isShortClosure()
{
if ($this->isShortClosure === null) {
$code = $this->getCode();

if ($this->isStatic()) {
$code = substr($code, 6);
}

$this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn';
}

Expand Down Expand Up @@ -122,6 +124,7 @@ public function getCode()

for ($i = 0, $l = count($tokens); $i < $l; $i++) {
$token = $tokens[$i];

switch ($state) {
case 'start':
if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
Expand Down