• Stars
    star
    310
  • Rank 134,926 (Top 3 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 8 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

PHP mocks engine that allows to redefine functions and user methods on-the-fly (provides similar functionality to runkit and uopz extensions)

SoftMocks

The idea behind "Soft Mocks" - as opposed to "hardcore" mocks that work on the level of the PHP interpreter (runkit and uopz) - is to rewrite class code on the spot so that it can be inserted in any place. It works by rewriting code on the fly during file inclusion instead of using extensions like runkit or uopz.

Build Status GitHub release Total Downloads Daily Downloads Minimum PHP Version License

Installation

You can install SoftMocks via Composer:

composer require --dev badoo/soft-mocks

Usage

The thing that sets SoftMocks apart (and also limits their usage) is that they need to be initiated at the earliest phase of the app launch. It's necessary to do it this way because you can't redefine the classes and functions that are already loaded into the memory in PHP. For an example bootstrap presets, see src/bootstrap.php. For PHPUnit you should use patches form composer.json, because you should require composer autoload through SoftMocks.

SoftMocks don't rewrite the following system parts:

  • it's own code;
  • PHPUnit code (see \Badoo\SoftMocks::addIgnorePath() for details);
  • PHP-Parser code (see \Badoo\SoftMocks::addIgnorePath() for details);
  • already rewritten code;
  • code which was loaded before SoftMocks initialization.

In order to add external dependencies (for example, vendor/autoload.php) in file, which which was loaded before SoftMocks initialization, you need to use a wrapper:

require_once (\Badoo\SoftMocks::rewrite('vendor/autoload.php'));
require_once (\Badoo\SoftMocks::rewrite('path/to/external/lib.php'));

After you've added the file via SoftMocks::rewrite(), all nested include calls will already be "wrapped" by the system itself.

You can see a more detailed example by executing the following command:

$ php example/run_me.php
Result before applying SoftMocks = array (
  'TEST_CONSTANT_WITH_VALUE_42' => 42,
  'someFunc(2)' => 84,
  'Example::doSmthStatic()' => 42,
  'Example->doSmthDynamic()' => 84,
  'Example::STATIC_DO_SMTH_RESULT' => 42,
)
Result after applying SoftMocks = array (
  'TEST_CONSTANT_WITH_VALUE_42' => 43,
  'someFunc(2)' => 57,
  'Example::doSmthStatic()' => 'Example::doSmthStatic() redefined',
  'Example->doSmthDynamic()' => 'Example->doSmthDynamic() redefined',
  'Example::STATIC_DO_SMTH_RESULT' => 'Example::STATIC_DO_SMTH_RESULT value changed',
)
Result after reverting SoftMocks = array (
  'TEST_CONSTANT_WITH_VALUE_42' => 42,
  'someFunc(2)' => 84,
  'Example::doSmthStatic()' => 42,
  'Example->doSmthDynamic()' => 84,
  'Example::STATIC_DO_SMTH_RESULT' => 42,
)

API (short description)

Initialize SoftMocks (set phpunit injections, define internal mocks, get list of internal functions, etc):

\Badoo\SoftMocks::init();

Cache files are created in /tmp/mocks by default. If you want to choose a different path, you can redefine it as follows:

\Badoo\SoftMocks::setMocksCachePath($cache_path);

This method should be called before rewrite first file. Also you can redefine cache path using environment variable SOFT_MOCKS_CACHE_PATH.

Redefine constant

You can assign a new value to $constantName or create one if it wasn't already declared. Since it isn't created using the define() call, the operation can be canceled.

Both "regular constants" and class constants like "className::CONST_NAME" are supported.

\Badoo\SoftMocks::redefineConstant($constantName, $value)

There can be next cases with class constants redefining:

  • You can redefine base class constant:
    class A {const NAME = 'A';}
    class B {}
    echo A::NAME . "\n"; // A
    echo B::NAME . "\n"; // A
    \Badoo\SoftMocks::redefineConstant(A::class . '::NAME', 'B');
    echo A::NAME . "\n"; // B
    echo B::NAME . "\n"; // B
  • You can add middle class constant:
    class A {const NAME = 'A';}
    class B {}
    class C {}
    echo A::NAME . "\n"; // A
    echo B::NAME . "\n"; // A
    echo C::NAME . "\n"; // A
    \Badoo\SoftMocks::redefineConstant(B::class . '::NAME', 'B');
    echo A::NAME . "\n"; // A
    echo B::NAME . "\n"; // B
    echo C::NAME . "\n"; // B
  • You can add constant to base class:
    class A {const NAME = 'A';}
    class B {}
    echo A::NAME . "\n"; // Undefined class constant 'NAME'
    echo B::NAME . "\n"; // Undefined class constant 'NAME'
    \Badoo\SoftMocks::redefineConstant(A::class . '::NAME', 'A');
    echo A::NAME . "\n"; // A
    echo B::NAME . "\n"; // A
  • You can remove middle class constant:
    class A {const NAME = 'A';}
    class B {const NAME = 'B';}
    class C {}
    echo A::NAME . "\n"; // A
    echo B::NAME . "\n"; // B
    echo C::NAME . "\n"; // B
    \Badoo\SoftMocks::removeConstant(B::class . '::NAME');
    echo A::NAME . "\n"; // A
    echo B::NAME . "\n"; // A
    echo C::NAME . "\n"; // A
  • Other more simple cases (just add or redefine constant and etc.).

Redefine functions

SoftMocks let you redefine both user-defined and built-in functions except for those that depend on the current context (see \Badoo\SoftMocksTraverser::$ignore_functions property if you want to see the full list), or for those that have built-in mocks (debug_backtrace, call_user_func* and a few others, but built-in mocks you can enable redefine by call \Badoo\SoftMocks::setRewriteInternal(true)).

Definition:

\Badoo\SoftMocks::redefineFunction($func, $functionArgs, $fakeCode)

Usage example (redefine strlen function and call original for the trimmed string):

\Badoo\SoftMocks::redefineFunction(
    'strlen',
    '$a',
    'return \\Badoo\\SoftMocks::callOriginal("strlen", [trim($a)]));'
);

var_dump(strlen("  a  ")); // int(1)

Redefine methods

At the moment, only user-defined method redefinition is supported. This functionality is not supported for built-in classes.

Definition:

\Badoo\SoftMocks::redefineMethod($class, $method, $functionArgs, $fakeCode)

Arguments are the same as for redefineFunction, but argument $class is introduced.

As an argument $class accepts a class name or a trait name.

Redefining functions that are generators

This method that lets you replace a generator function call with another \Generator. Generators differ from regular functions in that you can't return a value using "return"; you have to use "yield".

\Badoo\SoftMocks::redefineGenerator($class, $method, \Generator $replacement)

Restore values

The following functions undo mocks that were made using one of the redefine methods described above.

\Badoo\SoftMocks::restoreAll()

// You can also undo only chosen mocks:
\Badoo\SoftMocks::restoreConstant($constantName)
\Badoo\SoftMocks::restoreAllConstants()
\Badoo\SoftMocks::restoreFunction($func)
\Badoo\SoftMocks::restoreMethod($class, $method)
\Badoo\SoftMocks::restoreGenerator($class, $method)
\Badoo\SoftMocks::restoreNew()
\Badoo\SoftMocks::restoreAllNew()
\Badoo\SoftMocks::restoreExit()

Using with PHPUnit

If you want to use SoftMocks with PHPUnit 8.x then there are next particularities:

Use phpunit7.x directory instead of phpunit8.x for phpunit7.x. Use phpunit6.x directory instead of phpunit8.x for phpunit6.x. Use phpunit5.x directory instead of phpunit8.x for phpunit5.x. Use phpunit4.x directory instead of phpunit8.x for phpunit4.x.

If you want that patches are applied automatically, you should write next in в composer.json:

{
  "require-dev": {
    "vaimo/composer-patches": "3.23.1",
    "phpunit/phpunit": "^8.4.3" // or "^7.5.17" or "^6.5.5" or "^5.7.20" or "^4.8.35"
  }
}

To force reapply patches use next command:

composer patch --redo

For more information about patching see vaimo/composer-patches documentation.

Using with xdebug

There is two possibilities to use soft-mocks with xdebug - debug rewritten files and debug original file using xdebug-proxy.

Debug rewritten files

If you use soft-mocks locally then you can just debug it by calling to xdebug_break(). Also you can add break point to the rewritten file, but you should know rewritten file path. For getting the rewritten file path you can call \Badoo\SoftMocks::rewrite($file), but be attentive - if you change the file then new one will be created and it'll have different path.

If you use soft-mocks on the server, then you can mount /tmp/mocks using sshfs or something like this.

Debug original files using xdebug-proxy

As you see debug rewritten files is uncomfortable. You can also debug original files using xdebug-proxy.

composer.phar require mougrim/php-xdebug-proxy --dev
cp -r vendor/mougrim/php-xdebug-proxy/config xdebug-proxy-config

After that change xdebug-proxy-config/factory.php to the following:

<?php
use Mougrim\XdebugProxy\Factory\SoftMocksFactory;

return new SoftMocksFactory();

If you use soft-mocks locally, then you can just run proxy:

vendor/bin/xdebug-proxy --configs=xdebug-proxy-config

After that register your IDE on 127.0.0.1:9001 and run script, which uses soft-mocks (for example phpunit):

php -d'zend_extension=xdebug.so' -d'xdebug.remote_autostart=On' -d'xdebug.idekey=idekey' -d'xdebug.remote_connect_back=On' -d'xdebug.remote_enable=On' -d'xdebug.remote_host=127.0.0.1' -d'xdebug.remote_port=9002' /local/php72/bin/phpunit

If you use soft-mocks on the server, then you should run xdebug-proxy on the server too, and modify ip in xdebug-proxy-config/config.php for ideRegistrationServer from 127.0.0.1 to 0.0.0.0.

In general xdebug-proxy works as the following:

  1. The first step is to register your IDE in the xdebug-proxy (eg: Main menu -> Tools -> DBGp proxy -> Register IDE in PHPStorm). Use 127.0.0.1:9001 or your server IP:PORT which xdebug-proxy is listening on for the IDE registration. You can configure that PORT in the xdebug-proxy config. On that step IDE sends its IP:PORT to the proxy which IDE is listening on.
  2. When you run php-script with command-line options provided above xdebug connects to 127.0.0.1:9002. This ip and port is where xdebug-proxy is listening on for the connection from xdebug. Xdebug-proxy matches IDEKEY with the registered IDE. If any registered IDE is matched then xdebug-proxy will connect to that particular IDE using provided IDE client IP:PORT at the registration step.

For more information read xdebug documentation and xdebug-proxy documentation.

SoftMocks development

If you need to make changes to SoftMocks, you need to clone repository and install dependencies:

composer install

Then you can change SoftMocks and run tests to be sure that all works:

./vendor/bin/phpunit 

FAQ

Q: How can I prevent a specific function/class/constant from being redefined?

A: Use the \Badoo\SoftMocks::ignore(Class|Function|Constant) method.

Q: I can't override certain function calls: call_user_func(_array)?, defined, etc.

A: There are a bunch of functions that have their own built-in mocks which by default can't be intercepted. Here is an incomplete list of them:

  • call_user_func_array
  • call_user_func
  • is_callable
  • function_exists
  • constant
  • defined
  • debug_backtrace

So you can enable intercepting for them by call \Badoo\SoftMocks::setRewriteInternal(true) after require bootstrap, but be attentive. For example, if strlen and call_user_func(_array) is redefined, then you can get different result for strlen:

\Badoo\SoftMocks::redefineFunction('call_user_func_array', '', 'return 20;');
\Badoo\SoftMocks::redefineFunction('strlen', '', 'return 5;');
...
strlen('test'); // will return 5
call_user_func_array('strlen', ['test']); // will return 20
call_user_func('strlen', 'test'); // will return 5

Q: Does SoftMocks work with PHP7?

A: Yes. The whole idea of SoftMocks is that it will continue to work for all further PHP versions without requiring a full system rewrite as it is for runkit and uopz.

Q: Does SoftMocks work with HHVM?

A: It seems that SoftMocks indeed works when using HHVM at the moment of writing this Q&A (HipHop VM 3.12.1 (rel)). We do not use HHVM internally so there can be some corner cases that are not covered. We appreciate any issues/pull requests regarding HHVM support.

Q: Why do I get parse errors or fatal errors like "PhpParser::pSmth is undefined"?

A: SoftMocks uses custom pretty-printer for PHP Parser that does not seem to be compatible with all PHP Parser versions. Please use our vendored version until we found a way to get around that.

More Repositories

1

Chatto

A lightweight framework to build chat applications, made in Swift
Swift
4,476
star
2

android-weak-handler

Memory safer implementation of android.os.Handler
Java
1,544
star
3

MVICore

MVI framework with events, time-travel, and more
Kotlin
1,253
star
4

Reaktive

Kotlin multi-platform implementation of Reactive Extensions
Kotlin
1,172
star
5

MVIKotlin

Extendable MVI framework for Kotlin Multiplatform with powerful debugging tools (logging and time travel), inspired by Badoo MVICore library
Kotlin
824
star
6

Decompose

Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing functionality and pluggable UI (Jetpack Compose, SwiftUI, JS React, etc.), inspired by Badoos RIBs fork of the Uber RIBs framework
Kotlin
817
star
7

Chateau

Chateau is a framework for adding (or improving) chat functionality in any Android app
Java
666
star
8

liveprof

A performance monitoring system for running on live sites
PHP
232
star
9

phpcf

PHP Code Formatter
PHP
183
star
10

lsd

Live Streaming Daemon
Go
169
star
11

RIBs

Badoo's take on RIBs
Kotlin
162
star
12

BMASpinningLabel

BMASpinningLabel is an UI component which provides easy way for displaying and animating text inside it
Objective-C
151
star
13

pinba2

Pinba2: new implementation of https://github.com/tony2001/pinba_engine
C++
131
star
14

hprof-tools

Tool for deobfuscating memory dump files
Java
127
star
15

liveprof-ui

An aggregator and web interface for Live Profiler
PHP
126
star
16

ios-collection-batch-updates

Safely perform batch updates in UITableView and UICollectionView
Objective-C
123
star
17

BMAGridPageControl

Objective-C
100
star
18

BMASliders

Configurable simple and range sliders
Objective-C
93
star
19

codeisok

Git code browsing and code review tool
PHP
85
star
20

KmpMvi

Sample of MVI in Kotlin Multiplatform
Kotlin
70
star
21

MockJS

JavaScript
67
star
22

jira-client

Badoo JIRA API Client with code generator
PHP
66
star
23

balancer

Load balancer that was presented at HighLoad++ 2015 Conference in Moscow
PHP
44
star
24

ios-device-server

A server to manage remote iOS simulators and devices for parallel testing
Kotlin
42
star
25

thunder

Our cloud system
Go
42
star
26

funcmap

PHP extension that logs all called userspace functions/methods
C
39
star
27

FreehandDrawing-iOS

A tutorial to build a freehand drawing feature on iOS.
Swift
38
star
28

StarBar

Java
35
star
29

libpssh

library implementing asynchronous SSH connections
C
28
star
30

THEPageControl

Swift
21
star
31

parallel_cucumber

Ruby
21
star
32

BMACollectionViewLayouts

A set of UICollectionView layouts
Objective-C
20
star
33

HyperLabel

Swift
16
star
34

uiautomatorviewer

Rebuild of UiAutomatorViewer app from the sources to make it compatible with modern JDK versions
Java
15
star
35

Gallery

Swift
15
star
36

techblog

HTML
15
star
37

ReceptionApp

Application for guests to sign in and sign off while in the office
Objective-C
15
star
38

ssmtp

extremely simple MTA to get mail off the system to a mail hub
C
13
star
39

pssh_extension

PHP extension-wrapper for libpssh
C
13
star
40

rtl-css

JavaScript
12
star
41

habr

Materials for habrahabr articles
PHP
11
star
42

xhprof_console

A console tool for grabbing profiles from XHProf database and collecting aggregates from them
PHP
11
star
43

libssh2

libssh2 clone with additional patches applied
11
star
44

styleguide

Badoo styleguide used to develop UI components for the Web and React Native
JavaScript
10
star
45

kexasol

Exasol database driver implemented in Kotlin (JVM). It is based on native WebSocket API, supports parallel CSV streaming and compression.
Kotlin
10
star
46

MobileAutomationSampleProject

A sample project to demonstrate best practices for a mobile automation using Cucumber framework
Ruby
8
star
47

intellij-idea-live-profiler

A PhpStorm plugin for Live Profiler.
Kotlin
7
star
48

exasol-data-lineage

Exasol data lineage scripts
Python
6
star
49

TooltipsQueue

Kotlin
6
star
50

phpunit-testlistener-teamcity

Reporting Test in TeamCity using Service Messages
PHP
5
star
51

tarantool-dissector

Wireshark's dissector for the Tarantool's protocol
Lua
5
star
52

dust2jsx

Convert Dust.js templates to JSX
JavaScript
3
star
53

coverage-service

Create code coverage report from window.__coverage__ object
JavaScript
3
star
54

uap-php-lite

PHP implementation of ua-parser without runtime dependencies
PHP
2
star
55

idea-printf-checker-plugin-example

Kotlin
2
star
56

file-streamer

Streams given file data into any buffered writer. Uses fsnotify system for new data detection in files.
Go
2
star
57

hadoop-xargs

Util to run heterogenous applications on Hadoop synchronously
Java
2
star
58

DeviceAgent.iOS.Inspector

Web inspector of UI elements for iOS DeviceAgent
JavaScript
1
star
59

badoo.github.com

PEAR Channel
1
star
60

centrifugo-bench

Benchmark tools for centrifugo
Go
1
star
61

app-tree-utils

Kotlin
1
star
62

meow

C++
1
star