{"id":19773921,"url":"https://github.com/badoo/soft-mocks","last_synced_at":"2025-05-16T11:06:30.778Z","repository":{"id":47676464,"uuid":"53654320","full_name":"badoo/soft-mocks","owner":"badoo","description":"PHP mocks engine that allows to redefine functions and user methods on-the-fly (provides similar functionality to runkit and uopz extensions)","archived":false,"fork":false,"pushed_at":"2024-02-07T16:04:11.000Z","size":641,"stargazers_count":309,"open_issues_count":14,"forks_count":42,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-04-09T06:08:39.802Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/badoo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2016-03-11T09:08:38.000Z","updated_at":"2025-02-22T21:23:37.000Z","dependencies_parsed_at":"2024-02-07T17:27:55.807Z","dependency_job_id":"dde602f1-65fe-4b68-95d3-9dfe6131ea36","html_url":"https://github.com/badoo/soft-mocks","commit_stats":{"total_commits":185,"total_committers":18,"mean_commits":"10.277777777777779","dds":0.5513513513513513,"last_synced_commit":"19db34380f523b15ce85b8095f8c309b39554800"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badoo%2Fsoft-mocks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badoo%2Fsoft-mocks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badoo%2Fsoft-mocks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badoo%2Fsoft-mocks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/badoo","download_url":"https://codeload.github.com/badoo/soft-mocks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254518383,"owners_count":22084374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T05:11:31.999Z","updated_at":"2025-05-16T11:06:25.759Z","avatar_url":"https://github.com/badoo.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SoftMocks\nThe 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.\n\n[![Build Status](https://secure.travis-ci.org/badoo/soft-mocks.png?branch=master)](https://travis-ci.org/badoo/soft-mocks)\n[![GitHub release](https://img.shields.io/github/release/badoo/soft-mocks.svg)](https://github.com/badoo/soft-mocks/releases/latest)\n[![Total Downloads](https://img.shields.io/packagist/dt/badoo/soft-mocks.svg)](https://packagist.org/packages/badoo/soft-mocks)\n[![Daily Downloads](https://img.shields.io/packagist/dd/badoo/soft-mocks.svg)](https://packagist.org/packages/badoo/soft-mocks)\n[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.5-8892BF.svg)](https://php.net/)\n[![License](https://img.shields.io/packagist/l/badoo/soft-mocks.svg)](https://packagist.org/packages/badoo/soft-mocks)\n\n## Installation\n\nYou can install SoftMocks via [Composer](https://getcomposer.org/):\n\n```bash\ncomposer require --dev badoo/soft-mocks\n```\n\n## Usage\n\nThe 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](src/bootstrap.php)_. For PHPUnit you should use patches form _[composer.json](composer.json)_, because you should require composer autoload through SoftMocks.\n\nSoftMocks don't rewrite the following system parts:\n* it's own code;\n* PHPUnit code (see `\\Badoo\\SoftMocks::addIgnorePath()` for details);\n* PHP-Parser code (see `\\Badoo\\SoftMocks::addIgnorePath()` for details);\n* already rewritten code;\n* code which was loaded before SoftMocks initialization.\n\nIn 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:\n```\nrequire_once (\\Badoo\\SoftMocks::rewrite('vendor/autoload.php'));\nrequire_once (\\Badoo\\SoftMocks::rewrite('path/to/external/lib.php'));\n```\n\nAfter you've added the file via `SoftMocks::rewrite()`, all nested include calls will already be \"wrapped\" by the system itself.\n\nYou can see a more detailed example by executing the following command:\n```\n$ php example/run_me.php\nResult before applying SoftMocks = array (\n  'TEST_CONSTANT_WITH_VALUE_42' =\u003e 42,\n  'someFunc(2)' =\u003e 84,\n  'Example::doSmthStatic()' =\u003e 42,\n  'Example-\u003edoSmthDynamic()' =\u003e 84,\n  'Example::STATIC_DO_SMTH_RESULT' =\u003e 42,\n)\nResult after applying SoftMocks = array (\n  'TEST_CONSTANT_WITH_VALUE_42' =\u003e 43,\n  'someFunc(2)' =\u003e 57,\n  'Example::doSmthStatic()' =\u003e 'Example::doSmthStatic() redefined',\n  'Example-\u003edoSmthDynamic()' =\u003e 'Example-\u003edoSmthDynamic() redefined',\n  'Example::STATIC_DO_SMTH_RESULT' =\u003e 'Example::STATIC_DO_SMTH_RESULT value changed',\n)\nResult after reverting SoftMocks = array (\n  'TEST_CONSTANT_WITH_VALUE_42' =\u003e 42,\n  'someFunc(2)' =\u003e 84,\n  'Example::doSmthStatic()' =\u003e 42,\n  'Example-\u003edoSmthDynamic()' =\u003e 84,\n  'Example::STATIC_DO_SMTH_RESULT' =\u003e 42,\n)\n```\n\n## API (short description)\n\nInitialize SoftMocks (set phpunit injections, define internal mocks, get list of internal functions, etc):\n\n```\n\\Badoo\\SoftMocks::init();\n```\n\nCache files are created in /tmp/mocks by default. If you want to choose a different path, you can redefine it as follows:\n\n```\n\\Badoo\\SoftMocks::setMocksCachePath($cache_path);\n```\n\nThis method should be called before rewrite first file. Also you can redefine cache path using environment variable `SOFT_MOCKS_CACHE_PATH`.\n\n### Redefine constant\n\nYou 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.\n\nBoth \"regular constants\" and class constants like \"className::CONST_NAME\" are supported.\n\n```\n\\Badoo\\SoftMocks::redefineConstant($constantName, $value)\n```\n\nThere can be next cases with class constants redefining:\n\n- You can redefine base class constant:\n  ```php\n  class A {const NAME = 'A';}\n  class B {}\n  echo A::NAME . \"\\n\"; // A\n  echo B::NAME . \"\\n\"; // A\n  \\Badoo\\SoftMocks::redefineConstant(A::class . '::NAME', 'B');\n  echo A::NAME . \"\\n\"; // B\n  echo B::NAME . \"\\n\"; // B\n  ```\n- You can add middle class constant:\n  ```php\n  class A {const NAME = 'A';}\n  class B {}\n  class C {}\n  echo A::NAME . \"\\n\"; // A\n  echo B::NAME . \"\\n\"; // A\n  echo C::NAME . \"\\n\"; // A\n  \\Badoo\\SoftMocks::redefineConstant(B::class . '::NAME', 'B');\n  echo A::NAME . \"\\n\"; // A\n  echo B::NAME . \"\\n\"; // B\n  echo C::NAME . \"\\n\"; // B\n  ```\n- You can add constant to base class:\n  ```php\n  class A {const NAME = 'A';}\n  class B {}\n  echo A::NAME . \"\\n\"; // Undefined class constant 'NAME'\n  echo B::NAME . \"\\n\"; // Undefined class constant 'NAME'\n  \\Badoo\\SoftMocks::redefineConstant(A::class . '::NAME', 'A');\n  echo A::NAME . \"\\n\"; // A\n  echo B::NAME . \"\\n\"; // A\n  ```\n- You can remove middle class constant:\n  ```php\n  class A {const NAME = 'A';}\n  class B {const NAME = 'B';}\n  class C {}\n  echo A::NAME . \"\\n\"; // A\n  echo B::NAME . \"\\n\"; // B\n  echo C::NAME . \"\\n\"; // B\n  \\Badoo\\SoftMocks::removeConstant(B::class . '::NAME');\n  echo A::NAME . \"\\n\"; // A\n  echo B::NAME . \"\\n\"; // A\n  echo C::NAME . \"\\n\"; // A\n  ```\n- Other more simple cases (just add or redefine constant and etc.).\n\n### Redefine functions\n\nSoftMocks 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)`).\n\nDefinition:\n```\n\\Badoo\\SoftMocks::redefineFunction($func, $functionArgs, $fakeCode)\n```\n\nUsage example (redefine strlen function and call original for the trimmed string):\n```\n\\Badoo\\SoftMocks::redefineFunction(\n    'strlen',\n    '$a',\n    'return \\\\Badoo\\\\SoftMocks::callOriginal(\"strlen\", [trim($a)]));'\n);\n\nvar_dump(strlen(\"  a  \")); // int(1)\n```\n\n### Redefine methods\n\nAt the moment, only user-defined method redefinition is supported. This functionality is not supported for built-in classes.\n\nDefinition:\n```\n\\Badoo\\SoftMocks::redefineMethod($class, $method, $functionArgs, $fakeCode)\n```\n\nArguments are the same as for redefineFunction, but argument $class is introduced.\n\nAs an argument $class accepts a class name or a trait name.\n\n### Redefining functions that are generators\n\nThis 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\".\n\n```\n\\Badoo\\SoftMocks::redefineGenerator($class, $method, \\Generator $replacement)\n```\n\n### Restore values\n\nThe following functions undo mocks that were made using one of the redefine methods described above.\n```\n\\Badoo\\SoftMocks::restoreAll()\n\n// You can also undo only chosen mocks:\n\\Badoo\\SoftMocks::restoreConstant($constantName)\n\\Badoo\\SoftMocks::restoreAllConstants()\n\\Badoo\\SoftMocks::restoreFunction($func)\n\\Badoo\\SoftMocks::restoreMethod($class, $method)\n\\Badoo\\SoftMocks::restoreGenerator($class, $method)\n\\Badoo\\SoftMocks::restoreNew()\n\\Badoo\\SoftMocks::restoreAllNew()\n\\Badoo\\SoftMocks::restoreExit()\n```\n\n## Using with PHPUnit\n\nIf you want to use SoftMocks with PHPUnit 8.x then there are next particularities:\n- If phpunit is installed by composer then you should apply patch to `phpunit` _[patches/phpunit7.x/phpunit_phpunit.patch](patches/phpunit7.x/phpunit_phpunit.patch)_,so that classes loaded by composer would be rewritten by SoftMocks;\n- if phpunit is installed manually then you should require _[src/bootstrap.php](src/bootstrap.php)_, so that classes loaded by composer would be rewritten by SoftMocks;\n- so that trace would be readable you should apply patch for `phpunit` _[patches/phpunit8.x/phpunit_add_ability_to_set_custom_filename_rewrite_callbacks.patch](patches/phpunit8.x/phpunit_add_ability_to_set_custom_filename_rewrite_callbacks.patch)_;\n- so that coverage would be right the you should apply patch to `php-code-coverage` _[patches/phpunit8.x/php-code-coverage_add_ability_to_set_custom_filename_rewrite_callbacks.patch](patches/phpunit8.x/php-code-coverage_add_ability_to_set_custom_filename_rewrite_callbacks.patch)_.\n\nUse `phpunit7.x` directory instead of `phpunit8.x` for `phpunit7.x`.\nUse `phpunit6.x` directory instead of `phpunit8.x` for `phpunit6.x`.\nUse `phpunit5.x` directory instead of `phpunit8.x` for `phpunit5.x`.\nUse `phpunit4.x` directory instead of `phpunit8.x` for `phpunit4.x`.\n\nIf you want that patches are applied automatically, you should write next in в composer.json:\n```json\n{\n  \"require-dev\": {\n    \"vaimo/composer-patches\": \"3.23.1\",\n    \"phpunit/phpunit\": \"^8.4.3\" // or \"^7.5.17\" or \"^6.5.5\" or \"^5.7.20\" or \"^4.8.35\"\n  }\n}\n```\n\nTo force reapply patches use next command:\n```bash\ncomposer patch --redo\n```\n\nFor more information about patching see [vaimo/composer-patches documentation](https://github.com/vaimo/composer-patches/blob/3.22.4/README.md).\n\n## Using with xdebug\n\nThere is two possibilities to use soft-mocks with xdebug - debug rewritten files and debug original file using xdebug-proxy.\n\n### Debug rewritten files\n\nIf 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.\n\nIf you use soft-mocks on the server, then you can mount /tmp/mocks using sshfs or something like this.\n\n### Debug original files using xdebug-proxy\n\nAs you see debug rewritten files is uncomfortable. You can also debug original files using [xdebug-proxy](https://github.com/mougrim/php-xdebug-proxy).\n\n```php\ncomposer.phar require mougrim/php-xdebug-proxy --dev\ncp -r vendor/mougrim/php-xdebug-proxy/config xdebug-proxy-config\n```\n\nAfter that change `xdebug-proxy-config/factory.php` to the following:\n```php\n\u003c?php\nuse Mougrim\\XdebugProxy\\Factory\\SoftMocksFactory;\n\nreturn new SoftMocksFactory();\n```\n\nIf you use soft-mocks locally, then you can just run proxy:\n```bash\nvendor/bin/xdebug-proxy --configs=xdebug-proxy-config\n```\n\nAfter that register your IDE on `127.0.0.1:9001` and run script, which uses soft-mocks (for example phpunit):\n```bush\nphp -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\n```\n\nIf 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`.\n\nIn general xdebug-proxy works as the following:\n1. The first step is to register your IDE in the xdebug-proxy (eg: Main menu -\u003e Tools -\u003e DBGp proxy -\u003e 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.\n2. 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.\n\nFor more information read [xdebug documentation](https://xdebug.org/docs/remote) and [xdebug-proxy documentation](https://github.com/mougrim/php-xdebug-proxy).\n\n## SoftMocks development\n\nIf you need to make changes to SoftMocks, you need to clone repository and install dependencies:\n\n```\ncomposer install\n```\n\nThen you can change SoftMocks and run tests to be sure that all works:\n\n```\n./vendor/bin/phpunit \n```\n\n## FAQ\n\n**Q**: How can I prevent a specific function/class/constant from being redefined?\n\n**A**: Use the \\Badoo\\SoftMocks::ignore(Class|Function|Constant) method.\n\n**Q**: I can't override certain function calls: call_user_func(_array)?, defined, etc.\n\n**A**: There are a bunch of functions that have their own built-in mocks which by default can't be intercepted.\nHere is an incomplete list of them:\n* call_user_func_array\n* call_user_func\n* is_callable\n* function_exists\n* constant\n* defined\n* debug_backtrace\n\nSo you can enable intercepting for them by call `\\Badoo\\SoftMocks::setRewriteInternal(true)` after require bootstrap, but be attentive.\nFor example, if strlen and call_user_func(_array) is redefined, then you can get different result for strlen:\n```php\n\\Badoo\\SoftMocks::redefineFunction('call_user_func_array', '', 'return 20;');\n\\Badoo\\SoftMocks::redefineFunction('strlen', '', 'return 5;');\n...\nstrlen('test'); // will return 5\ncall_user_func_array('strlen', ['test']); // will return 20\ncall_user_func('strlen', 'test'); // will return 5\n```\n\n**Q**: Does SoftMocks work with PHP7?\n\n**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.\n\n**Q**: Does SoftMocks work with HHVM?\n\n**A**: It seems that SoftMocks indeed works when using HHVM at the moment of writing this Q\u0026A (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.\n\n**Q**: Why do I get parse errors or fatal errors like \"PhpParser::pSmth is undefined\"?\n\n**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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbadoo%2Fsoft-mocks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbadoo%2Fsoft-mocks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbadoo%2Fsoft-mocks/lists"}