1 of 133

Crazy Fun Experiments with PHP�(Not for Production)

SymfonyCon Amsterdam 2019

@ZanBaldwin

2 of 133

https://bit.ly/2QF3zh0

3 of 133

About This Talk

4 of 133

About This Talk

what?

huh?

5 of 133

About This Talk

what?

huh?

I wonder...

what if?

6 of 133

About This Talk

  • 🚀 Lots of information, quickly

what?

huh?

I wonder...

what if?

7 of 133

About This Talk

  • 🚀 Lots of information, quickly
  • 📝 Slides, including speaker notes, are available to download

what?

huh?

I wonder...

what if?

8 of 133

About This Talk

  • 🚀 Lots of information, quickly
  • 📝 Slides, including speaker notes, are available to download
  • 💬 I’ll be outside in the hallway during the next talk for anyone who wants to discuss this talk

what?

huh?

I wonder...

what if?

9 of 133

About This Talk

  • 🚀 Lots of information, quickly
  • 📝 Slides, including speaker notes, are available to download
  • 💬 I’ll be outside in the hallway during the next talk for anyone who wants to discuss this talk
  • 🤓 This is a technical talk, but don’t worry about keeping up with every code example.

what?

huh?

I wonder...

what if?

10 of 133

PERFORMANCE

11 of 133

Streams

12 of 133

Computer Science

A sequence of data elements made available over time.

Can have finite size, or be continuous.

Read from & written to in chunks.

13 of 133

Computer Science

A sequence of data elements made available over time.

Can have finite size, or be continuous.

Read from & written to in chunks.

PHP Streams

A way of generalizing file, network and other operations which share a common set of functions.

A stream is a resource object that can be read from or written to in a linear fashion.

14 of 133

You’ve Used Streams

  • Guzzle
  • Sockets
  • Accessed the filesystem
  • Dealt with a $_POST request

15 of 133

file_get_contents(‘ hello.txt’);

16 of 133

file_get_contents(‘file://hello.txt’);

17 of 133

Supported Protocols (Stream Wrappers)

Native PHP

  • file
  • http
  • ftp
  • php
  • zlib
  • bzip2
  • data
  • glob
  • phar

18 of 133

Supported Protocols (Stream Wrappers)

Native PHP

  • file
  • http
  • ftp
  • php
  • zlib
  • bzip2
  • data
  • glob
  • phar

Enabled through PHP extensions

  • zip
  • ssh2
  • rar
  • ogg
  • expect

19 of 133

s3://bucket/key

20 of 133

Esoteric Language

Brainf*ck

21 of 133

Esoteric Language

Brainf*ck

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

hello.bf

22 of 133

require ‘ /app/hello.bf’;

23 of 133

require ‘file:///app/hello.bf’;

24 of 133

require ‘ bf:///app/hello.bf’;

25 of 133

class BfStreamWrapper

{

/** @var resource $context */

public $context;

public static function register(string $filterName): void

{

stream_wrapper_register($filterName, static::class);

}

public function stream_open(string $path, string $mode, int $options, &$opened_path): bool;�� public function stream_stat(): array;�� public function stream_read(int $readNumBytes);�� public function stream_eof(): bool;�� public function stream_close(): bool;���

}

26 of 133

class BfStreamWrapper

{

// ...

private $input = [];

private $output;

private function execute(): void

{

if (is_string($output)) {

return;

}

$result = (new \Brainfuck\Language)->run(

file_get_contents('file://' . $this->uri),

$this->input

);

$this->output = implode('', array_map(function (int $ord): string {

// Brainf*ck returns result in bytes (8-bit integers). Convert to ASCII.

return chr($ord);

}, $result));

}

}

27 of 133

Input?

28 of 133

class BfStreamWrapper

{

/** @var resource $context */

public $context;

public static function register(string $filterName): void

{

stream_wrapper_register($filterName, static::class);

}

public function stream_open(string $path, string $mode, int $options, &$opened_path): bool;�� public function stream_stat(): array;�� public function stream_read(int $readNumBytes);�� public function stream_eof(): bool;�� public function stream_close(): bool;�

public function stream_write(string $data): int;

}

29 of 133

index.php

BfStreamWrapper::register('bf');

$stream = 'bf:///app/hello.bf';

$handle = fopen($stream, ‘r+’);

fwrite($handle, 'script input');

$output = stream_get_contents($handle);

30 of 133

Stream Filters

31 of 133

php_stream_filter Implementation

class BfStreamFilter extends \php_user_filter

{

public static function register(string $filterName): void

{

stream_filter_register($filterName, self::class);

}

// ...

}

32 of 133

php_stream_filter Implementation

public function filter($in, $out, &$consumed, $closing): int

{

while ($bucket = stream_bucket_make_writeable($in)) {

$this->input .= $bucket->data;

}

if ($closing || feof($this->stream)) {

$consumed = strlen($this->input);

$bucket = stream_bucket_new(

$this->stream,

// Has to be static because userland doesn't control instantiation.

static::$twig->render('bf_closure.twig.php', [

'bf_php_value' => var_export($this->input, true),

])

);

stream_bucket_append($out, $bucket);

return \PSFS_PASS_ON;

}

return \PSFS_FEED_ME;

}

33 of 133

php_stream_filter Implementation

public function filter($in, $out, &$consumed, $closing): int

{

while ($bucket = stream_bucket_make_writeable($in)) {

$this->input .= $bucket->data;

}

if ($closing || feof($this->stream)) {

$consumed = strlen($this->input);

$bucket = stream_bucket_new(

$this->stream,

// Has to be static because userland doesn't control instantiation.

static::$twig->render('bf_closure.twig.php', [

'bf_php_value' => var_export($this->input, true),

])

);

stream_bucket_append($out, $bucket);

return \PSFS_PASS_ON;

}

return \PSFS_FEED_ME;

}

34 of 133

bf_closure.twig.php

<?php declare(strict_types=1);

return function (array $input = []): string {

$result = (new \Brainfuck\Language)->run(

,

$input

);

return implode('', array_map(

function (int $ord): string {

// BF returns result in bytes (8-bit

// integers). Convert to ASCII.

return chr($ord);

},

$result

));

};

{% Brainf*ck Closure Code Template %}

��

{{ bf_php_value }}

35 of 133

index.php

BfStreamFilter::register('bf');

$handle = fopen('/app/hello.bf', 'r+');

stream_filter_append($handle, 'bf');

$closureCode = stream_get_contents($handle);

$closure = eval('?>' . $closureCode);

$output = $closure($input);

36 of 133

php://filter

37 of 133

php://filter

/resource=<resource-name>

The resource identifier of the stream you’d like to filter.

38 of 133

php://filter

/resource=<resource-name>���/read=<list,of,filter,names>

The resource identifier of the stream you’d like to filter.��Apply the specified filters when reading data from the stream.

39 of 133

php://filter

/resource=<resource-name>���/read=<list,of,filter,names>���/write=<list,of,filter,names>

The resource identifier of the stream you’d like to filter.��Apply the specified filters when reading data from the stream.��Apply the specified filters when writing data to the stream.

40 of 133

php://filter

BfStreamFilter::register('bf');

$script = '/app/hello.bf';

# php://filter/read=bf/resource=file:///app/hello.bf

$closure = require 'php://filter'

. '/read=bf'

. '/resource=file://' . $script;

$output = $closure($input);

41 of 133

Go! AOP�by Alexander Lisachenko

42 of 133

Aspect-oriented Programming

  • Allows separation of cross-cutting concerns
  • Increases modularity
  • Adds additional behaviour to existing code

43 of 133

Aspect-oriented Programming

  • Allows separation of cross-cutting concerns
  • Increases modularity
  • Adds additional behaviour to existing code

PRODUCTION READY! 🎉

44 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user;

}

45 of 133

function createNewUser(string $email, string $password): UserInterface {����

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();������

return $user;

}

46 of 133

function createNewUser(string $email, string $password): UserInterface {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}�

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();������

return $user;

}

47 of 133

function createNewUser(string $email, string $password): UserInterface {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}�

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();�����

$this->logger->info(‘User created successfully.’, [‘email’ => $email]);

return $user;

}

48 of 133

function createNewUser(string $email, string $password): UserInterface {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}�

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();����

$this->eventDispatcher->dispatch(new UserCreatedEvent($email));

$this->logger->info(‘User created successfully.’, [‘email’ => $email]);

return $user;

}

49 of 133

function createNewUser(string $email, string $password): UserInterface {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}

try {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

} catch (ORMException $e) {

$this->logger->error(‘Could not persist new user.’, [‘email’ => $email]);

throw $e;

}

$this->eventDispatcher->dispatch(new UserCreatedEvent($email));

$this->logger->info(‘User created successfully.’, [‘email’ => $email]);

return $user;

}

50 of 133

function createNewUser(string $email, string $password): UserInterface {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}

try {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

} catch (ORMException $e) {

$this->logger->error(‘Could not persist new user.’, [‘email’ => $email]);

throw $e;

}

$this->eventDispatcher->dispatch(new UserCreatedEvent($email));

$this->logger->info(‘User created successfully.’, [‘email’ => $email]);

return $user;

}

51 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user;

}

52 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user

}

/** @Before(pointcut=”public UserService->*(*)”) */

function checkCurrentUserCanCreateUsers(MethodInvocation $method): void {

if (!$this->security->isGranted('ROLE_ADMIN')) {

throw new AccessDeniedException;

}

}

53 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user

}

/** @Before(pointcut=”public UserService->*(*)”) */

function checkCurrentUserCanCreateUsers(MethodInvocation $method): void {

if (!$this->security->isGranted('ROLE_ADMIN')) {

throw new AccessDeniedException;

}

}

54 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user

}

/** @Before(pointcut=”public UserService->*(*)”) */

function checkCurrentUserCanCreateUsers(MethodInvocation $method): void {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}

}

/** @AfterThrow(pointcut=”public UserService->createNewUser(*)”) */

function handleNewUserDatabaseError(MethodInvocation $method): void {

$this->logger->error('Could not persist new user.', ['email' => $method->getArguments()[0]]);

throw $e;

}

55 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user

}

/** @Before(pointcut=”public UserService->*(*)”) */

function checkCurrentUserCanCreateUsers(MethodInvocation $method): void {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}

}

/** @AfterThrow(pointcut=”public UserService->createNewUser(*)”) */

function handleNewUserDatabaseError(MethodInvocation $method): void {

$this->logger->error('Could not persist new user.', ['email' => $method->getArguments()[0]]);

throw $e;

}

56 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user

}

/** @Before(pointcut=”public UserService->*(*)”) */

function checkCurrentUserCanCreateUsers(MethodInvocation $method): void {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}

}

/** @AfterThrow(pointcut=”public UserService->createNewUser(*)”) */

function handleNewUserDatabaseError(MethodInvocation $method): void {

$this->logger->error(‘Could not persist new user.’, [‘email’ => $method->getArguments()[0]]);

throw $e;

}

/** @After(pointcut=”public UserService->createNewUser(*)”) */

function onNewUserSuccessfullyCreated(MethodInvocation $method): void {

$email = $method->getArguments()[0];

$this->eventDispatcher->dispatch(new UserCreatedEvent($email));

$this->logger->info('User created successfully.', ['email' => $email]);

}

57 of 133

function createNewUser(string $email, string $password): UserInterface {

$user = new User($email, $password);

$this->entityManager->persist($user);

$this->entityManager->flush();

return $user

}

/** @Before(pointcut=”public UserService->*(*)”) */

function checkCurrentUserCanCreateUsers(MethodInvocation $method): void {

if (!$this->security->isGranted(‘ROLE_ADMIN’)) {

throw new AccessDeniedException;

}

}

/** @AfterThrow(pointcut=”public UserService->createNewUser(*)”) */

function handleNewUserDatabaseError(MethodInvocation $method): void {

$this->logger->error(‘Could not persist new user.’, [‘email’ => $method->getArguments()[0]]);

throw $e;

}

/** @After(pointcut=”public UserService->createNewUser(*)”) */

function onNewUserSuccessfullyCreated(MethodInvocation $method): void {

$email = $method->getArguments()[0];

$this->eventDispatcher->dispatch(new UserCreatedEvent($email));

$this->logger->info('User created successfully.', ['email' => $email]);

}

58 of 133

/** @Around(pointcut=”public UserService->createNewUser(*)”) */

function logTimeTakenToCreateUser(MethodInvocation $method): UserInterface {

$start = microtime(true);�

$result = $method->proceed();

$duration = microtime(true) - $start;

$this->logger->log(sprintf('Creating a user took %f seconds', $duration));

// Modify $result before returning?return $result;

}

59 of 133

Source Transformers

60 of 133

Go! AOP’s source transforming process:

61 of 133

Go! AOP’s source transforming process:

  • Go! AOP queries Composer for file location

62 of 133

Go! AOP’s source transforming process:

  • Go! AOP queries Composer for file location
  • Generates metadata about the filter input (original source code) file

63 of 133

Go! AOP’s source transforming process:

  • Go! AOP queries Composer for file location
  • Generates metadata about the filter input (original source code) file
  • Pass metadata through a series of source transformers

64 of 133

Go! AOP’s source transforming process:

  • Go! AOP queries Composer for file location
  • Generates metadata about the filter input (original source code) file
  • Pass metadata through a series of source transformers
  • Dump final manipulated AST back into PHP code

65 of 133

Go! AOP’s source transforming process:

  • Go! AOP queries Composer for file location
  • Generates metadata about the filter input (original source code) file
  • Pass metadata through a series of source transformers
  • Dump final manipulated AST back into PHP code
  • Streams final PHP code string as the output of the filter

66 of 133

Automagically ✨

$userService = new UserService(/* dependencies */);

$user = $userService->createNewUser(

'admin@example.com',

'Password'

);

67 of 133

$executableCodeContents =

transformCodeFilter(

$sourceCodeFileOnDisk

);

68 of 133

Autoloader Overloading

new MyClass???transformCodeFilter()

69 of 133

new MyClass;

70 of 133

new MyClass;

Composer determines source file location:�/var/www/MyClass.php

71 of 133

new MyClass;

Composer determines source file location:�/var/www/MyClass.php

Go! AOP suddenly says “I’d like to interject for a moment…

72 of 133

new MyClass;

Composer determines source file location:�/var/www/MyClass.php

Go! AOP suddenly says “I’d like to interject for a moment…

The framework instructs PHP to instead load:�php://filter � /read=go.source.transforming.loader � /resource=file:///var/www/MyClass.php

73 of 133

new MyClass;

Composer determines source file location:�/var/www/MyClass.php

Go! AOP suddenly says “I’d like to interject for a moment…

The framework instructs PHP to instead load:�php://filter � /read=go.source.transforming.loader � /resource=file:///var/www/MyClass.php

74 of 133

new MyClass;

Composer determines source file location:�/var/www/MyClass.php

Go! AOP suddenly says “I’d like to interject for a moment…

The framework instructs PHP to instead load:�php://filter � /read=go.source.transforming.loader � /resource=file:///var/www/MyClass.php

75 of 133

new MyClass;

Composer determines source file location:�/var/www/MyClass.php

Go! AOP suddenly says “I’d like to interject for a moment…

The framework instructs PHP to instead load:�php://filter � /read=go.source.transforming.loader � /resource=file:///var/www/MyClass.php

76 of 133

new MyClass;

Composer determines source file location:�/var/www/MyClass.php

Go! AOP suddenly says “I’d like to interject for a moment…

The framework instructs PHP to instead load:�php://filter � /read=go.source.transforming.loader � /resource=file:///var/www/MyClass.php

Sneaky! 🐍

77 of 133

spl_autoload_functions();

Go! AOP

Hey, PHP! Give me all of the registered autoloaders please! 🙏

78 of 133

[ Composer\Autoload\ClassLoader, MyAppLoader ]

Go! AOP

Hey, PHP! Give me all of the registered autoloaders please! 🙏

79 of 133

[ Composer\Autoload\ClassLoader, MyAppLoader ]

Hmm, let’s take a closer look at those… 🔍

Go! AOP

Hey, PHP! Give me all of the registered autoloaders please! 🙏

80 of 133

[ Composer\Autoload\ClassLoader, MyAppLoader ]

[ AopComposerLoader(Composer\Autoload\ClassLoader), MyAppLoader ]

Go! AOP

Hey, PHP! Give me all of the registered autoloaders please! 🙏

Hmm, let’s take a closer look at those… 🔍

81 of 133

[ Composer\Autoload\ClassLoader, MyAppLoader ]

[ AopComposerLoader(Composer\Autoload\ClassLoader), MyAppLoader ]

Hot dang! I knew it!

Go! AOP

Hey, PHP! Give me all of the registered autoloaders please! 🙏

Hmm, let’s take a closer look at those… 🔍

82 of 133

[ Composer\Autoload\ClassLoader, MyAppLoader ]

[ AopComposerLoader(Composer\Autoload\ClassLoader), MyAppLoader ]

Okay, thanks PHP! You can have those back now! 😘

Go! AOP

Hey, PHP! Give me all of the registered autoloaders please! 🙏

Hmm, let’s take a closer look at those… 🔍

Hot dang! I knew it!

83 of 133

[ Composer\Autoload\ClassLoader, MyAppLoader ]

[ AopComposerLoader(Composer\Autoload\ClassLoader), MyAppLoader ]

Sure, looks legit 🤷‍♀️

Go! AOP

PHP

Hey, PHP! Give me all of the registered autoloaders please! 🙏

Hmm, let’s take a closer look at those… 🔍

Hot dang! I knew it!

Okay, thanks PHP! You can have those back now! 😘

84 of 133

The Magic™

class AutoloaderOverloader

{

private $composer;

private $filterName;

protected function __construct(ComposerClassLoader $composer, string $filterName) {

$this->composer = $composer;

$this->filterName = $filterName

}

��������

}

85 of 133

The Magic™

class AutoloaderOverloader

{

private $composer;

private $filterName;

protected function __construct(ComposerClassLoader $composer, string $filterName) {

$this->composer = $composer;

$this->filterName = $filterName

}

public function loadClass($class) {

$file = $this->composer->findFile($class);

$compiledFile = $this->cache->findCachedFile($file)

?: 'php://filter/read=' . $this->filterName . '/resource=file://' . $file;

include $compiledFile;

}����

}

86 of 133

The Magic™

class AutoloaderOverloader

{

private $composer;

private $filterName;

protected function __construct(ComposerClassLoader $composer, string $filterName) {

$this->composer = $composer;

$this->filterName = $filterName

}

public function loadClass($class) {

$file = $this->composer->findFile($class);

$compiledFile = $this->cache->findCachedFile($file)

?: 'php://filter/read=' . $this->filterName . '/resource=file://' . $file;

include $compiledFile;

}����

}

No file returned?�Code templates!

87 of 133

The Magic™

class AutoloaderOverloader

{

// ...

public static function init(string $filterName): void

{

$loaders = spl_autoload_functions();

foreach ($loaders as &$loader) {

// Unregister each loader, so they can be re-registered in the same order.

spl_autoload_unregister($loader);

if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {

// Replace Composer autoloader with our hijacking autoloader.

$loader[0] = new self($loader[0], $filterName);

}

}

// Processed all the loaders, re-register them with PHP in their original order.

foreach ($loaders as $loader) {

spl_autoload_register($loader);

}

}

}

88 of 133

The Magic™

class AutoloaderOverloader

{

// ...

public static function init(string $filterName): void

{

$loaders = spl_autoload_functions();

foreach ($loaders as &$loader) {

// Unregister each loader, so they can be re-registered in the same order.

spl_autoload_unregister($loader);

if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {

// Replace Composer autoloader with our hijacking autoloader.

$loader[0] = new self($loader[0], $filterName);

}

}

// Processed all the loaders, re-register them with PHP in their original order.

foreach ($loaders as $loader) {

spl_autoload_register($loader);

}

}

}

89 of 133

The Magic™

class AutoloaderOverloader

{

// ...

public static function init(string $filterName): void

{

$loaders = spl_autoload_functions();

foreach ($loaders as &$loader) {

// Unregister each loader, so they can be re-registered in the same order.

spl_autoload_unregister($loader);

if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {

// Replace Composer autoloader with our hijacking autoloader.

$loader[0] = new self($loader[0], $filterName);

}

}

// Processed all the loaders, re-register them with PHP in their original order.

foreach ($loaders as $loader) {

spl_autoload_register($loader);

}

}

}

90 of 133

The Magic™

class AutoloaderOverloader

{

// ...

public static function init(string $filterName): void

{

$loaders = spl_autoload_functions();

foreach ($loaders as &$loader) {

// Unregister each loader, so they can be re-registered in the same order.

spl_autoload_unregister($loader);

if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {

// Replace Composer autoloader with our hijacking autoloader.

$loader[0] = new self($loader[0], $filterName);

}

}

// Processed all the loaders, re-register them with PHP in their original order.

foreach ($loaders as $loader) {

spl_autoload_register($loader);

}

}

}

91 of 133

The Magic™

class AutoloaderOverloader

{

// ...

public static function init(string $filterName): void

{

$loaders = spl_autoload_functions();

foreach ($loaders as &$loader) {

// Unregister each loader, so they can be re-registered in the same order.

spl_autoload_unregister($loader);

if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {

// Replace Composer autoloader with our hijacking autoloader.

$loader[0] = new self($loader[0], $filterName);

}

}

// Processed all the loaders, re-register them with PHP in their original order.

foreach ($loaders as $loader) {

spl_autoload_register($loader);

}

}

}

92 of 133

The Magic™

// index.php

require_once __DIR__ . '/vendor/autoload.php';

stream_filter_register('my_compiler_filter', MyCompilerFilter::class);

AutoloaderOverloader::init('my_compiler_filter');

93 of 133

The Magic™

// index.php

require_once __DIR__ . '/vendor/autoload.php';

stream_filter_register('my_compiler_filter', MyCompilerFilter::class);

AutoloaderOverloader::init('my_compiler_filter');

94 of 133

The Magic™

// index.php

require_once __DIR__ . '/vendor/autoload.php';

stream_filter_register('my_compiler_filter', MyCompilerFilter::class);

AutoloaderOverloader::init('my_compiler_filter');

95 of 133

The Magic™

// index.php

require_once __DIR__ . '/vendor/autoload.php';

stream_filter_register('my_compiler_filter', MyCompilerFilter::class);

AutoloaderOverloader::init('my_compiler_filter');

96 of 133

The Magic™

// index.php

require_once __DIR__ . '/vendor/autoload.php';

stream_filter_register('my_compiler_filter', MyCompilerFilter::class);

AutoloaderOverloader::init('my_compiler_filter');��$class = new MyClass;

97 of 133

$executableCode = transformCode($sourceCodeFile);

98 of 133

$executableCode = transformCode($sourceCodeFile);

eval($executableCode);

99 of 133

$executableCode = transformCode($sourceCodeFile);

eval($executableCode);

eval() is evil* and is disabled on hardened PHP installations.����* is what we’re doing here any better though? 🙄

100 of 133

$executableCode = transformCode($sourceCodeFile);

eval($executableCode);

file_put_contents(

$compiledFile,

$executableCode

);

require $compiledFile;

eval() is evil* and is disabled on hardened PHP installations.����* is what we’re doing here any better though? 🙄

101 of 133

$executableCode = transformCode($sourceCodeFile);

eval($executableCode);

file_put_contents(

$compiledFile,

$executableCode

);

require $compiledFile;

eval() is evil* and is disabled on hardened PHP installations.����* is what we’re doing here any better though? 🙄

But this only works if PHP has permissions to write to disk.

102 of 133

STREAMS

AWESOME

ARE

103 of 133

What Next?

104 of 133

Short Closures!

PHP 7.4 … y so slow?

105 of 133

Pre

106 of 133

Pre

$oddLetterCountWords = array_filter($fruits,� return strlen($fruit) % 2;� );

($fruit) => {��}

107 of 133

Pre

$oddLetterCountWords = array_filter($fruits,� return strlen($fruit) % 2;� );�����$oddLetterCountWords = array_filter($fruits,� return strlen($fruit) % 2;� );

($fruit) => {��}����� function ($fruit) {��}

108 of 133

$ composer require pre/short-closures:^0.8

109 of 133

$ composer require pre/short-closures:^0.8����// Alias for implementing and registering a stream filter.�public function transformCodeFilter($sourceFile) {� return \Pre\Plugin\parse(file_get_contents($sourceFile));�}

110 of 133

Obfuscation

111 of 133

<?php exit('Source Code Protected.'); ?>��aFcZG1BJF0UNAxVSBAgAEREcERF/HxwJRRJTVQlSeSoADhlFHhUADREANR9QLyYMGwYXAxVMABxYeHMFB0VUOxxNFQAbCz8mTw4fCwtLOhw1O1QdA2YBGhpEAFQaCg0pIABTGwoXUwRVbioKH0EdHFRkCAMAGxhUNwBOBxcMGR4AHnNbb05DUlkAAUIYAQYAFRobERcMTw1POzpHOh4GGEVBIUUfGhFTFQBXFwYEBwBTH0xDADMLF1AGHVMLTw8qTUVBTlQAVE9SFhEWBxxFAhxXRTwGAQkfGlMRQEJoFgMZHU9FdwwdCAEPc0FSeQBJUwATZQk=

112 of 133

<?php declare(strict_types=1);

namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

class DefaultController

{

public function __invoke(Request $request): Response {

return new Response('Hello, World!');

}

}

113 of 133

function transformCodeFilter(string $sourceFile): string {

$source = file_get_contents($sourceFile);

return strpos($source, PREAMBLE_TEXT) !== 0

? $source

: xorStringWithKey(

base64_decode(substr($source, strlen(PREAMBLE_TEXT))),

ENCRYPTION_KEY

);

}

function xorStringWithKey(string $source, string $key): string {

$output = '';

for ($i = 0; $i < strlen($source); $i++) {

$output .= $source[$i] ^ $key[$i % strlen($key)];

}

return $output;

}

114 of 133

function transformCodeFilter(string $sourceFile): string {

$source = file_get_contents($sourceFile);

return strpos($source, PREAMBLE_TEXT) !== 0

? $source

: xorStringWithKey(

base64_decode(substr($source, strlen(PREAMBLE_TEXT))),

ENCRYPTION_KEY

);

}

function xorStringWithKey(string $source, string $key): string {

$output = '';

for ($i = 0; $i < strlen($source); $i++) {

$output .= $source[$i] ^ $key[$i % strlen($key)];

}

return $output;

}

115 of 133

function transformCodeFilter(string $sourceFile): string {

$source = file_get_contents($sourceFile);

return strpos($source, PREAMBLE_TEXT) !== 0

? $source

: xorStringWithKey(

base64_decode(substr($source, strlen(PREAMBLE_TEXT))),

ENCRYPTION_KEY

);

}

function xorStringWithKey(string $source, string $key): string {

$output = '';

for ($i = 0; $i < strlen($source); $i++) {

$output .= $source[$i] ^ $key[$i % strlen($key)];

}

return $output;

}

116 of 133

function transformCodeFilter(string $sourceFile): string {

$source = file_get_contents($sourceFile);

return strpos($source, PREAMBLE_TEXT) !== 0

? $source

: xorStringWithKey(

base64_decode(substr($source, strlen(PREAMBLE_TEXT))),

ENCRYPTION_KEY

);

}

function xorStringWithKey(string $source, string $key): string {

$output = '';

for ($i = 0; $i < strlen($source); $i++) {

$output .= $source[$i] ^ $key[$i % strlen($key)];

}

return $output;

}

117 of 133

function transformCodeFilter(string $sourceFile): string {

$source = file_get_contents($sourceFile);

return strpos($source, PREAMBLE_TEXT) !== 0

? $source

: xorStringWithKey(

base64_decode(substr($source, strlen(PREAMBLE_TEXT))),

ENCRYPTION_KEY

);

}

function xorStringWithKey(string $source, string $key): string {

$output = '';

for ($i = 0; $i < strlen($source); $i++) {

$output .= $source[$i] ^ $key[$i % strlen($key)];

}

return $output;

}

118 of 133

Performance like it’s 1994 🎉

119 of 133

But that’s not the 📌

120 of 133

But that’s not the 📌

  • Pre transpiles new language features just like BabelJS

121 of 133

But that’s not the 📌

  • Pre transpiles new language features just like BabelJS
  • Go! AOP allows you to inject logic on-the-fly

122 of 133

But that’s not the 📌

  • Pre transpiles new language features just like BabelJS
  • Go! AOP allows you to inject logic on-the-fly
  • Source transformation means you can execute non-existent code

123 of 133

But that’s not the 📌

  • Pre transpiles new language features just like BabelJS
  • Go! AOP allows you to inject logic on-the-fly
  • Source transformation means you can execute non-existent code
  • ReactPHP uses stream_select() asynchronously

124 of 133

But that’s not the 📌

  • Pre transpiles new language features just like BabelJS
  • Go! AOP allows you to inject logic on-the-fly
  • Source transformation means you can execute non-existent code
  • ReactPHP uses stream_select() asynchronously
    • Icicle uses generators

125 of 133

Question.��

126 of 133

Question.

Experiment.�

127 of 133

Question.

Experiment.

Have fun.

128 of 133

Create terrible things.

129 of 133

Z-Engine by Alexander Lisachenko�

github.com/lisachenko/z-engine

Z-Engine uses FFI to access internal structures of PHP itself. It loads the definitions of native PHP structures, like zend_class_entry, zval, etc and manipulates them in runtime.

130 of 133

hey guess what I got working!

SymfonyCon 2020

131 of 133

Thank you!

132 of 133

@ZanBaldwin🚀 Intergalactic Agency, Vancouver

133 of 133

https://bit.ly/2QF3zh0