A MySQL engine written in pure PHP

Overview

PHP MySQL Engine

PHP MySQL Engine is a library for PHP that allows you to test database-driven applications with an in-memory simulation of MySQL 5.6. This project extends the PDO class and allows you to call common PDO MySQL methods. It supports a wide variety of queries, and some PDO-specific functionality like transactions and different fetch modes.

PHP MySQL Engine is based on Slack's Hack SQL Fake created by Scott Sandler.

You can read an article about this tool here.

Motivation

Currently there are two ways to test code that reads and writes to a database:

  • Mock SQL query execution
    Mocks require an explicit list of queries that are expected to run and results to return. This leads to significant manual work setting up expectations, and tests which are fragile and must be updated even on benign changes to the code or queries. It also means the data access layer is not unit tested.

  • Use an actual database
    It might make sense to test with a separate database instance – this is what we have done in the past at Vimeo. But databases like MySQL are designed to be filled with lots of long-lasting data, whereas unit tests write small amounts of very short-lived data. This means that extra care has to be taken to make sure that test databases are truncated between tests, which creates a performance issue.

PHP MySQL Engine takes a different approach - it parses and executes SELECT, INSERT, UPDATE, and DELETE queries against an in-memory "database" stored in PHP arrays. As long as the amount of data used for testing is small, this solves the problems mentioned above.

SQL Syntax Supported

This library supports a wide variety of query syntax, including:

  • FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT clauses supported as appropriate for each query type
  • JOIN queries with all join types
  • multi-queries such as subqueries, UNION, UNION ALL, INTERSECT, EXCEPT
  • complex expressions such as CASE, BETWEEN, and row comparators (1, 2, 3) < (4, 5, 6)
  • all basic operators implemented with operator precedence
  • column aliases, cross-database queries
  • INSERT ... ON DUPLICATE KEY UPDATE
  • A variety of SQL functions such as COUNT(), NULLIF(), COALESCE(), CONCAT_WS() and many others
  • Temporary variables like @previous_name := user.name
  • Validating parser: the query parser will throw exceptions on most invalid SQL Queries, helping protect your production environment from accidental SQL syntax errors

Unsupported MySQL features

This engine does not support MySQL Stored objects, which precludes the testing of stored procedures, triggers and views.

Caveat Emptor

Unlike Psalm, this package is not designed with a wide audience in mind. For a project to really benefit from this library it should already have a large number of tests that require a database connection to complete, and the project maintainers must understand the tradeoffs associated with using an unofficial MySQL implementation in their test suite.

Known issues

Result types when not emulating prepares

By default the engine returns all data formatted as a string. If $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false) is called, the engine will instead infer column types (for example, SUM(some_int_column) will be given an int type). In some cases php-mysql-engine may do a better job of inferring correct column types than actual MySQL, which defaults to string when it can’t work out a column type. If you do strict type checks on the results you may see small discrepancies.

Installation

composer require --dev vimeo/php-mysql-engine

Usage

PHP MySQL Engine works by providing a subclass of PDO.

You can instantiate the subclass as you would PDO, and use dependency injection or similar to provide that instance to your application code.

// use a class specific to your cuurrent PHP version (APIs changed in major versions)
$pdo = new \Vimeo\MysqlEngine\Php8\FakePdo($dsn, $user, $password);
// currently supported attributes
$pdo->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);

The rest of your code can operate as normal, using the database in the same way it is used in production.

Why doesn't it support X?

This library aims to support everything its users use in MySQL, rather than every possibly feature MySQL offers. We welcome pull requests to add support for new syntax, sql functions, data types, bug fixes, and other features.

Why doesn’t this project have an issue tracker?

Maintaining open-source projects is hard work, and I don't want to make more work for me or my colleagues. Use this project very much use at your own risk.

If you want to fork the project with an issue tracker, feel free!

Contributing

If you want to create a PR, please make sure it passes unit tests:

vendor/bin/phpunit

and also Psalm's checks

vendor/bin/psalm

Thanks!

Comments
  • feat(UNIX_TIMESTAMP): add basic support for UNIX_TIMESTAMP function

    feat(UNIX_TIMESTAMP): add basic support for UNIX_TIMESTAMP function

    Added basic support for UNIX_TIMESTAMP() function:

    • without arguments: returns current timestamp
    • with single argument: transforms string representation of date/datetime to timestamp

    Things could be done:

    • add some checks for invalid values/formats
    • add support for timezones
    • support for floating point type of return value
      mysql> SELECT UNIX_TIMESTAMP('2015-11-13 10:20:19.012');
          -> 1447431619.012
      

    Docs for function - MySQL 8.0 Reference Manual

    opened by rtm-ctrlz 6
  • Add Exec and Query methods to FakePDO

    Add Exec and Query methods to FakePDO

    Adds other public methods (rather than just prepare() to FakePdo)

    The current master branch state of Psalm surfaces the following fails:

    ERROR: ArgumentTypeCoercion - src/Parser/SQLLexer.php:24:13 - Argument 4 of preg_split expects int(0)|int(1)|int(2)|int(3)|int(4)|int(5)|int(6)|int(7), parent type positive-int provided (see https://psalm.dev/193)
                \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_OFFSET_CAPTURE
    
    
    ERROR: InvalidArgument - src/Parser/SQLLexer.php:32:44 - Argument 1 of Vimeo\MysqlEngine\Parser\SQLLexer::groupComments expects list<array{string, int}>, list<array{string, int}|string> provided (see https://psalm.dev/004)
                $tokens = $this->groupComments($tokens);
    
    
    ERROR: InvalidArgument - src/Parser/SQLLexer.php:35:51 - Argument 1 of Vimeo\MysqlEngine\Parser\SQLLexer::groupEscapeSequences expects list<array{string, int}>, list<array{string, int}|string> provided (see https://psalm.dev/004)
                $tokens = $this->groupEscapeSequences($tokens);
    
    
    ERROR: InvalidArgument - src/Parser/SQLLexer.php:37:41 - Argument 1 of Vimeo\MysqlEngine\Parser\SQLLexer::groupQuotedTokens expects list<array{string, int}>, list<array{string, int}|string> provided (see https://psalm.dev/004)
            return $this->groupQuotedTokens($tokens);
    

    This branch does not resolve these, but also does not add new fails.

    opened by M1ke 4
  • Fix CI configuration and update Psalm for development

    Fix CI configuration and update Psalm for development

    The CI for this project continues to fail since https://github.com/vimeo/php-mysql-engine/commit/144d67bd725e01ae9736e5632dc89cf9ffe87e0a, affecting #15 and #16.

    These issues have been resolved in the latest version of Psalm, so updating the dependencies for this project will be successful.

    opened by zonuexe 2
  • Adds INET_ATON and INET_NTOA mysql methods

    Adds INET_ATON and INET_NTOA mysql methods

    Adds 2 new methods to function evaluator, INET_ATON and INET_NTOA

    https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_inet-aton

    opened by aaronm67 1
  • Fix a warning when DateTime column has empty value

    Fix a warning when DateTime column has empty value

    Dear reviewer,

    When you have a DateTime column and the value is empty a warning was raised.

    The code first checks the string at position 0 then check if is "empty".

    On PHP version <= 7.4 A warning is raised if you try to access [0] of an "Uninitialized" string and $foo = '' is an "Uninitialized" string 🤷‍♂️ .

    (I haven't check if this behavior still exists on PHP >=8.0).

    The change is just a small change in the order of an existing code.

    image

    opened by xinhus 1
  • Stub errorInfo() to prevent upstream issues

    Stub errorInfo() to prevent upstream issues

    The library is written for exception error handling (which is fine), but if any code treats it as generic PDO and calls errorInfo() things fall apart:

    1. Native PDO code barfs rather annoying No error: PDO constructor was not called warning, that isn't easy to work around.
    2. The return is null, rather than supposed three fields array structure.

    Since the any real error would be an exception anyway, I stubbed the methods to hardcode a no-error state for anything that asks.

    Not confident that is the way to go about it, but don't have a better idea. Open to explore alternative takes if there are any pointers. :)

    opened by Rarst 1
  • create table default data for integer

    create table default data for integer

    CREATE TABLE `orders`
    (
    	`id` INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    	`user_id` INTEGER(11) UNSIGNED,
    	`price` INTEGER(11) UNSIGNED NOT NULL DEFAULT 0,
    	`created_on`  timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    	`modified_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    	PRIMARY KEY (`id`)
    )ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
    

    Notice Error...

    Notice Error: Uninitialized string offset: 0 in [/path/to/vendor/vimeo/php-mysql-engine/src/Parser/CreateTableParser.php, line 936]

    opened by kojirock5260 1
  • Set default fetch style

    Set default fetch style

    PDO::ATTR_DEFAULT_FETCH_MODE is the key for setAttribute, not fetch_style. (https://www.php.net/manual/en/pdo.setattribute.php) The default for fetch_style is PDO::FETCH_BOTH, so set it. (https://www.php.net/manual/en/pdostatement.fetch.php) And I made it possible to set DEFAULT_FETCH_MODE with setAttribute. This allows we to omit the fetch argument.

    opened by Javakky-pxv 1
  • Supports SHOW INDEX syntax

    Supports SHOW INDEX syntax

    Implement SHOW INDEX syntax. Set null for columns that depend on unimplemented processing. A value that is set to CreateIndex but not set to Index is set to null for the time being. Since Index->$column is a string array, it is necessary to change the type, and it was expected that the scale would increase.

    opened by Javakky-pxv 1
  • Add PDO::quote() to FakePdoTrait

    Add PDO::quote() to FakePdoTrait

    Implement PDO::quote().

    Why

    We maintain a SQL template engine that depends on escaping with PDO::quote(). Currently we have this method in a class that inherits from FakePdo.

    opened by zonuexe 1
  • feat(TruncateParser): update `TRUNCATE`

    feat(TruncateParser): update `TRUNCATE`

    • promote truncate to clause, that also allows to use lowercase form truncate
    • add optional form TRUNCATE TABLE <table name>
    • add check for invalid $tokens array at TruncateParser::parse (ex. empty array), this could be helpful for direct usage of TruncateParser class (I don't know who will ever need this, but this is the way things should be done)
    • add unit tests for TruncateParser class
    opened by rtm-ctrlz 1
Releases(0.5.6)
Owner
Vimeo
You know, for videos.
Vimeo
PHP client for Selenium/WebDriver protocol. Previously facebook/php-webdriver

Php-webdriver library is PHP language binding for Selenium WebDriver, which allows you to control web browsers from PHP.

php-webdriver 4.7k Jan 3, 2023
The modern, simple and intuitive PHP unit testing framework.

atoum PHP version atoum version 5.3 -> 5.6 1.x -> 3.x 7.2 -> 8.x 4.x (current) A simple, modern and intuitive unit testing framework for PHP! Just lik

atoum 1.4k Nov 29, 2022
Full-stack testing PHP framework

Codeception Modern PHP Testing for everyone Codeception is a modern full-stack testing framework for PHP. Inspired by BDD, it provides an absolutely n

Codeception Testing Framework 4.6k Jan 7, 2023
Faker is a PHP library that generates fake data for you

Faker Faker is a PHP library that generates fake data for you. Whether you need to bootstrap your database, create good-looking XML documents, fill-in

FakerPHP 2.7k Dec 27, 2022
Mock HTTP requests on the server side in your PHP unit tests

HTTP Mock for PHP Mock HTTP requests on the server side in your PHP unit tests. HTTP Mock for PHP mocks the server side of an HTTP request to allow in

InterNations GmbH 386 Dec 27, 2022
AST based PHP Mutation Testing Framework

Infection - Mutation Testing framework Please read documentation here: infection.github.io Twitter: @infection_php Discord: https://discord.gg/ZUmyHTJ

Infection - Mutation Testing Framework for PHP 1.8k Jan 2, 2023
:heavy_check_mark: PHP Test Framework for Freedom, Truth, and Justice

Kahlan is a full-featured Unit & BDD test framework a la RSpec/JSpec which uses a describe-it syntax and moves testing in PHP one step forward. Kahlan

Kahlan 1.1k Jan 2, 2023
Event driven BDD test framework for PHP

The highly extensible, highly enjoyable, PHP testing framework. Read more at peridot-php.github.io or head over to the wiki. Building PHAR Peridot's p

Peridot 327 Jan 5, 2023
PHP Mocking Framework

Phake Phake is a framework for PHP that aims to provide mock objects, test doubles and method stubs. Phake was inspired by a lack of flexibility and e

Phake 469 Dec 2, 2022
BDD test framework for PHP

BDD test framework for PHP, inspired by Jasmine and RSpec. Features a familiar syntax, and a watch command to automatically re-run specs during develo

Daniel St. Jules 286 Nov 12, 2022
Mock built-in PHP functions (e.g. time(), exec() or rand())

PHP-Mock: mocking built-in PHP functions PHP-Mock is a testing library which mocks non deterministic built-in PHP functions like time() or rand(). Thi

null 331 Jan 3, 2023
SpecBDD Framework for PHP

phpspec The main website with documentation is at http://www.phpspec.net. Installing Dependencies Dependencies are handled via composer: wget -nc http

PHPSpec Framework 1.8k Dec 30, 2022
The PHP Unit Testing framework.

PHPUnit PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. Installat

Sebastian Bergmann 18.8k Jan 4, 2023
Highly opinionated mocking framework for PHP 5.3+

Prophecy Prophecy is a highly opinionated yet very powerful and flexible PHP object mocking framework. Though initially it was created to fulfil phpsp

PHPSpec Framework 8.5k Jan 3, 2023
PHP unit testing framework with built in mocks and stubs. Runs in the browser, or via the command line.

Enhance PHP A unit testing framework with mocks and stubs. Built for PHP, in PHP! Quick Start: Just add EnhanceTestFramework.php and you are ready to

Enhance PHP 67 Sep 12, 2022
A PHP Module, that help with geneting of task script for playwright and send it node.js

A PHP Module, that help with geneting of task script for playwright and send it node.js

LuKa 13 Dec 7, 2022
Library that provides collection, processing, and rendering functionality for PHP code coverage information.

phpunit/php-code-coverage Provides collection, processing, and rendering functionality for PHP code coverage information. Installation You can add thi

Sebastian Bergmann 8.5k Jan 5, 2023
A PHP library for mocking date and time in tests

ClockMock Slope s.r.l. ClockMock provides a way for mocking the current timestamp used by PHP for \DateTime(Immutable) objects and date/time related f

Slope 44 Dec 7, 2022