Compare commits
4 commits
main
...
legacy-dig
Author | SHA1 | Date | |
---|---|---|---|
|
5fe55c2c0d | ||
|
171e32fc18 | ||
|
0a2bbe44a1 | ||
|
7c4a51f95e |
58 changed files with 2571 additions and 2129 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,7 +1,5 @@
|
|||
vendor
|
||||
composer.lock
|
||||
.phpunit.result.cache
|
||||
test.php
|
||||
test.sqlite
|
||||
*.test.sqlite
|
||||
.DS_Store
|
||||
/vendor/
|
||||
*.tmp
|
||||
/test.php
|
||||
*.ignore
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
image: php:7.1-alpine
|
||||
|
||||
services:
|
||||
- mysql:5.7
|
||||
|
||||
variables:
|
||||
MYSQL_DATABASE: destructr_test
|
||||
MYSQL_ROOT_PASSWORD: badpassword
|
||||
|
||||
before_script:
|
||||
- apk update
|
||||
- apk add git
|
||||
- docker-php-ext-install pdo
|
||||
- curl -sS https://getcomposer.org/installer | php
|
||||
- php composer.phar install
|
||||
|
||||
test:local:
|
||||
script:
|
||||
- php composer.phar test-local
|
||||
|
||||
test:mysql:
|
||||
script:
|
||||
- docker-php-ext-install pdo_mysql
|
||||
- php composer.phar test-mysql
|
||||
|
||||
test:sqlite:
|
||||
script:
|
||||
- php composer.phar test-sqlite
|
8
.travis.yml
Normal file
8
.travis.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
dist: precise
|
||||
language: php
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
install: composer install
|
96
README.md
96
README.md
|
@ -1,82 +1,32 @@
|
|||
# Destructr
|
||||
# Digraph DataObject v0.5
|
||||
|
||||
Destructr is a specialized ORM that allows a seamless mix of structured, relational data with unstructured JSON data.
|
||||
[![Build Status](https://travis-ci.org/digraphcms/digraph-dataobject.svg?branch=v0.5)](https://travis-ci.org/digraphcms/digraph-dataobject)
|
||||
|
||||
## Getting started
|
||||
*This is not going to be the final version of Digraph DataObjects.*
|
||||
I will be supporting the v0.5 branch with its current interface, at least for myself, because I am using it in a few production projects. Those projects are stuck on some pretty weird old custom builds of PHP, and so this branch will be maintaining PHP 5.3 compatibility for the foreseeable future. If you're on a more recent version than PHP 5.3 (as you very much should be), you shouldn't use this branch. You should instead wait for the 1.0 release, which should hopefully be in 2018 sometime. The 1.0 branch will fix a lot of conceptual shortcomings of the alpha versions, and is designed to target modern environments and take full advantage of all the features of PHP 7.1 that make it finally act something like a grown-up programming language.
|
||||
|
||||
The purpose of Destructr is to allow many "types" of objects to be stored in a single table.
|
||||
Every Destructr Data Object (DSO) simply contains an array of data that will be saved into the database as JSON.
|
||||
Array access is also flattened, using dots as delimiters, so rather than reading `$dso["foo"]["bar"]` you access that data via `$dso["foo.bar"]`.
|
||||
This is for two reasons.
|
||||
It sidesteps the issue of updating nested array values by reference, and it creates an unambiguous way of locating a node of the unstructured data with a string, which mirrors how we can write SQL queries to reference them.
|
||||
So I guess in a nutshell: This version is relatively stable, and the author is actually using it in production. The interface shouldn't change significantly, unless I find something absolutely terrible. You still probably shouldn't use it, though, because it's targeting a truly decrepit version of PHP and better things are on the way.
|
||||
|
||||
If this sounds like an insanely slow idea, that's because it is.
|
||||
Luckily MySQL and MariaDB have mechanisms we can take advantage of to make generated columns from any part of the unstructured data, so that pieces of it can be pulled out into their own virtual columns for indexing and faster searching/sorting.
|
||||
Also this version will probably never be properly documented.
|
||||
|
||||
### Database driver and factory
|
||||
## MIT License
|
||||
|
||||
In order to read/write objects from a database table, you'll need to configure a Driver and Factory class.
|
||||
Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
```php
|
||||
// DriverFactory::factory() has the same arguments as PDO::__construct
|
||||
// You can also construct a driver directly, from a class in Drivers,
|
||||
// but for common databases DriverFactory::factory should pick the right class
|
||||
$driver = \Destructr\DriverFactory::factory(
|
||||
'mysql:host=127.0.0.1',
|
||||
'username',
|
||||
'password'
|
||||
);
|
||||
// Driver is then used to construct a Factory
|
||||
$factory = new \Destructr\Factory(
|
||||
$driver, //driver is used to manage connection and generate queries
|
||||
'dso_objects' //all of a Factory's data is stored in a single table
|
||||
);
|
||||
```
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
### Creating a new record
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
Next, you can use the factory to create a new record object.
|
||||
|
||||
```php
|
||||
// by default all objects are the DSO class, but factories can be made to use
|
||||
// other classes depending on what data objects are instantiated with
|
||||
$obj = $factory->create();
|
||||
|
||||
// returns boolean indicating whether insertion succeeded
|
||||
// insert() must be called before update() will work
|
||||
$obj->insert();
|
||||
|
||||
// set a new value and call update() to save changes into database. update()
|
||||
// will return true without doing anything if no changes have been made.
|
||||
$obj['foo.bar'] = 'some value';
|
||||
$obj->update();
|
||||
|
||||
// deleting an object will by default just set dso.deleted to the current time
|
||||
// objects with a non-null dso.deleted are excluded from queries by default
|
||||
// delete() calls update() inside it, so its effect is immediate
|
||||
$obj->delete();
|
||||
|
||||
// objects that were deleted via default delete() are recoverable via undelete()
|
||||
// undelete() also calls update() for you
|
||||
$obj->undelete();
|
||||
|
||||
// objects can be actually removed from the table by calling delete(true)
|
||||
$obj->delete(true);
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
This system relies **heavily** on the JSON features of the underlying database.
|
||||
This means it cannot possibly run without a database that supports JSON features.
|
||||
Exact requirements are in flux during development, but basically if a database doesn't have JSON functions it's probably impossible for Destructr to ever work with it.
|
||||
|
||||
In practice this means Destructr will **never** be able to run on less than the following versions of the following popular databases:
|
||||
|
||||
* MySQL >=5.7
|
||||
* MariaDB >=10.2
|
||||
* PostgreSQL >=9.3
|
||||
* SQL Server >=2016
|
||||
|
||||
Theoretically Destructr is also an excellent fit for NoSQL databases.
|
||||
If I ever find myself needing it there's a good chance it's possible to write drivers for running it on something like MongoDB as well.
|
||||
It might even be kind of easy.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
|
@ -1,41 +1,33 @@
|
|||
{
|
||||
"name": "byjoby/destructr",
|
||||
"description": "A library for storing a mix of structured and unstructured data in relational databases",
|
||||
"description": "Interfaces and abstract classes for managing CRUD through straightforward PHP classes. Primarily focused on creating objects that yield clean readable code where they are used.",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"authors": [{
|
||||
"name": "Joby Elliott",
|
||||
"email": "joby@byjoby.com"
|
||||
}],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"mofodojodino/profanity-filter": "^1.3",
|
||||
"byjoby/flatrr": "^1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7",
|
||||
"phpunit/dbunit": "^4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"phpunit"
|
||||
],
|
||||
"test-local": [
|
||||
"phpunit --testsuite Local"
|
||||
],
|
||||
"test-mysql": [
|
||||
"phpunit --testsuite MySQL"
|
||||
],
|
||||
"test-sqlite": [
|
||||
"phpunit --testsuite SQLite"
|
||||
]
|
||||
"php": ">=5.3.3",
|
||||
"fpdo/fluentpdo": "^1.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Destructr\\": "src/"
|
||||
"Digraph\\DataObject\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Destructr\\": "tests/"
|
||||
"Digraph\\DataObject\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8",
|
||||
"phpunit/dbunit": "^1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"phpunit"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
10
phpunit.xml
10
phpunit.xml
|
@ -1,15 +1,7 @@
|
|||
<phpunit bootstrap="vendor/autoload.php">
|
||||
<testsuites>
|
||||
<testsuite name="Local">
|
||||
<testsuite name="Digraph Tests">
|
||||
<directory>tests</directory>
|
||||
<exclude>tests/Drivers</exclude>
|
||||
<exclude>tests/LegacyDrivers</exclude>
|
||||
</testsuite>
|
||||
<testsuite name="MySQL">
|
||||
<directory>tests/Drivers/MySQL</directory>
|
||||
</testsuite>
|
||||
<testsuite name="SQLite">
|
||||
<directory>tests/LegacyDrivers/SQLite</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
|
|
77
src/AbstractArrayDataObject.php
Normal file
77
src/AbstractArrayDataObject.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject;
|
||||
|
||||
abstract class AbstractArrayDataObject extends AbstractDataObject implements \ArrayAccess, \Iterator
|
||||
{
|
||||
protected $iterMap = array();
|
||||
protected $iterPos = 0;
|
||||
|
||||
protected function buildIterMap()
|
||||
{
|
||||
$this->iterMap = array();
|
||||
foreach ($this->map() as $key => $value) {
|
||||
$this->iterMap[] = $key;
|
||||
}
|
||||
}
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->$offset = $value;
|
||||
$this->buildIterMap();
|
||||
}
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->$offset);
|
||||
}
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new Exceptions\Exception("Can't unset a DataObject field", 1);
|
||||
}
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
$return = $this->$offset;
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->iterPos = 0;
|
||||
}
|
||||
public function ¤t()
|
||||
{
|
||||
$key = $this->key();
|
||||
if (isset($this->$key)) {
|
||||
$return = $this->$key;
|
||||
return $return;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function key()
|
||||
{
|
||||
return isset($this->iterMap[$this->iterPos]) ? $this->iterMap[$this->iterPos] : null;
|
||||
}
|
||||
public function next()
|
||||
{
|
||||
$this->iterPos++;
|
||||
}
|
||||
public function valid()
|
||||
{
|
||||
$key = $this->key();
|
||||
return isset($this->$key);
|
||||
}
|
||||
}
|
549
src/AbstractDataObject.php
Normal file
549
src/AbstractDataObject.php
Normal file
|
@ -0,0 +1,549 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject;
|
||||
|
||||
use \Digraph\DataObject\Files\FilesContainer;
|
||||
use \Digraph\DataObject\JSON\JSONContainer;
|
||||
|
||||
abstract class AbstractDataObject implements DataObjectInterface
|
||||
{
|
||||
/**
|
||||
* Characters that are allowed in ID generation
|
||||
* @var string
|
||||
*/
|
||||
protected static $_idChars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
/**
|
||||
* Length of random portion of ID -- with the default 36 character set, 12
|
||||
* characters gives you about 62 bits of info, which is probably good for
|
||||
* most applications, even after profanity filtering
|
||||
* @var int
|
||||
*/
|
||||
protected static $_idLength = 12;
|
||||
|
||||
/**
|
||||
* Prefix for this class's objects
|
||||
* @var string
|
||||
*/
|
||||
protected static $_idPrefix = 'do-';
|
||||
|
||||
/**
|
||||
* Prefix for this class's storage names
|
||||
* @var string
|
||||
*/
|
||||
protected static $_storagePrefix = '';
|
||||
|
||||
/**
|
||||
* Stores the current data for this object
|
||||
* @var array
|
||||
*/
|
||||
protected $_data = array();
|
||||
|
||||
/**
|
||||
* Stores a list of what data has been changed in this object
|
||||
* @var array
|
||||
*/
|
||||
protected $_dataChanged = array();
|
||||
|
||||
/**
|
||||
* Stores the rules for transforming properties as they are get/set
|
||||
* @var array
|
||||
*/
|
||||
protected $_transforms = array();
|
||||
|
||||
/**
|
||||
* Holds static transforms that are loaded for each class at construction
|
||||
* @var array
|
||||
*/
|
||||
protected static $_classTransforms = array(
|
||||
'JSON' => array(
|
||||
'class' => '\\Digraph\\DataObject\\DataTransformers\\JSON'
|
||||
),
|
||||
'bool' => array(
|
||||
'set' => '\\Digraph\\DataObject\\DataTransformers\\SimpleTransforms::bool_set',
|
||||
'get' => '\\Digraph\\DataObject\\DataTransformers\\SimpleTransforms::bool_get'
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds transformation objects for properties that have them defined via
|
||||
* class transforms and the map
|
||||
* @var array
|
||||
*/
|
||||
protected $_transformObjects = array();
|
||||
|
||||
/**
|
||||
* Construct a new copy of a DataObject -- data values can be specified,
|
||||
* and those with defaults will be populated and those with generators will
|
||||
* be generated (overriding anything the user requests)
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct($data = array(), $fromRaw = false)
|
||||
{
|
||||
$this->registerClassTransforms();
|
||||
//set up class transformers
|
||||
foreach ($this->map() as $name => $map) {
|
||||
if (isset($map['transform'])) {
|
||||
if (isset($this->_transforms[$map['transform']]['class'])) {
|
||||
$class = $this->_transforms[$map['transform']]['class'];
|
||||
$this->_transformObjects[$name] = new $class($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
//set values
|
||||
if (!$fromRaw) {
|
||||
$data = $this->dataGenerate($data, true);
|
||||
foreach ($data as $name => $value) {
|
||||
$this->set($name, $value);
|
||||
}
|
||||
} else {
|
||||
foreach ($data as $name => $value) {
|
||||
$this->setRaw($name, $value);
|
||||
}
|
||||
}
|
||||
//build itermap
|
||||
$this->buildIterMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* getter for retrieving all changed data
|
||||
* @return array
|
||||
*/
|
||||
public function _get_dataChanged()
|
||||
{
|
||||
foreach ($this->_transformObjects as $name => $object) {
|
||||
$this->_dataChanged[$name] = true;
|
||||
}
|
||||
$changed = array();
|
||||
foreach ($this->_dataChanged as $name => $value) {
|
||||
$changed[] = $name;
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* getter for the raw storage values of all data
|
||||
* @return array
|
||||
*/
|
||||
public function _get_dataRaw()
|
||||
{
|
||||
$this->dataChanged;
|
||||
return $this->_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* walk up the inheritance list, registering all class transforms
|
||||
* @return void
|
||||
*/
|
||||
protected function registerClassTransforms($class = false)
|
||||
{
|
||||
if (!$class) {
|
||||
$class = get_called_class();
|
||||
}
|
||||
$parent = false;
|
||||
if ($parent = get_parent_class($class)) {
|
||||
$parent::registerClassTransforms($parent);
|
||||
}
|
||||
foreach ($class::$_classTransforms as $key => $value) {
|
||||
$this->_transforms[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of name/value pairs, fill it out with any defaults or
|
||||
* anything that must be generated per the map
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function dataGenerate($data = array())
|
||||
{
|
||||
$dataOut = array();
|
||||
//set values of $dataOut from $data or defaults and queue things that need generation
|
||||
foreach (static::map() as $name => $conf) {
|
||||
if (isset($data[$name])) {
|
||||
//simple value being set in data
|
||||
$dataOut[$name] = $data[$name];
|
||||
} elseif (isset($conf['generated'])) {
|
||||
//generated value
|
||||
$dataOut[$name] = $this->generateValue($name);
|
||||
} elseif (isset($conf['default'])) {
|
||||
//default value
|
||||
$dataOut[$name] = $conf['default'];
|
||||
} else {
|
||||
//null is the default of defaults
|
||||
$dataOut[$name] = null;
|
||||
}
|
||||
}
|
||||
//return
|
||||
return $dataOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a value for a particular parameter name
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function generateValue($name)
|
||||
{
|
||||
$map = $this->mapEntry($name);
|
||||
$generator = $map['generated'];
|
||||
if (method_exists($this, $generator)) {
|
||||
return $this->$generator($name);
|
||||
}
|
||||
if (is_callable($generator)) {
|
||||
return $generator($name);
|
||||
}
|
||||
throw new Exceptions\Exception("No valid generator found for $name", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value using its custom getter
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get($name)
|
||||
{
|
||||
$map = $this->mapEntry($name);
|
||||
//getters by property name
|
||||
$getter = '_get_'.$name;
|
||||
if (method_exists($this, $getter)) {
|
||||
return $this->$getter($name);
|
||||
}
|
||||
//transformed
|
||||
if (isset($map['transform']) && isset($this->_transforms[$map['transform']])) {
|
||||
$transform = $this->_transforms[$map['transform']];
|
||||
//transformation object
|
||||
if (isset($transform['class'])) {
|
||||
return $this->ref($this->_transformObjects[$name]);
|
||||
}
|
||||
//transformation function
|
||||
if (isset($transform['get'])) {
|
||||
$getter = $transform['get'];
|
||||
if (method_exists($this, $getter)) {
|
||||
return $this->$getter($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
//just regular return
|
||||
if (array_key_exists($name, $this->_data)) {
|
||||
$return = $this->getRaw($name);
|
||||
return $return;
|
||||
}
|
||||
$return = null;
|
||||
return $return;
|
||||
}
|
||||
|
||||
protected function &ref(&$var)
|
||||
{
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a field using its custom setters
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected function set($name, $value)
|
||||
{
|
||||
$map = $this->mapEntry($name);
|
||||
//setters by property name
|
||||
$setter = '_set_'.$name;
|
||||
if (method_exists($this, $setter)) {
|
||||
$this->$setter($name, $value);
|
||||
return $this->get($name);
|
||||
}
|
||||
//transformed
|
||||
if (isset($map['transform']) && isset($this->_transforms[$map['transform']])) {
|
||||
$transform = $this->_transforms[$map['transform']];
|
||||
//transformation object
|
||||
if (isset($transform['class'])) {
|
||||
if ($this->_transformObjects[$name]->getUserValue() != $value) {
|
||||
$this->_dataChanged[$name] = true;
|
||||
}
|
||||
$this->_transformObjects[$name]->setUserValue($value);
|
||||
return $this->get($name);
|
||||
}
|
||||
//transformation function
|
||||
$transform = $this->_transforms[$map['transform']];
|
||||
if (isset($transform['set'])) {
|
||||
$setter = $transform['set'];
|
||||
if (method_exists($this, $setter)) {
|
||||
if ($value != $this->get($name)) {
|
||||
$this->_dataChanged[$name] = true;
|
||||
}
|
||||
$this->$setter($name, $value);
|
||||
return $this->get($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
//just regular value
|
||||
if (array_key_exists($name, $this->map())) {
|
||||
if ($value != $this->get($name)) {
|
||||
$this->_dataChanged[$name] = true;
|
||||
}
|
||||
$this->setRaw($name, $value);
|
||||
return $this->get($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
$mapEntry = $this->mapEntry($name);
|
||||
//hide masked values
|
||||
if (isset($mapEntry['masked']) && $mapEntry['masked']) {
|
||||
$return = null;
|
||||
return $return;
|
||||
}
|
||||
//use internal getter
|
||||
return $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
//load map entry
|
||||
$mapEntry = $this->mapEntry($name);
|
||||
//check for not setting masked values
|
||||
if (isset($mapEntry['masked']) && $mapEntry['masked']) {
|
||||
return null;
|
||||
}
|
||||
//check for not setting system values
|
||||
if (isset($mapEntry['system']) && $mapEntry['system']) {
|
||||
throw new Exceptions\Exception("Public interface doesn't allow setting system values (tried to set $name)", 1);
|
||||
}
|
||||
//do the actual setting
|
||||
return $this->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
//load map entry
|
||||
$mapEntry = $this->mapEntry($name);
|
||||
//hide masked values
|
||||
if (isset($mapEntry['masked']) && $mapEntry['masked']) {
|
||||
return false;
|
||||
}
|
||||
//function name getters
|
||||
$getter = '_get_' . $name;
|
||||
if (method_exists($this, $getter)) {
|
||||
return true;
|
||||
}
|
||||
//map names
|
||||
if (array_key_exists($name, $this->map())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the raw value of an item
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRaw($name)
|
||||
{
|
||||
if (!$this->mapEntry($name)) {
|
||||
throw new Exceptions\Exception("\"$name\" not mapped for \"".get_called_class()."\"", 1);
|
||||
}
|
||||
if (isset($this->_transformObjects[$name])) {
|
||||
$this->_data[$name] = $this->_transformObjects[$name]->getStorageValue();
|
||||
}
|
||||
if (!isset($this->_data[$name])) {
|
||||
return null;
|
||||
}
|
||||
return $this->_data[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* set the raw value of an item
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected function setRaw($name, $value)
|
||||
{
|
||||
if (!array_key_exists($name, $this->map())) {
|
||||
throw new Exceptions\Exception("\"$name\" not mapped for \"".get_called_class()."\"", 1);
|
||||
}
|
||||
//transform object
|
||||
if (isset($this->_transformObjects[$name])) {
|
||||
$this->_transformObjects[$name]->setStorageValue($value);
|
||||
$value = $this->_transformObjects[$name]->getStorageValue();
|
||||
if (!isset($this->_data[$name])) {
|
||||
$this->_data[$name] = $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
//regular value
|
||||
$this->_data[$name] = $value;
|
||||
return $this->_data[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the same data as getMap, but with $_storagePrefix applied to each
|
||||
* entry's name data.
|
||||
* @return Array
|
||||
*/
|
||||
protected static $_maps = array();
|
||||
public static function map()
|
||||
{
|
||||
$class = get_called_class();
|
||||
if (!isset(static::$_maps[$class])) {
|
||||
$map = static::getMap();
|
||||
if (static::$_storagePrefix) {
|
||||
foreach ($map as $key => $value) {
|
||||
if (strpos($map[$key]['name'], 'do_') === 0) {
|
||||
$map[$key]['name'] = preg_replace('/^do_/', static::$_storagePrefix, $value['name']);
|
||||
} else {
|
||||
$map[$key]['name'] = static::$_storagePrefix.$map[$key]['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
static::$_maps[$class] = $map;
|
||||
}
|
||||
return static::$_maps[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of this class's data names and how they are named in the
|
||||
* underlying storage layer. Abstract classes provide the bare minimum
|
||||
* mapping to create a DataObject, so child classes should extend what is
|
||||
* returned by parent::getMap()
|
||||
* @return Array
|
||||
*/
|
||||
public static function getMap()
|
||||
{
|
||||
return array(
|
||||
'do_id' => array(
|
||||
'name' => 'do_id',
|
||||
'system' => true,
|
||||
'generated' => 'generateID'
|
||||
),
|
||||
'do_cdate' => array(
|
||||
'name' => 'do_cdate',
|
||||
'system' => true,
|
||||
'generated' => 'time'
|
||||
),
|
||||
'do_cuser' => array(
|
||||
'name' => 'do_cuser',
|
||||
'system' => true,
|
||||
'generated' => 'generateUser',
|
||||
'transform' => 'JSON'
|
||||
),
|
||||
'do_mdate' => array(
|
||||
'name' => 'do_mdate',
|
||||
'system' => true,
|
||||
'generated' => 'time'
|
||||
),
|
||||
'do_muser' => array(
|
||||
'name' => 'do_muser',
|
||||
'system' => true,
|
||||
'generated' => 'generateUser',
|
||||
'transform' => 'JSON'
|
||||
),
|
||||
'do_deleted' => array(
|
||||
'name' => 'do_deleted',
|
||||
'system' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a map entry by name
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public static function mapEntry($name)
|
||||
{
|
||||
$class = get_called_class();
|
||||
if (!isset(static::$_maps[$class])) {
|
||||
static::map();
|
||||
}
|
||||
if (isset(static::$_maps[$class][$name])) {
|
||||
return static::$_maps[$class][$name];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the storage name of a map entry
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function storageName($name)
|
||||
{
|
||||
$mapEntry = static::mapEntry($name);
|
||||
if (!$mapEntry) {
|
||||
return false;
|
||||
}
|
||||
return $mapEntry['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a new ID
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public static function generateID($name, $depth = 0)
|
||||
{
|
||||
if ($depth >= 10) {
|
||||
throw new Exceptions\IDSpaceException();
|
||||
}
|
||||
$id = '';
|
||||
while (strlen($id) < static::$_idLength) {
|
||||
$id .= substr(
|
||||
static::$_idChars,
|
||||
rand(0, strlen(static::$_idChars)-1),
|
||||
1
|
||||
);
|
||||
}
|
||||
$id = static::$_idPrefix.$id;
|
||||
if (Utils\BadWords::profane($id) || static::idExists($id)) {
|
||||
return static::generateID($name, $depth+1);
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an array holding information about the current user
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public static function generateUser($name)
|
||||
{
|
||||
$out = array();
|
||||
$out['ip'] = $_SERVER['REMOTE_ADDR'];
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$out['fw'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
121
src/DSO.php
121
src/DSO.php
|
@ -1,121 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr;
|
||||
|
||||
use \Flatrr\FlatArray;
|
||||
|
||||
/**
|
||||
* Interface for DeStructure Objects (DSOs). These are the class that is
|
||||
* actually used for storing and retrieving partially-structured data from the
|
||||
* database.
|
||||
*/
|
||||
class DSO extends FlatArray implements DSOInterface
|
||||
{
|
||||
protected $factory;
|
||||
protected $changes;
|
||||
protected $removals;
|
||||
|
||||
public function __construct(array $data = null, DSOFactoryInterface &$factory = null)
|
||||
{
|
||||
$this->resetChanges();
|
||||
parent::__construct($data);
|
||||
$this->factory($factory);
|
||||
$this->resetChanges();
|
||||
}
|
||||
|
||||
public function hook_create()
|
||||
{
|
||||
//does nothing
|
||||
}
|
||||
public function hook_update()
|
||||
{
|
||||
//does nothing
|
||||
}
|
||||
|
||||
public function delete(bool $permanent = false) : bool
|
||||
{
|
||||
return $this->factory->delete($this, $permanent);
|
||||
}
|
||||
|
||||
public function undelete() : bool
|
||||
{
|
||||
return $this->factory->undelete($this);
|
||||
}
|
||||
|
||||
public function insert() : bool
|
||||
{
|
||||
return $this->factory()->insert($this);
|
||||
}
|
||||
|
||||
public function update() : bool
|
||||
{
|
||||
return $this->factory()->update($this);
|
||||
}
|
||||
|
||||
public function resetChanges()
|
||||
{
|
||||
$this->changes = new FlatArray();
|
||||
$this->removals = new FlatArray();
|
||||
}
|
||||
|
||||
public function changes() : array
|
||||
{
|
||||
return $this->changes->get();
|
||||
}
|
||||
|
||||
public function removals() : array
|
||||
{
|
||||
return $this->removals->get();
|
||||
}
|
||||
|
||||
public function set(string $name = null, $value, $force=false)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
if ($this->get($name) === $value) {
|
||||
return;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
//check for what's being removed
|
||||
if (is_array($this->get($name))) {
|
||||
foreach ($this->get($name) as $k => $v) {
|
||||
if (!isset($value[$k])) {
|
||||
if ($name) {
|
||||
$k = $name.'.'.$k;
|
||||
}
|
||||
$this->unset($k);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parent::set($name, []);
|
||||
}
|
||||
//recursively set individual values so we can track them
|
||||
foreach ($value as $k => $v) {
|
||||
if ($name) {
|
||||
$k = $name.'.'.$k;
|
||||
}
|
||||
$this->set($k, $v, $force);
|
||||
}
|
||||
} else {
|
||||
$this->changes->set($name, $value);
|
||||
unset($this->removals[$name]);
|
||||
parent::set($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function unset(?string $name)
|
||||
{
|
||||
if (isset($this[$name])) {
|
||||
$this->removals->set($name, $this->get($name));
|
||||
unset($this->changes[$name]);
|
||||
parent::unset($name);
|
||||
}
|
||||
}
|
||||
|
||||
public function factory(DSOFactoryInterface &$factory = null) : ?DSOFactoryInterface
|
||||
{
|
||||
if ($factory) {
|
||||
$this->factory = $factory;
|
||||
}
|
||||
return $this->factory;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr;
|
||||
|
||||
interface DSOFactoryInterface
|
||||
{
|
||||
public function __construct(Drivers\DSODriverInterface &$driver, string $table);
|
||||
|
||||
public function class(array $data) : ?string;
|
||||
|
||||
public function createTable() : bool;
|
||||
public function create(array $data = array()) : DSOInterface;
|
||||
public function read(string $value, string $field = 'dso.id', $deleted = false) : ?DSOInterface;
|
||||
public function insert(DSOInterface &$dso) : bool;
|
||||
public function update(DSOInterface &$dso) : bool;
|
||||
public function delete(DSOInterface &$dso, bool $permanent = false) : bool;
|
||||
|
||||
public function search() : Search;
|
||||
public function executeSearch(Search $search, array $params = array(), $deleted = false) : array;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr;
|
||||
|
||||
use Flatrr\FlatArrayInterface;
|
||||
|
||||
/**
|
||||
* Interface for DeStructure Objects (DSOs). These are the class that is
|
||||
* actually used for storing and retrieving partially-structured data from the
|
||||
* database.
|
||||
*/
|
||||
interface DSOInterface extends FlatArrayInterface
|
||||
{
|
||||
public function __construct(array $data = null, DSOFactoryInterface &$factory = null);
|
||||
public function factory(DSOFactoryInterface &$factory = null) : ?DSOFactoryInterface;
|
||||
|
||||
public function set(string $name = null, $value, $force=false);
|
||||
|
||||
public function resetChanges();
|
||||
public function changes() : array;
|
||||
public function removals() : array;
|
||||
|
||||
public function insert() : bool;
|
||||
public function update() : bool;
|
||||
public function delete(bool $permanent = false) : bool;
|
||||
public function undelete() : bool;
|
||||
}
|
40
src/DataObjectInterface.php
Normal file
40
src/DataObjectInterface.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject;
|
||||
|
||||
interface DataObjectInterface
|
||||
{
|
||||
public function create();
|
||||
public static function read($id);
|
||||
public static function search($parameters = array(), $sort = array(), $options = array());
|
||||
public function update($action = null);
|
||||
public function delete($permanent = false);
|
||||
|
||||
public static function idExists($id);
|
||||
|
||||
public function __construct($data = array(), $fromRaw = false);
|
||||
|
||||
public static function map();
|
||||
public static function getMap();
|
||||
public static function mapEntry($name);
|
||||
public static function storageName($name);
|
||||
|
||||
public function __get($name);
|
||||
public function __set($name, $value);
|
||||
public function __isset($name);
|
||||
}
|
49
src/DataTransformers/AbstractDataTransformer.php
Normal file
49
src/DataTransformers/AbstractDataTransformer.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\DataTransformers;
|
||||
|
||||
abstract class AbstractDataTransformer implements DataTransformerInterface
|
||||
{
|
||||
protected $parent = null;
|
||||
protected $changed = false;
|
||||
|
||||
public function __construct(&$parent)
|
||||
{
|
||||
$this->setParent($parent);
|
||||
}
|
||||
|
||||
public function setParent(&$parent)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
public function &getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function changed($changed=true)
|
||||
{
|
||||
$this->changed = $changed;
|
||||
}
|
||||
|
||||
public function isChanged()
|
||||
{
|
||||
return $this->changed;
|
||||
}
|
||||
}
|
34
src/DataTransformers/DataTransformerInterface.php
Normal file
34
src/DataTransformers/DataTransformerInterface.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\DataTransformers;
|
||||
|
||||
interface DataTransformerInterface
|
||||
{
|
||||
public function __construct(&$parent);
|
||||
public function setParent(&$parent);
|
||||
public function &getParent();
|
||||
|
||||
public function setStorageValue($storageValue);
|
||||
public function getStorageValue();
|
||||
|
||||
public function setUserValue($userValue);
|
||||
public function getUserValue();
|
||||
|
||||
public function changed($changed=true);
|
||||
public function isChanged();
|
||||
}
|
181
src/DataTransformers/JSON.php
Normal file
181
src/DataTransformers/JSON.php
Normal file
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\DataTransformers;
|
||||
|
||||
use \Digraph\DataObject\DataTransformers\AbstractDataTransformer;
|
||||
|
||||
class JSON extends AbstractDataTransformer implements \ArrayAccess, \Iterator
|
||||
{
|
||||
private $arrayAccessData = array();
|
||||
private $iteratorArrayMap = array();
|
||||
private $jsonParent = null;
|
||||
|
||||
public function __construct(&$parent, &$jsonParent=null)
|
||||
{
|
||||
$this->setParent($parent, $jsonParent);
|
||||
}
|
||||
|
||||
public function changed($changed = true)
|
||||
{
|
||||
parent::changed($changed);
|
||||
if ($this->jsonParent) {
|
||||
$this->jsonParent->changed($changed);
|
||||
}
|
||||
}
|
||||
|
||||
public function setParent(&$parent, &$jsonParent=null)
|
||||
{
|
||||
parent::setParent($parent);
|
||||
$this->jsonParent = $jsonParent;
|
||||
}
|
||||
|
||||
public function fieldCount()
|
||||
{
|
||||
return count($this->arrayAccessData);
|
||||
}
|
||||
|
||||
public function setUserValue($userValue)
|
||||
{
|
||||
if (is_array($userValue)) {
|
||||
foreach ($userValue as $key => $value) {
|
||||
if ($value !== null) {
|
||||
$this[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setStorageValue($storageValue)
|
||||
{
|
||||
$this->setUserValue(json_decode($storageValue, true));
|
||||
}
|
||||
|
||||
public function getStorageValue()
|
||||
{
|
||||
return json_encode($this->toArray());
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return $this->getUserValue();
|
||||
}
|
||||
|
||||
public function getUserValue()
|
||||
{
|
||||
$array = array();
|
||||
foreach ($this as $key => $value) {
|
||||
if ($value instanceof JSON) {
|
||||
$value = $value->toArray();
|
||||
}
|
||||
$array[$key] = $value;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function buildIterMap()
|
||||
{
|
||||
$this->iteratorArrayMap = array();
|
||||
foreach ($this->arrayAccessData as $key => $value) {
|
||||
$this->iteratorArrayMap[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
//do nothing for null values
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
//set offset
|
||||
if (is_null($offset)) {
|
||||
$this->arrayAccessData[] = $value;
|
||||
$offset = count($this->arrayAccessData);
|
||||
}
|
||||
//array values convert to JSON
|
||||
if (is_array($value)) {
|
||||
$array = $value;
|
||||
$value = new JSON($this->getParent(), $this);
|
||||
$value->setUserValue($array);
|
||||
}
|
||||
//JSON values need a parent set
|
||||
if ($value instanceof JSON) {
|
||||
$value->setParent($this->getParent(), $this);
|
||||
}
|
||||
//set in arrayAccessData
|
||||
if (isset($this->arrayAccessData[$offset])) {
|
||||
if ($this->arrayAccessData[$offset] != $value) {
|
||||
$this->changed();
|
||||
}
|
||||
}
|
||||
$this->arrayAccessData[$offset] = $value;
|
||||
$this->buildIterMap();
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->arrayAccessData[$offset]);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->arrayAccessData[$offset]);
|
||||
$this->buildIterMap();
|
||||
}
|
||||
|
||||
protected function &getRef($offset)
|
||||
{
|
||||
return $this->arrayAccessData[$offset];
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (isset($this->arrayAccessData[$offset])) {
|
||||
return $this->getRef($offset);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->iterPos = 0;
|
||||
}
|
||||
|
||||
public function ¤t()
|
||||
{
|
||||
if (isset($this->arrayAccessData[$this->key()])) {
|
||||
return $this->arrayAccessData[$this->key()];
|
||||
}
|
||||
$return = null;
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return isset($this->iteratorArrayMap[$this->iterPos]) ? $this->iteratorArrayMap[$this->iterPos] : null;
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
$this->iterPos++;
|
||||
}
|
||||
|
||||
public function valid()
|
||||
{
|
||||
return isset($this->arrayAccessData[$this->key()]);
|
||||
}
|
||||
}
|
23
src/DataTransformers/SimpleTransforms.php
Normal file
23
src/DataTransformers/SimpleTransforms.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\DataTransformers;
|
||||
|
||||
class SimpleTransforms implements DataTransformerInterface
|
||||
{
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr;
|
||||
|
||||
class DriverFactory
|
||||
{
|
||||
public static $map = [
|
||||
'mysql' => Drivers\MySQLDriver::class,
|
||||
'sqlite' => LegacyDrivers\SQLiteDriver::class
|
||||
];
|
||||
|
||||
public static function factory(string $dsn, string $username=null, string $password=null, array $options=null, string $type = null) : ?Drivers\DSODriverInterface
|
||||
{
|
||||
if (!$type) {
|
||||
$type = @array_shift(explode(':', $dsn, 2));
|
||||
}
|
||||
$type = strtolower($type);
|
||||
if ($class = @static::$map[$type]) {
|
||||
return new $class($dsn, $username, $password, $options);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr\Drivers;
|
||||
|
||||
use Destructr\DSOInterface;
|
||||
use Destructr\Search;
|
||||
|
||||
//TODO: Caching? It should happen somewhere in this class I think.
|
||||
abstract class AbstractDriver implements DSODriverInterface
|
||||
{
|
||||
public $lastPreparationErrorOn;
|
||||
public $pdo;
|
||||
const EXTENSIBLE_VIRTUAL_COLUMNS = true;
|
||||
|
||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null)
|
||||
{
|
||||
if (!$this->pdo = new \PDO($dsn, $username, $password, $options)) {
|
||||
throw new \Exception("Error creating PDO connection");
|
||||
}
|
||||
}
|
||||
|
||||
protected function expandPaths($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
$value = preg_replace_callback(
|
||||
'/\$\{([^\}\\\]+)\}/',
|
||||
function ($matches) {
|
||||
return $this->expandPath($matches[1]);
|
||||
},
|
||||
$value
|
||||
);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function errorInfo()
|
||||
{
|
||||
return $this->pdo->errorInfo();
|
||||
}
|
||||
|
||||
public function createTable(string $table, array $virtualColumns) : bool
|
||||
{
|
||||
$sql = $this->sql_ddl([
|
||||
'table'=>$table,
|
||||
'virtualColumns'=>$virtualColumns
|
||||
]);
|
||||
return $this->pdo->exec($sql) !== false;
|
||||
}
|
||||
|
||||
public function update(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
if (!$dso->changes() && !$dso->removals()) {
|
||||
return true;
|
||||
}
|
||||
$s = $this->getStatement(
|
||||
'setJSON',
|
||||
['table'=>$table]
|
||||
);
|
||||
return $s->execute([
|
||||
':dso_id' => $dso['dso.id'],
|
||||
':data' => json_encode($dso->get())
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
$s = $this->getStatement(
|
||||
'delete',
|
||||
['table'=>$table]
|
||||
);
|
||||
return $s->execute([
|
||||
':dso_id' => $dso['dso.id']
|
||||
]);
|
||||
}
|
||||
|
||||
public function select(string $table, Search $search, array $params)
|
||||
{
|
||||
$s = $this->getStatement(
|
||||
'select',
|
||||
['table'=>$table,'search'=>$search]
|
||||
);
|
||||
if (!$s->execute($params)) {
|
||||
return [];
|
||||
}
|
||||
return @$s->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function insert(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
return $this->getStatement(
|
||||
'insert',
|
||||
['table'=>$table]
|
||||
)->execute(
|
||||
[':data'=>json_encode($dso->get())]
|
||||
);
|
||||
}
|
||||
|
||||
protected function getStatement(string $type, $args=array()) : \PDOStatement
|
||||
{
|
||||
$fn = 'sql_'.$type;
|
||||
if (!method_exists($this, $fn)) {
|
||||
throw new \Exception("Error getting SQL statement, driver doesn't have a method named $fn");
|
||||
}
|
||||
$sql = $this->$fn($args);
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
if (!$stmt) {
|
||||
$this->lastPreparationErrorOn = $sql;
|
||||
throw new \Exception("Error preparing statement: ".implode(': ', $this->pdo->errorInfo()), 1);
|
||||
}
|
||||
return $stmt;
|
||||
//TODO: turn this on someday and see if caching statements helps in the real world
|
||||
// $sql = $this->$fn($args);
|
||||
// $id = md5($sql);
|
||||
// if (!isset($this->prepared[$id])) {
|
||||
// $this->prepared[$id] = $this->pdo->prepare($sql);
|
||||
// }
|
||||
// return @$this->prepared[$id];
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr\Drivers;
|
||||
|
||||
use Destructr\DSOInterface;
|
||||
use Destructr\Search;
|
||||
|
||||
interface DSODriverInterface
|
||||
{
|
||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null);
|
||||
|
||||
public function createTable(string $table, array $virtualColumns) : bool;
|
||||
public function select(string $table, Search $search, array $params);
|
||||
public function insert(string $table, DSOInterface $dso) : bool;
|
||||
public function update(string $table, DSOInterface $dso) : bool;
|
||||
public function delete(string $table, DSOInterface $dso) : bool;
|
||||
|
||||
public function errorInfo();
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr\Drivers;
|
||||
|
||||
/**
|
||||
* What this driver supports: MySQL and MariaDB databases new enough to support
|
||||
* JSON functions. This means:
|
||||
* * MySQL >= 5.7
|
||||
* * MariaDB >= 10.2
|
||||
*/
|
||||
class MySQLDriver extends AbstractDriver
|
||||
{
|
||||
/**
|
||||
* Within the search we expand strings like ${dso.id} into JSON queries.
|
||||
* Note that the Search will have already had these strings expanded into
|
||||
* column names if there are virtual columns configured for them. That
|
||||
* happens in the Factory before it gets here.
|
||||
*/
|
||||
protected function sql_select($args)
|
||||
{
|
||||
//extract query parts from Search and expand paths
|
||||
$where = $this->expandPaths($args['search']->where());
|
||||
$order = $this->expandPaths($args['search']->order());
|
||||
$limit = $args['search']->limit();
|
||||
$offset = $args['search']->offset();
|
||||
//select from
|
||||
$out = ["SELECT * FROM `{$args['table']}`"];
|
||||
//where statement
|
||||
if ($where !== null) {
|
||||
$out[] = "WHERE ".$where;
|
||||
}
|
||||
//order statement
|
||||
if ($order !== null) {
|
||||
$out[] = "ORDER BY ".$order;
|
||||
}
|
||||
//limit
|
||||
if ($limit !== null) {
|
||||
$out[] = "LIMIT ".$limit;
|
||||
}
|
||||
//offset
|
||||
if ($offset !== null) {
|
||||
$out[] = "OFFSET ".$offset;
|
||||
}
|
||||
//return
|
||||
return implode(PHP_EOL, $out).';';
|
||||
}
|
||||
|
||||
protected function sql_ddl($args=array())
|
||||
{
|
||||
$out = [];
|
||||
$out[] = "CREATE TABLE `{$args['table']}` (";
|
||||
$lines = [];
|
||||
$lines[] = "`json_data` JSON DEFAULT NULL";
|
||||
foreach ($args['virtualColumns'] as $path => $col) {
|
||||
$line = "`{$col['name']}` {$col['type']} GENERATED ALWAYS AS (".$this->expandPath($path).")";
|
||||
if (@$col['primary']) {
|
||||
//this needs to be "PERSISTENT" for MariaDB -- I guess there are going to be two drivers now
|
||||
$line .= ' STORED';
|
||||
} else {
|
||||
$line .= ' VIRTUAL';
|
||||
}
|
||||
$lines[] = $line;
|
||||
}
|
||||
foreach ($args['virtualColumns'] as $path => $col) {
|
||||
if (@$col['primary']) {
|
||||
$lines[] = "PRIMARY KEY (`{$col['name']}`)";
|
||||
} elseif (@$col['unique'] && $as = @$col['index']) {
|
||||
$lines[] = "UNIQUE KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
||||
} elseif ($as = @$col['index']) {
|
||||
$lines[] = "KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
||||
}
|
||||
}
|
||||
$out[] = implode(','.PHP_EOL, $lines);
|
||||
$out[] = ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
||||
return implode(PHP_EOL, $out);
|
||||
}
|
||||
|
||||
protected function expandPath(string $path) : string
|
||||
{
|
||||
return "JSON_UNQUOTE(JSON_EXTRACT(`json_data`,'$.{$path}'))";
|
||||
}
|
||||
|
||||
protected function sql_setJSON($args)
|
||||
{
|
||||
return 'UPDATE `'.$args['table'].'` SET `json_data` = :data WHERE `dso_id` = :dso_id;';
|
||||
}
|
||||
|
||||
protected function sql_insert($args)
|
||||
{
|
||||
return "INSERT INTO `{$args['table']}` (`json_data`) VALUES (:data);";
|
||||
}
|
||||
|
||||
protected function sql_delete($args)
|
||||
{
|
||||
return 'DELETE FROM `'.$args['table'].'` WHERE `dso_id` = :dso_id;';
|
||||
}
|
||||
}
|
24
src/Exceptions/Exception.php
Normal file
24
src/Exceptions/Exception.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Exceptions;
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
|
||||
|
||||
}
|
26
src/Exceptions/IDExistsException.php
Normal file
26
src/Exceptions/IDExistsException.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Exceptions;
|
||||
|
||||
class IDExistsException extends Exception
|
||||
{
|
||||
public function __construct($id)
|
||||
{
|
||||
parent::__construct("DataObject ID $id already exists -- create() may have been called twice", 1);
|
||||
}
|
||||
}
|
26
src/Exceptions/IDSpaceException.php
Normal file
26
src/Exceptions/IDSpaceException.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Exceptions;
|
||||
|
||||
class IDSpaceException extends Exception
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct("Maximum ID generation attempts exceeded -- ID space may be exhausted", 1);
|
||||
}
|
||||
}
|
26
src/Exceptions/InvalidEnumException.php
Normal file
26
src/Exceptions/InvalidEnumException.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Exceptions;
|
||||
|
||||
class InvalidEnumException extends Exception
|
||||
{
|
||||
public function __construct($value)
|
||||
{
|
||||
parent::__construct("Value \"$value\" is not a valid enum value for this field", 1);
|
||||
}
|
||||
}
|
275
src/Factory.php
275
src/Factory.php
|
@ -1,275 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr;
|
||||
|
||||
use mofodojodino\ProfanityFilter\Check;
|
||||
|
||||
/**
|
||||
* The Factory is responsible for keeping track of which columns may or may not
|
||||
* be configured as virtual columns (although in the future for NoSQL databases
|
||||
* this may not be relevant).
|
||||
*
|
||||
* The overall responsibilities of the Factory are:
|
||||
* * Tracking which table is to be used
|
||||
* * Holding the driver to be used
|
||||
* * Creating DSOs and passing itself to them
|
||||
* * Calling its own and the DSO's hook_create() and hook_update() methods
|
||||
* * Passing DSOs that need CRUD-ing to the appropriate Driver methods
|
||||
* * Creating Search objects
|
||||
* * Executing Searches (which largely consists of passing them to the Driver)
|
||||
* * Inspecting unstructured data straight from the database and figuring out what class to make it (defaults to just DSO)
|
||||
*/
|
||||
class Factory implements DSOFactoryInterface
|
||||
{
|
||||
const ID_CHARS = 'abcdefghijkmnorstuvwxyz0123456789';
|
||||
const ID_LENGTH = 16;
|
||||
|
||||
protected $driver;
|
||||
protected $table;
|
||||
/**
|
||||
* Virtual columns are only supported by modern SQL servers. Most of the
|
||||
* legacy drivers will only use the ones defined in CORE_VIRTUAL_COLUMNS,
|
||||
* but that should be handled automatically.
|
||||
*/
|
||||
protected $virtualColumns = [
|
||||
'dso.id' => [
|
||||
'name'=>'dso_id',
|
||||
'type'=>'VARCHAR(16)',
|
||||
'index' => 'BTREE',
|
||||
'unique' => true,
|
||||
'primary' => true
|
||||
],
|
||||
'dso.type' => [
|
||||
'name'=>'dso_type',
|
||||
'type'=>'VARCHAR(30)',
|
||||
'index'=>'BTREE'
|
||||
],
|
||||
'dso.deleted' => [
|
||||
'name'=>'dso_deleted',
|
||||
'type'=>'BIGINT',
|
||||
'index'=>'BTREE'
|
||||
]
|
||||
];
|
||||
/**
|
||||
* This cannot be modified by extending classes, it's used by legacy drivers
|
||||
*/
|
||||
const CORE_VIRTUAL_COLUMNS = [
|
||||
'dso.id' => [
|
||||
'name'=>'dso_id',
|
||||
'type'=>'VARCHAR(16)',
|
||||
'index' => 'BTREE',
|
||||
'unique' => true,
|
||||
'primary' => true
|
||||
],
|
||||
'dso.type' => [
|
||||
'name'=>'dso_type',
|
||||
'type'=>'VARCHAR(30)',
|
||||
'index'=>'BTREE'
|
||||
],
|
||||
'dso.deleted' => [
|
||||
'name'=>'dso_deleted',
|
||||
'type'=>'BIGINT',
|
||||
'index'=>'BTREE'
|
||||
]
|
||||
];
|
||||
|
||||
public function __construct(Drivers\DSODriverInterface &$driver, string $table)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
protected function hook_create(DSOInterface &$dso)
|
||||
{
|
||||
if (!$dso->get('dso.id')) {
|
||||
$dso->set('dso.id', static::generate_id(static::ID_CHARS, static::ID_LENGTH), true);
|
||||
}
|
||||
if (!$dso->get('dso.created.date')) {
|
||||
$dso->set('dso.created.date', time());
|
||||
}
|
||||
if (!$dso->get('dso.created.user')) {
|
||||
$dso->set('dso.created.user', ['ip'=>@$_SERVER['REMOTE_ADDR']]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function hook_update(DSOInterface &$dso)
|
||||
{
|
||||
$dso->set('dso.modified.date', time());
|
||||
$dso->set('dso.modified.user', ['ip'=>@$_SERVER['REMOTE_ADDR']]);
|
||||
}
|
||||
|
||||
public function class(array $data) : ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function delete(DSOInterface &$dso, bool $permanent = false) : bool
|
||||
{
|
||||
if ($permanent) {
|
||||
return $this->driver->delete($this->table, $dso);
|
||||
}
|
||||
$dso['dso.deleted'] = time();
|
||||
return $this->update($dso);
|
||||
}
|
||||
|
||||
public function undelete(DSOInterface &$dso) : bool
|
||||
{
|
||||
unset($dso['dso.deleted']);
|
||||
return $this->update($dso);
|
||||
}
|
||||
|
||||
public function create(array $data = array()) : DSOInterface
|
||||
{
|
||||
if (!($class = $this->class($data))) {
|
||||
$class = DSO::class;
|
||||
}
|
||||
$dso = new $class($data, $this);
|
||||
$this->hook_create($dso);
|
||||
$dso->hook_create();
|
||||
$dso->resetChanges();
|
||||
return $dso;
|
||||
}
|
||||
|
||||
public function createTable() : bool
|
||||
{
|
||||
return $this->driver->createTable(
|
||||
$this->table,
|
||||
($this->driver::EXTENSIBLE_VIRTUAL_COLUMNS?$this->virtualColumns:$this::CORE_VIRTUAL_COLUMNS)
|
||||
);
|
||||
}
|
||||
|
||||
protected function virtualColumnName($path) : ?string
|
||||
{
|
||||
if ($this->driver::EXTENSIBLE_VIRTUAL_COLUMNS) {
|
||||
$vcols = $this->virtualColumns;
|
||||
} else {
|
||||
$vcols = static::CORE_VIRTUAL_COLUMNS;
|
||||
}
|
||||
return @$vcols[$path]['name'];
|
||||
}
|
||||
|
||||
public function update(DSOInterface &$dso) : bool
|
||||
{
|
||||
if (!$dso->changes() && !$dso->removals()) {
|
||||
return true;
|
||||
}
|
||||
$this->hook_update($dso);
|
||||
$dso->hook_update();
|
||||
$out = $this->driver->update($this->table, $dso);
|
||||
$dso->resetChanges();
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function search() : Search
|
||||
{
|
||||
return new Search($this);
|
||||
}
|
||||
|
||||
protected function makeObjectFromRow($arr)
|
||||
{
|
||||
$data = json_decode($arr['json_data'], true);
|
||||
return $this->create($data);
|
||||
}
|
||||
|
||||
protected function makeObjectsFromRows($arr)
|
||||
{
|
||||
foreach ($arr as $key => $value) {
|
||||
$arr[$key] = $this->makeObjectFromRow($value);
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
public function executeSearch(Search $search, array $params = array(), $deleted = false) : array
|
||||
{
|
||||
//add deletion clause and expand column names
|
||||
$search = $this->preprocessSearch($search, $deleted);
|
||||
//run select
|
||||
$r = $this->driver->select(
|
||||
$this->table,
|
||||
$search,
|
||||
$params
|
||||
);
|
||||
return $this->makeObjectsFromRows($r);
|
||||
}
|
||||
|
||||
public function read(string $value, string $field = 'dso.id', $deleted = false) : ?DSOInterface
|
||||
{
|
||||
$search = $this->search();
|
||||
$search->where('${'.$field.'} = :value');
|
||||
if ($results = $search->execute([':value'=>$value], $deleted)) {
|
||||
return array_shift($results);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function insert(DSOInterface &$dso) : bool
|
||||
{
|
||||
$this->hook_update($dso);
|
||||
$dso->hook_update();
|
||||
$dso->resetChanges();
|
||||
return $this->driver->insert($this->table, $dso);
|
||||
}
|
||||
|
||||
protected function preprocessSearch($input, $deleted)
|
||||
{
|
||||
//clone search so we're not accidentally messing with a reference
|
||||
$search = new Search($this);
|
||||
$search->where($input->where());
|
||||
$search->order($input->order());
|
||||
/* add deletion awareness to where clause */
|
||||
if ($deleted !== null) {
|
||||
$where = $search->where();
|
||||
if ($deleted === true) {
|
||||
$added = '${dso.deleted} is not null';
|
||||
} else {
|
||||
$added = '${dso.deleted} is null';
|
||||
}
|
||||
if ($where) {
|
||||
$where = '('.$where.') AND '.$added;
|
||||
} else {
|
||||
$where = $added;
|
||||
}
|
||||
$search->where($where);
|
||||
}
|
||||
/* expand virtual column names */
|
||||
foreach (['where','order'] as $clause) {
|
||||
if ($value = $search->$clause()) {
|
||||
$value = preg_replace_callback(
|
||||
'/\$\{([^\}\\\]+)\}/',
|
||||
function ($matches) {
|
||||
/* depends on whether a virtual column is expected for this value */
|
||||
if ($vcol = $this->virtualColumnName($matches[1])) {
|
||||
return "`$vcol`";
|
||||
}
|
||||
return $matches[0];
|
||||
},
|
||||
$value
|
||||
);
|
||||
$search->$clause($value);
|
||||
}
|
||||
}
|
||||
/* return search */
|
||||
return $search;
|
||||
}
|
||||
|
||||
protected static function generate_id($chars, $length) : string
|
||||
{
|
||||
$check = new Check();
|
||||
do {
|
||||
$id = '';
|
||||
while (strlen($id) < $length) {
|
||||
$id .= substr(
|
||||
$chars,
|
||||
rand(0, strlen($chars)-1),
|
||||
1
|
||||
);
|
||||
}
|
||||
} while ($check->hasProfanity($id));
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function errorInfo()
|
||||
{
|
||||
return $this->driver->errorInfo();
|
||||
}
|
||||
}
|
156
src/Files/FilesContainer.php
Normal file
156
src/Files/FilesContainer.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Files;
|
||||
|
||||
use \Digraph\DataObject\Files\SingleFile;
|
||||
|
||||
class FilesContainer implements FilesContainerInterface, \ArrayAccess, \Iterator
|
||||
{
|
||||
protected $files = array();
|
||||
protected $iterMap = array();
|
||||
protected $iterPos = 0;
|
||||
|
||||
protected $_obj = null;
|
||||
|
||||
public function __construct($obj, $storageString)
|
||||
{
|
||||
$this->setParent($obj);
|
||||
$files = @json_decode($storageString, true);
|
||||
if (!is_array($files)) {
|
||||
$files = array();
|
||||
}
|
||||
foreach ($files as $key => $value) {
|
||||
$this->addFile($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function setParent(&$obj)
|
||||
{
|
||||
$this->_obj = $obj;
|
||||
}
|
||||
|
||||
public function getStashFolder()
|
||||
{
|
||||
return $this->_obj->getStashFolder();
|
||||
}
|
||||
|
||||
public function getStorageFolder()
|
||||
{
|
||||
return $this->_obj->getStorageFolder();
|
||||
}
|
||||
|
||||
public function addFile($id, $info)
|
||||
{
|
||||
$this[$id] = new SingleFile($this, $info);
|
||||
}
|
||||
|
||||
public function deleteFile($id)
|
||||
{
|
||||
unlink($this->files[$id]->fullPath());
|
||||
unset($this->files[$id]);
|
||||
$this->buildIterMap();
|
||||
}
|
||||
|
||||
public function getStorageString()
|
||||
{
|
||||
$out = array();
|
||||
foreach ($this as $name => $file) {
|
||||
$out[$name] = $file->getProperties();
|
||||
}
|
||||
return json_encode($out);
|
||||
}
|
||||
|
||||
public function generateTimestampNow()
|
||||
{
|
||||
return $this->_obj->generateTimestampNow('FilesContainer', array());
|
||||
}
|
||||
public function generateCurrentUser()
|
||||
{
|
||||
return $this->_obj->generateCurrentUser('FilesContainer', array());
|
||||
}
|
||||
|
||||
public function fieldCount()
|
||||
{
|
||||
return count($this->files);
|
||||
}
|
||||
|
||||
protected function buildIterMap()
|
||||
{
|
||||
$this->iterMap = array();
|
||||
foreach ($this->files as $key => $value) {
|
||||
$this->iterMap[] = $key;
|
||||
}
|
||||
}
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
$this->files[] = $value;
|
||||
$offset = count($this->files);
|
||||
} else {
|
||||
$this->files[$offset] = $value;
|
||||
}
|
||||
$value->setParent($this);
|
||||
$this->buildIterMap();
|
||||
}
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->files[$offset]);
|
||||
}
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
$this->deleteFile($offset);
|
||||
}
|
||||
|
||||
protected function &getRef($offset)
|
||||
{
|
||||
return $this->files[$offset];
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (isset($this->files[$offset])) {
|
||||
return $this->getRef($offset);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->iterPos = 0;
|
||||
}
|
||||
public function ¤t()
|
||||
{
|
||||
if (isset($this->files[$this->key()])) {
|
||||
return $this->files[$this->key()];
|
||||
}
|
||||
$return = null;
|
||||
return $return;
|
||||
}
|
||||
public function key()
|
||||
{
|
||||
return isset($this->iterMap[$this->iterPos]) ? $this->iterMap[$this->iterPos] : null;
|
||||
}
|
||||
public function next()
|
||||
{
|
||||
$this->iterPos++;
|
||||
}
|
||||
public function valid()
|
||||
{
|
||||
return isset($this->files[$this->key()]);
|
||||
}
|
||||
}
|
29
src/Files/FilesContainerInterface.php
Normal file
29
src/Files/FilesContainerInterface.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Files;
|
||||
|
||||
use \Digraph\DataObject\DataByReferenceInterface;
|
||||
|
||||
interface FilesContainerInterface extends DataByReferenceInterface
|
||||
{
|
||||
public function getStashFolder();
|
||||
public function getStorageFolder();
|
||||
|
||||
public function addFile($id, $infoArray);
|
||||
public function deleteFile($id);
|
||||
}
|
214
src/Files/SingleFile.php
Normal file
214
src/Files/SingleFile.php
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Files;
|
||||
|
||||
class SingleFile implements SingleFileInterface, \ArrayAccess, \Iterator
|
||||
{
|
||||
protected $properties = array(
|
||||
'name' => 'empty',
|
||||
'ext' => 'txt',
|
||||
'type' => 'text/plain',
|
||||
'size' => 0,
|
||||
'ctime' => null,
|
||||
'cuser' => null,
|
||||
'mtime' => null,
|
||||
'muser' => null,
|
||||
'store_name' => null
|
||||
);
|
||||
protected $iterMap = array();
|
||||
protected $iterPos = 0;
|
||||
protected $writeProtect = array(
|
||||
'ctime','cuser','mtime','muser','size','type','ext'
|
||||
);
|
||||
|
||||
protected $_container = false;
|
||||
|
||||
public function __construct($container, $info)
|
||||
{
|
||||
$this->setParent($container);
|
||||
if (!isset($info['ctime'])) {
|
||||
$info['ctime'] = $this->generateTimestampNow();
|
||||
$info['cuser'] = $this->generateCurrentUser();
|
||||
}
|
||||
if (!isset($info['mtime'])) {
|
||||
$info['mtime'] = $this->generateTimestampNow();
|
||||
$info['muser'] = $this->generateCurrentUser();
|
||||
}
|
||||
foreach ($this->properties as $key => $value) {
|
||||
$this->properties[$key] = isset($info[$key])?$info[$key]:null;
|
||||
}
|
||||
if (isset($info['tmp_name'])) {
|
||||
$this->properties['tmp_name'] = $info['tmp_name'];
|
||||
}
|
||||
if (isset($info['stash_name'])) {
|
||||
$this->properties['stash_name'] = $info['stash_name'];
|
||||
}
|
||||
}
|
||||
|
||||
public function getProperties()
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
public function fullPath()
|
||||
{
|
||||
if (isset($this['store_name'])) {
|
||||
return $this->_container->getStorageFolder() . '/' . $this['store_name'];
|
||||
}
|
||||
if (isset($this['stash_name'])) {
|
||||
return $this['stash_name'];
|
||||
}
|
||||
if (isset($this['tmp_name'])) {
|
||||
return $this['tmp_name'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function store($skipCheck = false)
|
||||
{
|
||||
$storeFolder = $this->_container->getStorageFolder();
|
||||
if (isset($this['tmp_name'])) {
|
||||
$result = $this->stash($skipCheck);
|
||||
if ($result == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isset($this['stash_name'])) {
|
||||
$storageName = strtolower(preg_replace('/[^a-z0-9]/i', '', $this['name'])).'_'.md5(file_get_contents($this['stash_name'])).'.'.$this['ext'];
|
||||
$moved = rename($this['stash_name'], $storeFolder . '/' . $storageName);
|
||||
if ($moved) {
|
||||
unset($this['stash_name']);
|
||||
$this['store_name'] = $storageName;
|
||||
}
|
||||
return $moved;
|
||||
}
|
||||
//return true because nothing needed doing
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stash($skipCheck = false)
|
||||
{
|
||||
$stashFolder = $this->_container->getStashFolder();
|
||||
if (isset($this['tmp_name'])) {
|
||||
$stashFile = $stashFolder . '/' . md5(rand());
|
||||
if ($skipCheck) {
|
||||
$moved = rename($this['tmp_name'], $stashFile);
|
||||
} else {
|
||||
$moved = move_uploaded_file($this['tmp_name'], $stashFile);
|
||||
}
|
||||
if ($moved) {
|
||||
unset($this['tmp_name']);
|
||||
$this['stash_name'] = $stashFile;
|
||||
}
|
||||
return $moved;
|
||||
}
|
||||
//return true becuase nothing needed doing
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setParent($container)
|
||||
{
|
||||
$this->_container = $container;
|
||||
}
|
||||
|
||||
public function updateModified()
|
||||
{
|
||||
$this->properties['mtime'] = $this->generateTimestampNow();
|
||||
$this->properties['muser'] = $this->generateCurrentUser();
|
||||
}
|
||||
|
||||
public function generateTimestampNow()
|
||||
{
|
||||
return $this->_container->generateTimestampNow();
|
||||
}
|
||||
|
||||
public function generateCurrentUser()
|
||||
{
|
||||
return $this->_container->generateCurrentUser();
|
||||
}
|
||||
|
||||
public function fieldCount()
|
||||
{
|
||||
return count($this->properties);
|
||||
}
|
||||
|
||||
protected function buildIterMap()
|
||||
{
|
||||
$this->iterMap = array();
|
||||
foreach ($this->properties as $key => $value) {
|
||||
$this->iterMap[] = $key;
|
||||
}
|
||||
}
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (in_array($offset, $this->writeProtect)) {
|
||||
trigger_error("Property \"$offset\" is write-protected", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
if (is_null($offset)) {
|
||||
$this->properties[] = $value;
|
||||
$offset = count($this->properties);
|
||||
} else {
|
||||
$this->properties[$offset] = $value;
|
||||
}
|
||||
$this->updateModified();
|
||||
$this->buildIterMap();
|
||||
}
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->properties[$offset]);
|
||||
}
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->properties[$offset]);
|
||||
$this->updateModified();
|
||||
$this->buildIterMap();
|
||||
}
|
||||
public function &offsetGet($offset)
|
||||
{
|
||||
if (isset($this->properties[$offset])) {
|
||||
return $this->properties[$offset];
|
||||
}
|
||||
$ref = null;
|
||||
return $ref;
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->iterPos = 0;
|
||||
}
|
||||
public function ¤t()
|
||||
{
|
||||
if (isset($this->properties[$this->key()])) {
|
||||
return $this->properties[$this->key()];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function key()
|
||||
{
|
||||
return isset($this->iterMap[$this->iterPos]) ? $this->iterMap[$this->iterPos] : null;
|
||||
}
|
||||
public function next()
|
||||
{
|
||||
$this->iterPos++;
|
||||
}
|
||||
public function valid()
|
||||
{
|
||||
return isset($this->properties[$this->key()]);
|
||||
}
|
||||
}
|
29
src/Files/SingleFileInterface.php
Normal file
29
src/Files/SingleFileInterface.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Files;
|
||||
|
||||
interface SingleFileInterface
|
||||
{
|
||||
public function __construct($container, $properties);
|
||||
public function setParent($container);
|
||||
public function generateTimestampNow();
|
||||
public function generateCurrentUser();
|
||||
public function store($skipCheck = false);
|
||||
public function stash($skipCheck = false);
|
||||
public function getProperties();
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr\LegacyDrivers;
|
||||
|
||||
use Destructr\Drivers\AbstractDriver;
|
||||
use Destructr\DSOInterface;
|
||||
use Destructr\Factory;
|
||||
use Destructr\Search;
|
||||
use Flatrr\FlatArray;
|
||||
|
||||
/**
|
||||
* This driver is for supporting older SQL servers that don't have their own
|
||||
* JSON functions. It uses a highly suspect alternative JSON serialization and
|
||||
* user-defined function.
|
||||
*
|
||||
* There are also DEFINITELY bugs in legacy drivers. They should only be used
|
||||
* for very simple queries. These are probably also bugs that cannot be fixed.
|
||||
* Legacy drivers shouldn't really be considered "supported" per se.
|
||||
*/
|
||||
class AbstractLegacyDriver extends AbstractDriver
|
||||
{
|
||||
const EXTENSIBLE_VIRTUAL_COLUMNS = false;
|
||||
|
||||
public function select(string $table, Search $search, array $params)
|
||||
{
|
||||
$results = parent::select($table, $search, $params);
|
||||
foreach ($results as $rkey => $row) {
|
||||
$new = new FlatArray();
|
||||
foreach (json_decode($row['json_data'], true) as $key => $value) {
|
||||
$new->set(str_replace('|', '.', $key), $value);
|
||||
}
|
||||
$results[$rkey]['json_data'] = json_encode($new->get());
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function sql_select($args)
|
||||
{
|
||||
//extract query parts from Search and expand paths
|
||||
$where = $this->expandPaths($args['search']->where());
|
||||
$order = $this->expandPaths($args['search']->order());
|
||||
$limit = $args['search']->limit();
|
||||
$offset = $args['search']->offset();
|
||||
//select from
|
||||
$out = ["SELECT * FROM `{$args['table']}`"];
|
||||
//where statement
|
||||
if ($where !== null) {
|
||||
$out[] = "WHERE ".$where;
|
||||
}
|
||||
//order statement
|
||||
if ($order !== null) {
|
||||
$out[] = "ORDER BY ".$order;
|
||||
}
|
||||
//limit
|
||||
if ($limit !== null) {
|
||||
$out[] = "LIMIT ".$limit;
|
||||
}
|
||||
//offset
|
||||
if ($offset !== null) {
|
||||
$out[] = "OFFSET ".$offset;
|
||||
}
|
||||
//return
|
||||
return implode(PHP_EOL, $out).';';
|
||||
}
|
||||
|
||||
protected function sql_ddl($args=array())
|
||||
{
|
||||
$out = [];
|
||||
$out[] = "CREATE TABLE `{$args['table']}` (";
|
||||
$lines = [];
|
||||
$lines[] = "`json_data` TEXT DEFAULT NULL";
|
||||
foreach ($args['virtualColumns'] as $path => $col) {
|
||||
$lines[] = "`{$col['name']}` {$col['type']}";
|
||||
}
|
||||
foreach ($args['virtualColumns'] as $path => $col) {
|
||||
if (@$col['unique'] && $as = @$col['index']) {
|
||||
$lines[] = "UNIQUE KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
||||
} elseif ($as = @$col['index']) {
|
||||
$lines[] = "KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
||||
}
|
||||
}
|
||||
$out[] = implode(','.PHP_EOL, $lines);
|
||||
$out[] = ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
||||
return implode(PHP_EOL, $out);
|
||||
}
|
||||
|
||||
public function update(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
if (!$dso->changes() && !$dso->removals()) {
|
||||
return true;
|
||||
}
|
||||
$s = $this->getStatement(
|
||||
'setJSON',
|
||||
['table'=>$table]
|
||||
);
|
||||
$params = $this->legacyParams($dso);
|
||||
$out = $s->execute($params);
|
||||
return $out;
|
||||
}
|
||||
|
||||
protected function sql_setJSON($args)
|
||||
{
|
||||
$out = [];
|
||||
$out[] = 'UPDATE `'.$args['table'].'`';
|
||||
$out[] = 'SET';
|
||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $v) {
|
||||
$out[] = '`'.$v['name'].'` = :'.$v['name'].',';
|
||||
}
|
||||
$out[] = '`json_data` = :data';
|
||||
$out[] = 'WHERE `dso_id` = :dso_id';
|
||||
return implode(PHP_EOL, $out).';';
|
||||
}
|
||||
|
||||
public function insert(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
$s = $this->getStatement(
|
||||
'insert',
|
||||
['table'=>$table]
|
||||
);
|
||||
$params = $this->legacyParams($dso);
|
||||
return $s->execute($params);
|
||||
}
|
||||
|
||||
protected function legacyParams(DSOInterface $dso)
|
||||
{
|
||||
$params = [':data' => $this->json_encode($dso->get())];
|
||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $vk => $vv) {
|
||||
$params[':'.$vv['name']] = $dso->get($vk);
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
protected function sql_insert($args)
|
||||
{
|
||||
$out = [];
|
||||
$out[] = 'INSERT INTO `'.$args['table'].'`';
|
||||
$out[] = '(`json_data`,`dso_id`,`dso_type`,`dso_deleted`)';
|
||||
$out[] = 'VALUES (:data, :dso_id, :dso_type, :dso_deleted)';
|
||||
return implode(PHP_EOL, $out).';';
|
||||
}
|
||||
|
||||
public function delete(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
$s = $this->getStatement(
|
||||
'delete',
|
||||
['table'=>$table]
|
||||
);
|
||||
$out = $s->execute([
|
||||
':dso_id' => $dso['dso.id']
|
||||
]);
|
||||
// if (!$out) {
|
||||
// var_dump($s->errorInfo());
|
||||
// }
|
||||
return $out;
|
||||
}
|
||||
|
||||
protected function sql_delete($args)
|
||||
{
|
||||
return 'DELETE FROM `'.$args['table'].'` WHERE `dso_id` = :dso_id;';
|
||||
}
|
||||
|
||||
public function json_encode($a, array &$b = null, string $prefix = '')
|
||||
{
|
||||
if ($b === null) {
|
||||
$b = [];
|
||||
$this->json_encode($a, $b, '');
|
||||
return json_encode($b);
|
||||
} else {
|
||||
if (is_array($a)) {
|
||||
foreach ($a as $ak => $av) {
|
||||
if ($prefix == '') {
|
||||
$nprefix = $ak;
|
||||
} else {
|
||||
$nprefix = $prefix.'|'.$ak;
|
||||
}
|
||||
$this->json_encode($av, $b, $nprefix);
|
||||
}
|
||||
} else {
|
||||
$b[$prefix] = $a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
# Destructr legacy drivers
|
||||
|
||||
This folder holds what are called "legacy drivers."
|
||||
These drivers attempt to extend Destructr support to as many databases as possible,
|
||||
even in some cases at the expense of functionality.
|
||||
|
||||
All these drivers are designed to support the following features:
|
||||
* Inserting objects (and disallowing duplicates at the *SQL* level)
|
||||
* Updating existing objects
|
||||
* Deleting objects
|
||||
* Searching by exact text matches
|
||||
|
||||
The things many of this sort of driver will *not* ever support:
|
||||
* Performance optimization via virtual columns. Many of them will have their
|
||||
performance lightly optimized for common system queries, just by hard-coding
|
||||
virtual columns for `dso.id`, `dso.deleted`, and `dso.type`
|
||||
* Complex queries, like joins, REGEX, or LIKE queries.
|
||||
|
||||
## Current state of legacy drivers
|
||||
|
||||
### SQLite 3
|
||||
|
||||
**\Destructr\LegacyDrivers\SQLiteDriver**
|
||||
|
||||
**Overall support level: Highly functional, a touch slow**
|
||||
|
||||
Via LegacyDrivers\SQLiteDriver Destructr now has surprisingly good support for
|
||||
SQLite 3. It accomplishes this by using SQLite's user-defined functions feature
|
||||
to offload JSON parsing to PHP. This means all the JSON parsing is actually up
|
||||
to spec, storage doesn't need to be flattened like the other legacy drivers,
|
||||
and tables are dramatically more easily forward-compatible.
|
||||
|
||||
If you're considering using legacy MySQL, you should really seriously consider
|
||||
just using SQLite instead. In benchmarks with 1000 records SQLite's performance
|
||||
is actually BETTER than MySQL 5.6 for everything but insert/update operations.
|
||||
In some cases it appears to even be *significantly* faster while also having the
|
||||
distinct advantage of not using any goofy home-rolled JSON extraction funcitons.
|
||||
|
||||
So unless you have a good reason to use MySQL 5.6, you're probably best off
|
||||
using SQLite if you don't have access to a fully supported database version.
|
||||
|
||||
### MySQL 5.6
|
||||
|
||||
** No longer supported. You should really use SQLite if you don't have access to
|
||||
something better. Will most likely never be supported. **
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr\LegacyDrivers;
|
||||
|
||||
use Destructr\DSOInterface;
|
||||
use Destructr\Factory;
|
||||
|
||||
/**
|
||||
* What this driver supports: Version of SQLite3 in PHP environments that allow
|
||||
* pdo::sqliteCreateFunction
|
||||
*
|
||||
* Overally this driver is quite safe and reliable. Definitely the most safe of
|
||||
* all the legacy drivers. The performance isn't even that bad. It benchmarks
|
||||
* close to the same speed as MySQL 5.7, even. The benchmarks are only operating
|
||||
* on databases of 500 objects though, so ymmv.
|
||||
*/
|
||||
class SQLiteDriver extends AbstractLegacyDriver
|
||||
{
|
||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null)
|
||||
{
|
||||
parent::__construct($dsn, $username, $password, $options);
|
||||
/*
|
||||
What we're doing here is adding a custom function to SQLite so that it
|
||||
can extract JSON values. It's not fast, but it does let us use JSON
|
||||
fairly seamlessly.
|
||||
*/
|
||||
$this->pdo->sqliteCreateFunction(
|
||||
'DESTRUCTR_JSON_EXTRACT',
|
||||
'\\Destructr\\LegacyDrivers\\SQLiteDriver::JSON_EXTRACT',
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
public static function JSON_EXTRACT($json, $path)
|
||||
{
|
||||
$path = substr($path, 2);
|
||||
$path = explode('.', $path);
|
||||
$arr = json_decode($json, true);
|
||||
$out = &$arr;
|
||||
while ($key = array_shift($path)) {
|
||||
if (isset($out[$key])) {
|
||||
$out = &$out[$key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return @"$out";
|
||||
}
|
||||
|
||||
public function createTable(string $table, array $virtualColumns) : bool
|
||||
{
|
||||
$sql = $this->sql_ddl([
|
||||
'table'=>$table,
|
||||
]);
|
||||
$out = $this->pdo->exec($sql) !== false;
|
||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $key => $vcol) {
|
||||
$idxResult = true;
|
||||
if (@$vcol['unique']) {
|
||||
$idxResult = $this->pdo->exec('CREATE UNIQUE INDEX '.$table.'_'.$vcol['name'].'_idx on `'.$table.'`(`'.$vcol['name'].'`)') !== false;
|
||||
} elseif (@$vcol['index']) {
|
||||
$idxResult = $this->pdo->exec('CREATE INDEX '.$table.'_'.$vcol['name'].'_idx on `'.$table.'`(`'.$vcol['name'].'`)') !== false;
|
||||
}
|
||||
if (!$idxResult) {
|
||||
$out = false;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
protected function sql_ddl($args=array())
|
||||
{
|
||||
$out = [];
|
||||
$out[] = "CREATE TABLE `{$args['table']}` (";
|
||||
$lines = [];
|
||||
$lines[] = "`json_data` TEXT DEFAULT NULL";
|
||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $path => $col) {
|
||||
$line = "`{$col['name']}` {$col['type']}";
|
||||
if (@$col['primary']) {
|
||||
$line .= ' PRIMARY KEY';
|
||||
}
|
||||
$lines[] = $line;
|
||||
}
|
||||
$out[] = implode(','.PHP_EOL, $lines);
|
||||
$out[] = ");";
|
||||
return implode(PHP_EOL, $out);
|
||||
}
|
||||
|
||||
protected function expandPath(string $path) : string
|
||||
{
|
||||
return "DESTRUCTR_JSON_EXTRACT(`json_data`,'$.{$path}')";
|
||||
}
|
||||
|
||||
public function json_encode($a, ?array &$b = null, string $prefix = '')
|
||||
{
|
||||
return json_encode($a);
|
||||
}
|
||||
}
|
232
src/SQL/AbstractSQLDataObject.php
Normal file
232
src/SQL/AbstractSQLDataObject.php
Normal file
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\SQL;
|
||||
|
||||
use \Digraph\DataObject\Exceptions\IDExistsException;
|
||||
use \Digraph\DataObject\Exceptions\UnmappedFieldException;
|
||||
use \Digraph\DataObject\SQL\Exceptions\QueryException;
|
||||
|
||||
abstract class AbstractSQLDataObject extends \Digraph\DataObject\AbstractArrayDataObject implements SQLDataObjectInterface
|
||||
{
|
||||
protected static $_table;
|
||||
|
||||
protected static $_conn = null;
|
||||
protected static $_fpdo = null;
|
||||
|
||||
protected static function getConn()
|
||||
{
|
||||
if (static::$_conn === null) {
|
||||
static::$_conn = static::buildConn();
|
||||
}
|
||||
return static::$_conn;
|
||||
}
|
||||
|
||||
protected static function getFPDO()
|
||||
{
|
||||
if (static::$_fpdo === null) {
|
||||
static::$_fpdo = new \FluentPDO(static::getConn());
|
||||
}
|
||||
return static::$_fpdo;
|
||||
}
|
||||
|
||||
protected static function getInsert()
|
||||
{
|
||||
return static::getFPDO()->insertInto(static::$_table);
|
||||
}
|
||||
|
||||
public static function getSelect($deleted = false)
|
||||
{
|
||||
$query = static::getFPDO()->from(static::$_table);
|
||||
if (!$deleted) {
|
||||
$deletedCol = static::storageName('do_deleted');
|
||||
$query->where("$deletedCol is null");
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getUpdate()
|
||||
{
|
||||
return static::getFPDO()->update(static::$_table)->where(
|
||||
static::storageName('do_id'),
|
||||
$this->do_id
|
||||
);
|
||||
}
|
||||
|
||||
public static function runSelect($query)
|
||||
{
|
||||
$class = get_called_class();
|
||||
$results = array();
|
||||
foreach ($query as $row) {
|
||||
$data = array();
|
||||
foreach (static::map() as $key => $mapEntry) {
|
||||
$data[$key] = $row[$mapEntry['name']];
|
||||
}
|
||||
$results[] = new $class($data, true);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function create($dump = false)
|
||||
{
|
||||
if (static::idExists($this->do_id)) {
|
||||
throw new IDExistsException($this->do_id);
|
||||
}
|
||||
$values = array();
|
||||
foreach ($this->dataRaw as $name => $value) {
|
||||
$map = $this->mapEntry($name);
|
||||
$values[$map['name']] = $value;
|
||||
}
|
||||
$query = $this->getInsert();
|
||||
$query->values($values);
|
||||
if ($dump) {
|
||||
var_dump($query->getQuery());
|
||||
var_dump($values);
|
||||
}
|
||||
$result = $query->execute();
|
||||
if ($result === false) {
|
||||
throw new QueryException();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function read($id, $deleted = false)
|
||||
{
|
||||
$query = static::getSelect($deleted);
|
||||
$query->where(static::storageName('do_id'), $id);
|
||||
$results = static::runSelect($query);
|
||||
return array_pop($results);
|
||||
}
|
||||
|
||||
protected static function isValidOrder($order)
|
||||
{
|
||||
switch (strtolower($order)) {
|
||||
case 'asc':
|
||||
return true;
|
||||
case 'desc':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected static function colParameterSearch($str)
|
||||
{
|
||||
$class = get_called_class();
|
||||
$str = preg_replace_callback('/(:([a-zA-Z0-9_]+))/', function ($matches) use ($class) {
|
||||
$col = $class::storageName($matches[2]);
|
||||
if (!$col) {
|
||||
throw new UnmappedFieldException($matches[2]);
|
||||
}
|
||||
return $col;
|
||||
}, $str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function count($parameters = array())
|
||||
{
|
||||
$query = static::getSelect();
|
||||
//parse $parameters
|
||||
foreach ($parameters as $search => $value) {
|
||||
$search = static::colParameterSearch($search);
|
||||
if ($value === null) {
|
||||
$query->where($search);
|
||||
} else {
|
||||
$query->where($search, $value);
|
||||
}
|
||||
}
|
||||
return count($query);
|
||||
}
|
||||
|
||||
public static function search($parameters = array(), $sort = array(), $options = array())
|
||||
{
|
||||
$deleted = (isset($options['deleted']) && $options['deleted']);
|
||||
$query = static::getSelect($deleted);
|
||||
//parse $parameters
|
||||
foreach ($parameters as $search => $value) {
|
||||
$search = static::colParameterSearch($search);
|
||||
if ($value === null) {
|
||||
$query->where($search);
|
||||
} else {
|
||||
$query->where($search, $value);
|
||||
}
|
||||
}
|
||||
//parse $sort
|
||||
foreach ($sort as $name => $order) {
|
||||
if (($col = static::colParameterSearch($name)) && static::isValidOrder($order)) {
|
||||
$query->orderBy("$col $order");
|
||||
} else {
|
||||
throw new UnmappedFieldException($name);
|
||||
}
|
||||
}
|
||||
//limit
|
||||
if (isset($options['limit'])) {
|
||||
$query->limit($options['limit']);
|
||||
}
|
||||
//offset
|
||||
if (isset($options['offset'])) {
|
||||
$query->offset($options['offset']);
|
||||
}
|
||||
return static::runSelect($query);
|
||||
}
|
||||
|
||||
public function update($action = null, $dump = false)
|
||||
{
|
||||
// var_dump($this->dataChanged);
|
||||
// exit();
|
||||
if ($action !== null) {
|
||||
$action = array('action'=>$action);
|
||||
} else {
|
||||
$action = array();
|
||||
}
|
||||
if (!$this->dataChanged) {
|
||||
return null;
|
||||
}
|
||||
$this->set('do_mdate', strval(time()));
|
||||
$this->set('do_muser', $this->generateUser('do_muser'));
|
||||
$query = static::getUpdate();
|
||||
$values = array();
|
||||
foreach ($this->dataChanged as $key) {
|
||||
$values[static::storageName($key)] = $this->getRaw($key);
|
||||
}
|
||||
$query->set($values);
|
||||
if ($dump) {
|
||||
var_dump($query->getQuery());
|
||||
var_dump($values);
|
||||
}
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
public function delete($permanent = false, $dump = false)
|
||||
{
|
||||
if ($permanent) {
|
||||
$query = $this->getFPDO()->deleteFrom(static::$_table);
|
||||
$query->where(static::storageName('do_id'), $this->do_id);
|
||||
return $query->execute();
|
||||
}
|
||||
$this->set('do_deleted', time());
|
||||
return $this->update(null, $dump);
|
||||
}
|
||||
|
||||
public static function idExists($id)
|
||||
{
|
||||
if (static::read($id)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
24
src/SQL/Exceptions/Exception.php
Normal file
24
src/SQL/Exceptions/Exception.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\SQL\Exceptions;
|
||||
|
||||
class Exception extends \Digraph\DataObject\Exceptions\Exception
|
||||
{
|
||||
|
||||
|
||||
}
|
26
src/SQL/Exceptions/QueryException.php
Normal file
26
src/SQL/Exceptions/QueryException.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\SQL\Exceptions;
|
||||
|
||||
class QueryException extends Exception
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct("A query threw an exception. A good place to look for problems is the column names in map()", 1);
|
||||
}
|
||||
}
|
24
src/SQL/SQLDataObjectInterface.php
Normal file
24
src/SQL/SQLDataObjectInterface.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\SQL;
|
||||
|
||||
interface SQLDataObjectInterface extends \Digraph\DataObject\DataObjectInterface
|
||||
{
|
||||
public static function runSelect($query);
|
||||
public static function getSelect($deleted = false);
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr;
|
||||
|
||||
use Destructr\DSOFactoryInterface;
|
||||
use Destructr\Drivers\DSODriverInterface;
|
||||
|
||||
class Search implements \Serializable
|
||||
{
|
||||
protected $factory;
|
||||
protected $where;
|
||||
protected $order;
|
||||
protected $limit;
|
||||
protected $offset;
|
||||
|
||||
public function __construct(DSOFactoryInterface &$factory=null)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
public function execute(array $params = array(), $deleted = false)
|
||||
{
|
||||
return $this->factory->executeSearch($this, $params, $deleted);
|
||||
}
|
||||
|
||||
public function where(string $set = null) : ?string
|
||||
{
|
||||
if ($set !== null) {
|
||||
$this->where = $set;
|
||||
}
|
||||
return $this->where;
|
||||
}
|
||||
|
||||
public function order(string $set = null) : ?string
|
||||
{
|
||||
if ($set !== null) {
|
||||
$this->order = $set;
|
||||
}
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
public function limit(int $set = null) : ?int
|
||||
{
|
||||
if ($set !== null) {
|
||||
$this->limit = $set;
|
||||
}
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function offset(int $set = null) : ?int
|
||||
{
|
||||
if ($set !== null) {
|
||||
$this->offset = $set;
|
||||
}
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
{
|
||||
return json_encode(
|
||||
[$this->where(),$this->order(),$this->limit(),$this->offset()]
|
||||
);
|
||||
}
|
||||
|
||||
public function unserialize($string)
|
||||
{
|
||||
list($where, $order, $limit, $offset) = json_decode($string, true);
|
||||
$this->where($where);
|
||||
$this->order($order);
|
||||
$this->limit($limit);
|
||||
$this->offset($offset);
|
||||
}
|
||||
}
|
128
src/Utils/BadWords.php
Normal file
128
src/Utils/BadWords.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
/**
|
||||
* Digraph CMS: DataObject
|
||||
* https://github.com/digraphcms/digraph-dataobject
|
||||
|
||||
* Copyright (c) 2017 Joby Elliott <joby@byjoby.com>
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*/
|
||||
namespace Digraph\DataObject\Utils;
|
||||
|
||||
/**
|
||||
* A very aggressive profanity filter, used in AbstractDataObject to make an
|
||||
* attempt at not generating IDs with profanity in them.
|
||||
*/
|
||||
class BadWords
|
||||
{
|
||||
|
||||
protected static $_setup = false;
|
||||
protected static $_dict = array();
|
||||
protected static $_dictSrc = array();
|
||||
protected static $_leet = array();
|
||||
protected static $_leetSrc = array();
|
||||
|
||||
/**
|
||||
* Check whether a piece of text contains anything that looks like a bad word
|
||||
* @param string $text
|
||||
* @return bool
|
||||
*/
|
||||
public static function profane($text)
|
||||
{
|
||||
if (!static::$_setup) {
|
||||
static::init();
|
||||
}
|
||||
foreach (static::$_dict as $pattern) {
|
||||
if (preg_match('/'.$pattern.'/i', $text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init class with default words and leet replacements
|
||||
* @return void
|
||||
*/
|
||||
protected static function init()
|
||||
{
|
||||
//add and prep leet replacements
|
||||
$leet = explode(PHP_EOL, trim(file_get_contents(__DIR__.'/_resources/leet.txt')));
|
||||
foreach ($leet as $replacement) {
|
||||
$replacement = explode(' ', $replacement);
|
||||
static::addLeet($replacement[0], $replacement[1], true);
|
||||
}
|
||||
static::prepLeet();
|
||||
//add and prep dictionary
|
||||
foreach (explode(PHP_EOL, trim(file_get_contents(__DIR__.'/_resources/badwords.txt'))) as $word) {
|
||||
static::addWord($word, true);
|
||||
}
|
||||
static::prepDict();
|
||||
}
|
||||
|
||||
/**
|
||||
* add a Leet replacement
|
||||
* @param string $a original
|
||||
* @param string $b replacement
|
||||
* @param boolean $skipPrep whether to run prepLeet afterwards
|
||||
*/
|
||||
public static function addLeet($a, $b, $skipPrep = false)
|
||||
{
|
||||
if (!isset(static::$_leet[$a])) {
|
||||
static::$_leetSrc[$a] = array($a=>$a);
|
||||
}
|
||||
static::$_leetSrc[$a][$b] = $b;
|
||||
if (!$skipPrep) {
|
||||
static::prepLeet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse added Leet rules into a set of regex rules
|
||||
* @return void
|
||||
*/
|
||||
public static function prepLeet()
|
||||
{
|
||||
static::$_leet = array();
|
||||
foreach (static::$_leetSrc as $key => $value) {
|
||||
static::$_leet[$key] = '('.implode('|', $value).')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add a profane word
|
||||
* @param string $word
|
||||
* @param boolean $skipPrep whether to run prepDict afterwards
|
||||
*/
|
||||
public static function addWord($word, $skipPrep = false)
|
||||
{
|
||||
static::$_dictSrc[] = $word;
|
||||
static::$_dictSrc = array_unique(static::$_dictSrc);
|
||||
if (!$skipPrep) {
|
||||
static::prepDict();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse added words and Leet rules into a set of regex rules
|
||||
* @return void
|
||||
*/
|
||||
public static function prepDict()
|
||||
{
|
||||
static::$_dict = array();
|
||||
foreach (static::$_dictSrc as $word) {
|
||||
foreach (static::$_leet as $a => $b) {
|
||||
$word = str_replace($a, $b, $word);
|
||||
}
|
||||
static::$_dict[] = $word;
|
||||
}
|
||||
}
|
||||
}
|
62
src/Utils/_resources/badwords.txt
Normal file
62
src/Utils/_resources/badwords.txt
Normal file
|
@ -0,0 +1,62 @@
|
|||
an(al|us)
|
||||
arse
|
||||
ass
|
||||
balls
|
||||
bastard
|
||||
bia?tch
|
||||
blood
|
||||
blowjob
|
||||
b(o|u)ll?oc?k
|
||||
boner
|
||||
boob
|
||||
butt
|
||||
chink
|
||||
clit
|
||||
cock
|
||||
coon
|
||||
crap
|
||||
cuck
|
||||
cunt
|
||||
damn
|
||||
dick
|
||||
dildo
|
||||
dyke
|
||||
fag
|
||||
feck
|
||||
fellat
|
||||
felch
|
||||
fuck
|
||||
fudgepacker
|
||||
flange
|
||||
hell
|
||||
homo
|
||||
jerk
|
||||
jizz
|
||||
knob
|
||||
kike
|
||||
kyke
|
||||
labia
|
||||
muff
|
||||
nigg?(er)?
|
||||
penis
|
||||
piss
|
||||
poop
|
||||
prick
|
||||
pr0n
|
||||
pube
|
||||
pussy
|
||||
queer
|
||||
scrotum
|
||||
sex
|
||||
shit
|
||||
slut
|
||||
smegma
|
||||
spunk
|
||||
tit
|
||||
tosser
|
||||
turd
|
||||
twat
|
||||
vagina
|
||||
wank
|
||||
whore
|
||||
wtf
|
15
src/Utils/_resources/leet.txt
Normal file
15
src/Utils/_resources/leet.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
a 4
|
||||
b 8
|
||||
ck xx?
|
||||
ex ecks
|
||||
e 3
|
||||
f ph
|
||||
g 6
|
||||
i l
|
||||
i 1
|
||||
o 0
|
||||
qu kw
|
||||
s 5
|
||||
s z
|
||||
t 7
|
||||
z 2
|
64
src/_resources/badwords.txt
Normal file
64
src/_resources/badwords.txt
Normal file
|
@ -0,0 +1,64 @@
|
|||
anal
|
||||
anus
|
||||
arse
|
||||
ass
|
||||
balls
|
||||
bastard
|
||||
bitch
|
||||
biatch
|
||||
blood
|
||||
blowjob
|
||||
bollo
|
||||
boner
|
||||
boob
|
||||
butt
|
||||
chink
|
||||
clit
|
||||
cock
|
||||
coon
|
||||
crap
|
||||
cuck
|
||||
cunt
|
||||
damn
|
||||
dick
|
||||
dildo
|
||||
dyke
|
||||
fag
|
||||
feck
|
||||
fellat
|
||||
felching
|
||||
fuck
|
||||
fudgepacker
|
||||
flange
|
||||
hell
|
||||
homo
|
||||
jerk
|
||||
jizz
|
||||
knob
|
||||
kike
|
||||
kyke
|
||||
labia
|
||||
muff
|
||||
nig
|
||||
penis
|
||||
piss
|
||||
poop
|
||||
prick
|
||||
pr0n
|
||||
pube
|
||||
pussy
|
||||
queer
|
||||
scrotum
|
||||
sex
|
||||
shit
|
||||
slut
|
||||
smegma
|
||||
spunk
|
||||
tit
|
||||
tosser
|
||||
turd
|
||||
twat
|
||||
vagina
|
||||
wank
|
||||
whore
|
||||
wtf
|
12
src/_resources/leet.txt
Normal file
12
src/_resources/leet.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
a 4
|
||||
b 8
|
||||
e 3
|
||||
f ph
|
||||
g 6
|
||||
i l
|
||||
i 1
|
||||
o 0
|
||||
s 5
|
||||
t 7
|
||||
z 2
|
||||
s z
|
BIN
test.sqlite
Normal file
BIN
test.sqlite
Normal file
Binary file not shown.
125
tests/AbstractArrayDataObjectTest.php
Normal file
125
tests/AbstractArrayDataObjectTest.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
namespace Digraph\DataObject\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Digraph\DataObject\Tests\AbstractArrayHarnessObject;
|
||||
|
||||
class AbstractArrayDataObjectTest extends TestCase
|
||||
{
|
||||
public function testFundamentalGetters()
|
||||
{
|
||||
$testObj = new AAOT();
|
||||
$this->assertEquals(
|
||||
'testPropDefault-gotten',
|
||||
$testObj["testProp1"],
|
||||
"Getter set in map isn't altering output"
|
||||
);
|
||||
$this->assertEquals(
|
||||
'testPropDefault',
|
||||
$testObj["testProp1Raw"],
|
||||
"Getter set via function name (_get_testProp1Raw) isn't working"
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$testObj["doesNotExist"],
|
||||
"getting must return null for nonexistent properties"
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$testObj["testProp4"],
|
||||
"getting must return null for masked properties"
|
||||
);
|
||||
}
|
||||
|
||||
public function testFundamentalIssetters()
|
||||
{
|
||||
$testObj = new AAOT();
|
||||
$this->assertFalse(
|
||||
isset($testObj["doesNotExist"]),
|
||||
"isset() must return false for items that don't exist"
|
||||
);
|
||||
$this->assertTrue(
|
||||
isset($testObj["testProp1"]),
|
||||
"isset() must return true for items with custom getters in the map"
|
||||
);
|
||||
$this->assertTrue(
|
||||
isset($testObj["testProp1Raw"]),
|
||||
"isset() must return true for custom get functions like _get_testProp1Raw()"
|
||||
);
|
||||
$this->assertTrue(
|
||||
isset($testObj["testProp2"]),
|
||||
"isset() must return true for items with no custom getter"
|
||||
);
|
||||
$this->assertFalse(
|
||||
isset($testObj["testProp4"]),
|
||||
"isset() must return false for masked properties"
|
||||
);
|
||||
}
|
||||
|
||||
public function testFundamentalSetters()
|
||||
{
|
||||
$testObj = new AAOT();
|
||||
$testObj["testProp2"] = 'testPropSet';
|
||||
$this->assertEquals(
|
||||
'testPropSet',
|
||||
$testObj["testProp2"],
|
||||
"Setting an item with no custom setter isn't working"
|
||||
);
|
||||
$testData = array(
|
||||
"test",
|
||||
"data",
|
||||
array("nested")
|
||||
);
|
||||
$testObj["testProp3"] = $testData;
|
||||
$this->assertEquals(
|
||||
$testData[0],
|
||||
$testObj["testProp3"][0],
|
||||
"JSON setter assigned in map isn't working"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AAOT extends AbstractArrayHarnessObject
|
||||
{
|
||||
static $TESTPROP1 = array(
|
||||
'name' => 'testprop1',
|
||||
'default' => 'testPropDefault',
|
||||
'transform' => 'testGetter'
|
||||
);
|
||||
|
||||
static $TESTPROP2 = array(
|
||||
'name' => 'testprop2',
|
||||
'default' => 'testProp2Default'
|
||||
);
|
||||
|
||||
static $TESTPROP3 = array(
|
||||
'name' => 'testProp3',
|
||||
'transform' => 'JSON'
|
||||
);
|
||||
|
||||
static $TESTPROP4 = array(
|
||||
'name' => 'testProp4',
|
||||
'masked' => true,
|
||||
'default' => 'testProp4Default'
|
||||
);
|
||||
|
||||
static function getMap()
|
||||
{
|
||||
$map = parent::getMap();
|
||||
$map['testProp1'] = static::$TESTPROP1;
|
||||
$map['testProp2'] = static::$TESTPROP2;
|
||||
$map['testProp3'] = static::$TESTPROP3;
|
||||
$map['testProp4'] = static::$TESTPROP4;
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function _getter_test($name)
|
||||
{
|
||||
return $this->getRaw($name) . '-gotten';
|
||||
}
|
||||
|
||||
public function _get_testProp1Raw($name)
|
||||
{
|
||||
return $this->getRaw('testProp1');
|
||||
}
|
||||
}
|
42
tests/AbstractArrayHarnessObject.php
Normal file
42
tests/AbstractArrayHarnessObject.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
namespace Digraph\DataObject\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Digraph\DataObject\AbstractArrayDataObject;
|
||||
|
||||
class AbstractArrayHarnessObject extends AbstractArrayDataObject
|
||||
{
|
||||
protected static $_classTransforms = array(
|
||||
'testGetter' => array(
|
||||
'get' => 'testGetter'
|
||||
),
|
||||
'testSetter' => array(
|
||||
'set' => 'testSetter'
|
||||
)
|
||||
);
|
||||
|
||||
public function testGetter($name)
|
||||
{
|
||||
return $this->getRaw($name) . '-gotten';
|
||||
}
|
||||
|
||||
function create()
|
||||
{
|
||||
}
|
||||
static function read($id)
|
||||
{
|
||||
}
|
||||
static function search($parameters = array(), $sort = array(), $options = array())
|
||||
{
|
||||
}
|
||||
function update($action = null)
|
||||
{
|
||||
}
|
||||
function delete($permanent = false)
|
||||
{
|
||||
}
|
||||
static function idExists($id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
166
tests/AbstractDataObjectTest.php
Normal file
166
tests/AbstractDataObjectTest.php
Normal file
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
namespace Digraph\DataObject\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Digraph\DataObject\Tests\AbstractHarnessObject;
|
||||
|
||||
class AbstractDataObjectTest extends TestCase
|
||||
{
|
||||
public function testMap()
|
||||
{
|
||||
|
||||
$baseMap = AbstractHarnessObject::map();
|
||||
$testMap = ADOT::map();
|
||||
|
||||
$this->assertEquals(
|
||||
4,
|
||||
count($testMap)-count($baseMap),
|
||||
"The test harness object doesn't have the right number of map items"
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
ADOT::$TESTPROP1,
|
||||
ADOT::mapEntry('testProp1')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Digraph\DataObject\Exceptions\IDSpaceException
|
||||
*/
|
||||
function testIDSpaceExhaustion()
|
||||
{
|
||||
new ADOT_BrokenIDExists();
|
||||
}
|
||||
|
||||
public function testGeneration()
|
||||
{
|
||||
$testObj = new ADOT(array(
|
||||
'testProp2' => 'manuallySet'
|
||||
));
|
||||
//cdate and mdate should be very close to now
|
||||
$this->assertLessThan(
|
||||
1,
|
||||
time()-$testObj->do_cdate,
|
||||
"Newly created object's do_cdate is more than 1 second from now"
|
||||
);
|
||||
$this->assertLessThan(
|
||||
1,
|
||||
time()-$testObj->do_mdate,
|
||||
"Newly created object's do_mdate is more than 1 second from now"
|
||||
);
|
||||
$this->assertEquals(
|
||||
'manuallySet',
|
||||
$testObj->testProp2,
|
||||
"Newly created object's property not correctly set from array"
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetters()
|
||||
{
|
||||
$testObj = new ADOT();
|
||||
$this->assertEquals(
|
||||
'testPropDefault-gotten',
|
||||
$testObj->testProp1,
|
||||
"Getter set in map isn't altering output"
|
||||
);
|
||||
$this->assertEquals(
|
||||
'testPropDefault',
|
||||
$testObj->testProp1Raw,
|
||||
"Getter set via function name (_get_testProp1Raw) isn't working"
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$testObj->doesNotExist,
|
||||
"__get() must return null for nonexistent properties"
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$testObj->testProp4,
|
||||
"__get() must return null for masked properties"
|
||||
);
|
||||
}
|
||||
|
||||
public function testIssetters()
|
||||
{
|
||||
$testObj = new ADOT();
|
||||
$this->assertFalse(
|
||||
isset($testObj->doesNotExist),
|
||||
"__isset() must return false for items that don't exist"
|
||||
);
|
||||
$this->assertTrue(
|
||||
isset($testObj->testProp1),
|
||||
"__isset() must return true for items with custom getters in the map"
|
||||
);
|
||||
$this->assertTrue(
|
||||
isset($testObj->testProp1Raw),
|
||||
"__isset() must return true for custom get functions like _get_testProp1Raw()"
|
||||
);
|
||||
$this->assertTrue(
|
||||
isset($testObj->testProp2),
|
||||
"__isset() must return true for items with no custom getter"
|
||||
);
|
||||
$this->assertFalse(
|
||||
isset($testObj->testProp4),
|
||||
"__isset() must return false for masked properties"
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetters()
|
||||
{
|
||||
$testObj = new ADOT();
|
||||
$testObj->testProp2 = 'testPropSet';
|
||||
$this->assertEquals(
|
||||
'testPropSet',
|
||||
$testObj->testProp2,
|
||||
"Setting an item with no custom setter isn't working"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ADOT extends AbstractHarnessObject
|
||||
{
|
||||
static $TESTPROP1 = array(
|
||||
'name' => 'testprop1',
|
||||
'default' => 'testPropDefault',
|
||||
'transform' => 'testGetter'
|
||||
);
|
||||
|
||||
static $TESTPROP2 = array(
|
||||
'name' => 'testprop2',
|
||||
'default' => 'testProp2Default'
|
||||
);
|
||||
|
||||
static $TESTPROP3 = array(
|
||||
'name' => 'testProp3',
|
||||
'transform' => 'JSON'
|
||||
);
|
||||
|
||||
static $TESTPROP4 = array(
|
||||
'name' => 'testProp4',
|
||||
'masked' => true,
|
||||
'default' => 'testProp4Default'
|
||||
);
|
||||
|
||||
static function getMap()
|
||||
{
|
||||
$map = parent::getMap();
|
||||
$map['testProp1'] = static::$TESTPROP1;
|
||||
$map['testProp2'] = static::$TESTPROP2;
|
||||
$map['testProp3'] = static::$TESTPROP3;
|
||||
$map['testProp4'] = static::$TESTPROP4;
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function _get_testProp1Raw($name)
|
||||
{
|
||||
return $this->getRaw('testProp1');
|
||||
}
|
||||
}
|
||||
|
||||
class ADOT_BrokenIDExists extends ADOT
|
||||
{
|
||||
static function idExists($id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
44
tests/AbstractHarnessObject.php
Normal file
44
tests/AbstractHarnessObject.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
namespace Digraph\DataObject\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Digraph\DataObject\AbstractArrayDataObject;
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
|
||||
class AbstractHarnessObject extends AbstractArrayDataObject
|
||||
{
|
||||
protected static $_classTransforms = array(
|
||||
'testGetter' => array(
|
||||
'get' => 'testGetter'
|
||||
),
|
||||
'testSetter' => array(
|
||||
'set' => 'testSetter'
|
||||
)
|
||||
);
|
||||
|
||||
public function testGetter($name)
|
||||
{
|
||||
return $this->getRaw($name) . '-gotten';
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
}
|
||||
public static function read($id)
|
||||
{
|
||||
}
|
||||
public static function search($parameters = array(), $sort = array(), $options = array())
|
||||
{
|
||||
}
|
||||
public function update($action = null)
|
||||
{
|
||||
}
|
||||
public function delete($permanent = false)
|
||||
{
|
||||
}
|
||||
public static function idExists($id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DSOTest extends TestCase
|
||||
{
|
||||
public function testChangeTracking()
|
||||
{
|
||||
$dso = new DSO([
|
||||
'a' => 'b',
|
||||
'c' => 'd',
|
||||
'e' => [1,2,3]
|
||||
]);
|
||||
$this->assertEquals([], $dso->changes());
|
||||
$this->assertEquals([], $dso->removals());
|
||||
//not actually a change, shouldn't trigger
|
||||
$dso['a'] = 'b';
|
||||
$this->assertEquals([], $dso->changes());
|
||||
$this->assertEquals([], $dso->removals());
|
||||
//not actually a change, shouldn't trigger
|
||||
$dso['e'] = [1,2,3];
|
||||
$this->assertEquals([], $dso->changes());
|
||||
$this->assertEquals([], $dso->removals());
|
||||
//changing a should trigger changes but not removals
|
||||
$dso['a'] = 'B';
|
||||
$this->assertEquals(['a'=>'B'], $dso->changes());
|
||||
$this->assertEquals([], $dso->removals());
|
||||
//removing c should trigger removals but not change changes
|
||||
unset($dso['c']);
|
||||
$this->assertEquals(['a'=>'B'], $dso->changes());
|
||||
$this->assertEquals(['c'=>'d'], $dso->removals());
|
||||
//setting c back should remove it from removals, but add it to changes
|
||||
$dso['c'] = 'd';
|
||||
$this->assertEquals(['a'=>'B','c'=>'d'], $dso->changes());
|
||||
$this->assertEquals([], $dso->removals());
|
||||
//unsetting c again should remove it from changes, but add it back to removals
|
||||
unset($dso['c']);
|
||||
$this->assertEquals(['a'=>'B'], $dso->changes());
|
||||
$this->assertEquals(['c'=>'d'], $dso->removals());
|
||||
//resetting changes
|
||||
$dso->resetChanges();
|
||||
$this->assertEquals([], $dso->changes());
|
||||
$this->assertEquals([], $dso->removals());
|
||||
}
|
||||
|
||||
public function testGetting()
|
||||
{
|
||||
$data = [
|
||||
'a' => 'A',
|
||||
'b' => ['c'=>'C']
|
||||
];
|
||||
$a = new DSO($data);
|
||||
//first level
|
||||
$this->assertEquals('A', $a['a']);
|
||||
$this->assertEquals('A', $a->get('a'));
|
||||
//nested
|
||||
$this->assertEquals('C', $a['b.c']);
|
||||
$this->assertEquals('C', $a->get('b.c'));
|
||||
//returning array
|
||||
$this->assertEquals(['c'=>'C'], $a['b']);
|
||||
$this->assertEquals(['c'=>'C'], $a->get('b'));
|
||||
//returning entire array by requesting null or empty string
|
||||
$this->assertEquals($data, $a[null]);
|
||||
$this->assertEquals($data, $a->get());
|
||||
$this->assertEquals($data, $a['']);
|
||||
$this->assertEquals($data, $a->get(''));
|
||||
//requesting invalid keys should return null
|
||||
$this->assertNull($a->get('nonexistent'));
|
||||
$this->assertNull($a->get('b.nonexistent'));
|
||||
$this->assertNull($a->get('..'));
|
||||
$this->assertNull($a->get('.'));
|
||||
//double dots
|
||||
$this->assertNull($a->get('..a'));
|
||||
$this->assertNull($a->get('a..'));
|
||||
$this->assertNull($a->get('..a..'));
|
||||
$this->assertNull($a->get('..a..'));
|
||||
$this->assertNull($a->get('b..c'));
|
||||
$this->assertNull($a->get('b..c..'));
|
||||
$this->assertNull($a->get('..b..c'));
|
||||
$this->assertNull($a->get('..b..c..'));
|
||||
$this->assertNull($a->get('b.c..'));
|
||||
$this->assertNull($a->get('..b.c'));
|
||||
$this->assertNull($a->get('..b.c..'));
|
||||
//single dots
|
||||
$this->assertNull($a->get('.a'));
|
||||
$this->assertNull($a->get('a.'));
|
||||
$this->assertNull($a->get('.a.'));
|
||||
$this->assertNull($a->get('.a.'));
|
||||
$this->assertNull($a->get('b.c.'));
|
||||
$this->assertNull($a->get('.b.c'));
|
||||
$this->assertNull($a->get('.b.c.'));
|
||||
$this->assertNull($a->get('b.c.'));
|
||||
$this->assertNull($a->get('.b.c'));
|
||||
$this->assertNull($a->get('.b.c.'));
|
||||
}
|
||||
|
||||
public function testSetting()
|
||||
{
|
||||
$data = [
|
||||
'a' => 'A',
|
||||
'b' => ['c'=>'C']
|
||||
];
|
||||
$a = new DSO($data);
|
||||
//setting on first layer
|
||||
$a['a'] = 'B';
|
||||
$this->assertEquals('B', $a['a']);
|
||||
$a['new'] = 'NEW';
|
||||
$this->assertEquals('NEW', $a['new']);
|
||||
//setting nested
|
||||
$a['b.c'] = 'D';
|
||||
$this->assertEquals('D', $a['b.c']);
|
||||
$a['b.new'] = 'NEW';
|
||||
$this->assertEquals('NEW', $a['b.new']);
|
||||
//final state
|
||||
$this->assertEquals(
|
||||
[
|
||||
'a' => 'B',
|
||||
'b' => [
|
||||
'c' => 'D',
|
||||
'new' => 'NEW'
|
||||
],
|
||||
'new' => 'NEW'
|
||||
],
|
||||
$a->get()
|
||||
);
|
||||
}
|
||||
|
||||
public function testSettingFalseyValues()
|
||||
{
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a['foo.bar'] = false;
|
||||
$this->assertFalse($a['foo.bar']);
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a['foo.bar'] = 0;
|
||||
$this->assertSame(0, $a['foo.bar']);
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a['foo.bar'] = '';
|
||||
$this->assertSame('', $a['foo.bar']);
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a['foo.bar'] = [];
|
||||
$this->assertIsArray($a['foo.bar']);
|
||||
}
|
||||
|
||||
public function testMergingFalseyValues()
|
||||
{
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a->merge(['foo'=>['bar'=>false]], null, true);
|
||||
$this->assertFalse($a['foo.bar']);
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a->merge(['foo'=>['bar'=>0]], null, true);
|
||||
$this->assertSame(0, $a['foo.bar']);
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a->merge(['foo'=>['bar'=>'']], null, true);
|
||||
$this->assertSame('', $a['foo.bar']);
|
||||
$a = new DSO(['foo'=>['bar'=>'baz']]);
|
||||
$a->merge(['foo'=>['bar'=>[]]], null, true);
|
||||
$this->assertIsArray($a['foo.bar']);
|
||||
}
|
||||
|
||||
public function testMerge()
|
||||
{
|
||||
$data = [
|
||||
'a' => 'b',
|
||||
'c' => [
|
||||
'd' => 'e'
|
||||
]
|
||||
];
|
||||
//overwrite false, original values should be preserved
|
||||
$c = new DSO($data);
|
||||
$c->merge([
|
||||
'a' => 'B',
|
||||
'c' => [
|
||||
'd' => 'E',
|
||||
'f' => 'g'
|
||||
],
|
||||
'h' => 'i'
|
||||
]);
|
||||
$this->assertEquals('b', $c['a']);
|
||||
$this->assertEquals('e', $c['c.d']);
|
||||
$this->assertEquals('i', $c['h']);
|
||||
$this->assertEquals('g', $c['c.f']);
|
||||
//overwrite true, original values should be overwritten
|
||||
$c = new DSO($data);
|
||||
$c->merge([
|
||||
'a' => 'B',
|
||||
'c' => [
|
||||
'd' => 'E',
|
||||
'f' => 'g'
|
||||
],
|
||||
'h' => 'i'
|
||||
], null, true);
|
||||
$this->assertEquals('B', $c['a']);
|
||||
$this->assertEquals('E', $c['c.d']);
|
||||
$this->assertEquals('i', $c['h']);
|
||||
$this->assertEquals('g', $c['c.f']);
|
||||
//overwrite false with mismatched array-ness
|
||||
$c = new DSO($data);
|
||||
$c->merge([
|
||||
'a' => ['b'=>'c'],
|
||||
'c' => 'd'
|
||||
]);
|
||||
$this->assertEquals('b', $c['a']);
|
||||
$this->assertEquals('e', $c['c.d']);
|
||||
//overwrite true with mismatched array-ness
|
||||
$c = new DSO($data);
|
||||
$c->merge([
|
||||
'a' => ['b'=>'c'],
|
||||
'c' => 'd'
|
||||
], null, true);
|
||||
$this->assertEquals('c', $c['a.b']);
|
||||
$this->assertEquals('d', $c['c']);
|
||||
}
|
||||
|
||||
public function testConstructionUnflattening()
|
||||
{
|
||||
$arr = new DSO([
|
||||
'foo.bar' => 'baz'
|
||||
]);
|
||||
$this->assertEquals(
|
||||
['foo'=>['bar'=>'baz']],
|
||||
$arr->get()
|
||||
);
|
||||
}
|
||||
}
|
54
tests/DataTransformers/JSONTest.php
Normal file
54
tests/DataTransformers/JSONTest.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
namespace Digraph\DataObject\Tests\DataTransformers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Digraph\DataObject\DataTransformers\JSON;
|
||||
|
||||
class JSONTest extends TestCase
|
||||
{
|
||||
public function testStorageValues()
|
||||
{
|
||||
$o = 'foo';
|
||||
$h = new JSON($o);
|
||||
$pre = $h->getStorageValue();
|
||||
$this->assertEquals('[]', $pre);
|
||||
$h['foo'] = 'bar';
|
||||
$post = $h->getStorageValue();
|
||||
$this->assertEquals(1, preg_match('/\{["\']foo["\']:["\']bar["\']\}/', $post));
|
||||
unset($h['foo']);
|
||||
$unset = $h->getStorageValue();
|
||||
$this->assertEquals('[]', $unset);
|
||||
}
|
||||
|
||||
public function testNullValues()
|
||||
{
|
||||
$o = 'foo';
|
||||
$h = new JSON($o);
|
||||
$pre = $h->getStorageValue();
|
||||
$this->assertEquals('[]', $pre);
|
||||
$h['foo'] = array(
|
||||
'bar' => null,
|
||||
'baz' => 'buzz'
|
||||
);
|
||||
$post = $h->getStorageValue();
|
||||
$this->assertEquals(1, preg_match('/\{["\']foo["\']:{["\']baz["\']:["\']buzz["\']}\}/', $post));
|
||||
}
|
||||
|
||||
public function testNullValuesOverwriting()
|
||||
{
|
||||
$o = 'foo';
|
||||
$h = new JSON($o);
|
||||
$pre = $h->getStorageValue();
|
||||
$this->assertEquals('[]', $pre);
|
||||
$h['foo'] = array(
|
||||
'bar' => 'baz',
|
||||
'baz' => 'buzz'
|
||||
);
|
||||
$h['foo'] = array(
|
||||
'bar' => null,
|
||||
'baz' => 'buzz'
|
||||
);
|
||||
$post = $h->getStorageValue();
|
||||
$this->assertEquals(1, preg_match('/\{["\']foo["\']:{["\']baz["\']:["\']buzz["\']}\}/', $post));
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr\Drivers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\DbUnit\TestCaseTrait;
|
||||
use Destructr\DSO;
|
||||
use Destructr\Search;
|
||||
use Destructr\Factory;
|
||||
|
||||
abstract class AbstractDriverIntegrationTest extends TestCase
|
||||
{
|
||||
use TestCaseTrait;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$pdo = static::createPDO();
|
||||
$pdo->exec('DROP TABLE '.static::TEST_TABLE);
|
||||
}
|
||||
|
||||
public function testCreateTable()
|
||||
{
|
||||
$factory = $this->createFactory();
|
||||
//should work the first time
|
||||
$out = $factory->createTable();
|
||||
if (!$out) {
|
||||
var_dump($factory->errorInfo());
|
||||
}
|
||||
$this->assertTrue($out);
|
||||
//but not the second time, because it already exists
|
||||
$this->assertFalse($factory->createTable());
|
||||
//table should exist and have zero rows
|
||||
$this->assertEquals(0, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||
}
|
||||
|
||||
public function testInsert()
|
||||
{
|
||||
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
||||
$factory = $this->createFactory();
|
||||
//inserting a freshly created object should return true
|
||||
$o = $factory->create(['dso.id'=>'object-one']);
|
||||
$this->assertTrue($o->insert());
|
||||
//inserting it a second time should not
|
||||
$this->assertFalse($o->insert());
|
||||
//there should now be one more row
|
||||
$this->assertEquals($startRowCount+1, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||
}
|
||||
|
||||
public function testReadAndUpdate()
|
||||
{
|
||||
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
||||
$factory = $this->createFactory();
|
||||
//insert some new objects
|
||||
$a1 = $factory->create(['foo'=>'bar']);
|
||||
$a1->insert();
|
||||
$b1 = $factory->create(['foo.bar'=>'baz']);
|
||||
$b1->insert();
|
||||
//read objects back out
|
||||
$a2 = $factory->read($a1['dso.id']);
|
||||
$b2 = $factory->read($b1['dso.id']);
|
||||
//objects should be the same
|
||||
$this->assertEquals($a1->get(), $a2->get());
|
||||
$this->assertEquals($b1->get(), $b2->get());
|
||||
//alter things in the objects and update them
|
||||
$a2['foo'] = 'baz';
|
||||
$b2['foo.bar'] = 'bar';
|
||||
$a2->update();
|
||||
$b2->update();
|
||||
//read objects back out a third time
|
||||
$a3 = $factory->read($a1['dso.id']);
|
||||
$b3 = $factory->read($b1['dso.id']);
|
||||
//objects should be the same
|
||||
$this->assertEquals($a2->get(), $a3->get());
|
||||
$this->assertEquals($b2->get(), $b3->get());
|
||||
//they should not be the same as the originals from the beginning
|
||||
$this->assertNotEquals($a1->get(), $a3->get());
|
||||
$this->assertNotEquals($b1->get(), $b3->get());
|
||||
//there should now be two more rows
|
||||
$this->assertEquals($startRowCount+2, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
||||
$factory = $this->createFactory();
|
||||
//insert some new objects
|
||||
$a1 = $factory->create(['testDelete'=>'undelete me']);
|
||||
$a1->insert();
|
||||
$b1 = $factory->create(['testDelete'=>'should be permanently deleted']);
|
||||
$b1->insert();
|
||||
//there should now be two more rows
|
||||
$this->assertEquals($startRowCount+2, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||
//delete one permanently and the other not, both shoudl take effect immediately
|
||||
$a1->delete();
|
||||
$b1->delete(true);
|
||||
//there should now be only one more row
|
||||
$this->assertEquals($startRowCount+1, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||
//a should be possible to read a back out with the right flags
|
||||
$this->assertNull($factory->read($a1['dso.id']));
|
||||
$this->assertNotNull($factory->read($a1['dso.id'], 'dso.id', true));
|
||||
$this->assertNotNull($factory->read($a1['dso.id'], 'dso.id', null));
|
||||
//undelete a, should have update() inside it
|
||||
$a1->undelete();
|
||||
//it should be possible to read a back out with different flags
|
||||
$this->assertNotNull($factory->read($a1['dso.id']));
|
||||
$this->assertNull($factory->read($a1['dso.id'], 'dso.id', true));
|
||||
$this->assertNotNull($factory->read($a1['dso.id'], 'dso.id', null));
|
||||
}
|
||||
|
||||
public function testSearch()
|
||||
{
|
||||
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
||||
$factory = $this->createFactory();
|
||||
//insert some dummy data
|
||||
$factory->create([
|
||||
'testSearch' => 'a',
|
||||
'a' => '1',
|
||||
'b' => '2'
|
||||
])->insert();
|
||||
$factory->create([
|
||||
'testSearch' => 'b',
|
||||
'a' => '2',
|
||||
'b' => '1'
|
||||
])->insert();
|
||||
$factory->create([
|
||||
'testSearch' => 'c',
|
||||
'a' => '3',
|
||||
'b' => '4'
|
||||
])->insert();
|
||||
$factory->create([
|
||||
'testSearch' => 'a',
|
||||
'a' => '4',
|
||||
'b' => '3'
|
||||
])->insert();
|
||||
//there should now be four more rows
|
||||
$this->assertEquals($startRowCount+4, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||
//TODO: test some searches
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Driver from class constants, so extending classes can test
|
||||
* different databases.
|
||||
*/
|
||||
public function createDriver()
|
||||
{
|
||||
$class = static::DRIVER_CLASS;
|
||||
return new $class(
|
||||
static::DRIVER_DSN,
|
||||
static::DRIVER_USERNAME,
|
||||
static::DRIVER_PASSWORD,
|
||||
static::DRIVER_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
public function createFactory()
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
return new Factory(
|
||||
$driver,
|
||||
static::TEST_TABLE
|
||||
);
|
||||
}
|
||||
|
||||
protected static function createPDO()
|
||||
{
|
||||
return new \PDO(
|
||||
static::DRIVER_DSN,
|
||||
static::DRIVER_USERNAME,
|
||||
static::DRIVER_PASSWORD,
|
||||
static::DRIVER_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->createDefaultDBConnection($this->createPDO(), 'phpunit');
|
||||
}
|
||||
|
||||
public function getDataSet()
|
||||
{
|
||||
return new \PHPUnit\DbUnit\DataSet\DefaultDataSet();
|
||||
}
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr\Drivers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\DbUnit\TestCaseTrait;
|
||||
use Destructr\DSO;
|
||||
use Destructr\Search;
|
||||
|
||||
/**
|
||||
* This class tests a factory in isolation. In the name of simplicity it's a bit
|
||||
* simplistic, because it doesn't get the help of the Factory.
|
||||
*
|
||||
* There is also a class called AbstractDriverIntegrationTest that tests drivers
|
||||
* through a Factory. The results of that are harder to interpret, but more
|
||||
* properly and thoroughly test the Drivers in a real environment.
|
||||
*/
|
||||
abstract class AbstractDriverTest extends TestCase
|
||||
{
|
||||
use TestCaseTrait;
|
||||
|
||||
/*
|
||||
In actual practice, these would come from a Factory
|
||||
*/
|
||||
protected $virtualColumns = [
|
||||
'dso.id' => [
|
||||
'name'=>'dso_id',
|
||||
'type'=>'VARCHAR(16)',
|
||||
'index' => 'BTREE',
|
||||
'unique' => true
|
||||
],
|
||||
'dso.type' => [
|
||||
'name'=>'dso_type',
|
||||
'type'=>'VARCHAR(30)',
|
||||
'index'=>'BTREE'
|
||||
],
|
||||
'dso.deleted' => [
|
||||
'name'=>'dso_deleted',
|
||||
'type'=>'BIGINT',
|
||||
'index'=>'BTREE'
|
||||
]
|
||||
];
|
||||
|
||||
public function testCreateTable()
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
$res = $driver->createTable('testCreateTable', $this->virtualColumns);
|
||||
$this->assertTrue($res);
|
||||
$this->assertEquals(0, $this->getConnection()->getRowCount('testCreateTable'));
|
||||
$this->assertFalse($driver->createTable('testCreateTable', $this->virtualColumns));
|
||||
}
|
||||
|
||||
public function testInsert()
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
$driver->createTable('testInsert', $this->virtualColumns);
|
||||
//test inserting an object
|
||||
$o = new DSO(['dso.id'=>'first-inserted']);
|
||||
$this->assertTrue($driver->insert('testInsert', $o));
|
||||
$this->assertEquals(1, $this->getConnection()->getRowCount('testInsert'));
|
||||
//test inserting a second object
|
||||
$o = new DSO(['dso.id'=>'second-inserted']);
|
||||
$this->assertTrue($driver->insert('testInsert', $o));
|
||||
$this->assertEquals(2, $this->getConnection()->getRowCount('testInsert'));
|
||||
//test inserting a second object with an existing id, it shouldn't work
|
||||
$o = new DSO(['dso.id'=>'first-inserted']);
|
||||
$this->assertFalse($driver->insert('testInsert', $o));
|
||||
$this->assertEquals(2, $this->getConnection()->getRowCount('testInsert'));
|
||||
}
|
||||
|
||||
public function testSelect()
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
$driver->createTable('testSelect', $this->virtualColumns);
|
||||
//set up dummy data
|
||||
$this->setup_testSelect();
|
||||
//empty search
|
||||
$search = new Search();
|
||||
$results = $driver->select('testSelect', $search, []);
|
||||
$this->assertSame(4, count($results));
|
||||
//sorting by json value sort
|
||||
$search = new Search();
|
||||
$search->order('${sort} asc');
|
||||
$results = $driver->select('testSelect', $search, []);
|
||||
$this->assertSame(4, count($results));
|
||||
$results = array_map(
|
||||
function ($a) {
|
||||
return json_decode($a['json_data'], true);
|
||||
},
|
||||
$results
|
||||
);
|
||||
$this->assertSame('item-a-1', $results[0]['dso']['id']);
|
||||
$this->assertSame('item-b-1', $results[1]['dso']['id']);
|
||||
$this->assertSame('item-a-2', $results[2]['dso']['id']);
|
||||
$this->assertSame('item-b-2', $results[3]['dso']['id']);
|
||||
// search with no results, searching by virtual column
|
||||
$search = new Search();
|
||||
$search->where('`dso_type` = :param');
|
||||
$results = $driver->select('testSelect', $search, [':param'=>'type-none']);
|
||||
$this->assertSame(0, count($results));
|
||||
// search with no results, searching by json field
|
||||
$search = new Search();
|
||||
$search->where('${foo} = :param');
|
||||
$results = $driver->select('testSelect', $search, [':param'=>'nonexistent foo value']);
|
||||
$this->assertSame(0, count($results));
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
$driver->createTable('testDelete', $this->virtualColumns);
|
||||
//set up dummy data
|
||||
$this->setup_testDelete();
|
||||
//try deleting an item
|
||||
$dso = new DSO(['dso.id'=>'item-a-1']);
|
||||
$driver->delete('testDelete', $dso);
|
||||
$this->assertEquals(3, $this->getConnection()->getRowCount('testDelete'));
|
||||
//try deleting an item at the other end of the table
|
||||
$dso = new DSO(['dso.id'=>'item-b-2']);
|
||||
$driver->delete('testDelete', $dso);
|
||||
$this->assertEquals(2, $this->getConnection()->getRowCount('testDelete'));
|
||||
}
|
||||
|
||||
protected function setup_testDelete()
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
$driver->insert('testDelete', new DSO([
|
||||
'dso'=>['id'=>'item-a-1','type'=>'type-a'],
|
||||
'foo'=>'bar',
|
||||
'sort'=>'a'
|
||||
]));
|
||||
$driver->insert('testDelete', new DSO([
|
||||
'dso'=>['id'=>'item-a-2','type'=>'type-a'],
|
||||
'foo'=>'baz',
|
||||
'sort'=>'c'
|
||||
]));
|
||||
$driver->insert('testDelete', new DSO([
|
||||
'dso'=>['id'=>'item-b-1','type'=>'type-b'],
|
||||
'foo'=>'buz',
|
||||
'sort'=>'b'
|
||||
]));
|
||||
$driver->insert('testDelete', new DSO([
|
||||
'dso'=>['id'=>'item-b-2','type'=>'type-b','deleted'=>100],
|
||||
'foo'=>'quz',
|
||||
'sort'=>'d'
|
||||
]));
|
||||
}
|
||||
|
||||
protected function setup_testSelect()
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
$driver->insert('testSelect', new DSO([
|
||||
'dso'=>['id'=>'item-a-1','type'=>'type-a'],
|
||||
'foo'=>'bar',
|
||||
'sort'=>'a'
|
||||
]));
|
||||
$driver->insert('testSelect', new DSO([
|
||||
'dso'=>['id'=>'item-a-2','type'=>'type-a'],
|
||||
'foo'=>'baz',
|
||||
'sort'=>'c'
|
||||
]));
|
||||
$driver->insert('testSelect', new DSO([
|
||||
'dso'=>['id'=>'item-b-1','type'=>'type-b'],
|
||||
'foo'=>'buz',
|
||||
'sort'=>'b'
|
||||
]));
|
||||
$driver->insert('testSelect', new DSO([
|
||||
'dso'=>['id'=>'item-b-2','type'=>'type-b','deleted'=>100],
|
||||
'foo'=>'quz',
|
||||
'sort'=>'d'
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Driver from class constants, so extending classes can test
|
||||
* different databases.
|
||||
*/
|
||||
public function createDriver()
|
||||
{
|
||||
$class = static::DRIVER_CLASS;
|
||||
return new $class(
|
||||
static::DRIVER_DSN,
|
||||
static::DRIVER_USERNAME,
|
||||
static::DRIVER_PASSWORD,
|
||||
static::DRIVER_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$pdo = static::createPDO();
|
||||
$pdo->exec('DROP TABLE testCreateTable');
|
||||
$pdo->exec('DROP TABLE testInsert');
|
||||
$pdo->exec('DROP TABLE testSelect');
|
||||
$pdo->exec('DROP TABLE testDelete');
|
||||
}
|
||||
|
||||
protected static function createPDO()
|
||||
{
|
||||
return new \PDO(
|
||||
static::DRIVER_DSN,
|
||||
static::DRIVER_USERNAME,
|
||||
static::DRIVER_PASSWORD,
|
||||
static::DRIVER_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->createDefaultDBConnection($this->createPDO(), 'phpunit');
|
||||
}
|
||||
|
||||
public function getDataSet()
|
||||
{
|
||||
return new \PHPUnit\DbUnit\DataSet\DefaultDataSet();
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr\Drivers\MySQL;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Destructr\Drivers\AbstractDriverIntegrationTest;
|
||||
use Destructr\Drivers\MySQLDriver;
|
||||
|
||||
class MySQLDriverIntegrationTest extends AbstractDriverIntegrationTest
|
||||
{
|
||||
const DRIVER_CLASS = \Destructr\Drivers\MySQLDriver::class;
|
||||
const DRIVER_DSN = 'mysql:host=mysql;dbname=destructr_test';
|
||||
const DRIVER_USERNAME = 'root';
|
||||
const DRIVER_PASSWORD = 'badpassword';
|
||||
const DRIVER_OPTIONS = null;
|
||||
const TEST_TABLE = 'integrationtest';
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr\Drivers\MySQL;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Destructr\Drivers\AbstractDriverTest;
|
||||
use Destructr\Drivers\MySQLDriver;
|
||||
|
||||
class MySQLDriverTest extends AbstractDriverTest
|
||||
{
|
||||
const DRIVER_CLASS = MySQLDriver::class;
|
||||
const DRIVER_DSN = 'mysql:host=mysql;dbname=destructr_test';
|
||||
const DRIVER_USERNAME = 'root';
|
||||
const DRIVER_PASSWORD = 'badpassword';
|
||||
const DRIVER_OPTIONS = null;
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FactoryTest extends TestCase
|
||||
{
|
||||
public function testSearch()
|
||||
{
|
||||
$d = new HarnessDriver('testSearch');
|
||||
$f = new Factory($d, 'table_name');
|
||||
|
||||
$s = $f->search();
|
||||
$s->where('foo = bar');
|
||||
|
||||
//default execute, should be adding dso_deleted is null to where
|
||||
$s->execute(['p'=>'q']);
|
||||
$this->assertEquals('table_name', $d->last_select['table']);
|
||||
$this->assertEquals('(foo = bar) AND `dso_deleted` is null', $d->last_select['search']->where());
|
||||
$this->assertEquals(['p'=>'q'], $d->last_select['params']);
|
||||
|
||||
//execute with deleted=true, should be adding dso_deleted is not null to where
|
||||
$s->execute(['p'=>'q'], true);
|
||||
$this->assertEquals('table_name', $d->last_select['table']);
|
||||
$this->assertEquals('(foo = bar) AND `dso_deleted` is not null', $d->last_select['search']->where());
|
||||
$this->assertEquals(['p'=>'q'], $d->last_select['params']);
|
||||
|
||||
//execute with deleted=null, shouldn't touch where
|
||||
$s->execute(['p'=>'q'], null);
|
||||
$this->assertEquals('table_name', $d->last_select['table']);
|
||||
$this->assertEquals('foo = bar', $d->last_select['search']->where());
|
||||
$this->assertEquals(['p'=>'q'], $d->last_select['params']);
|
||||
}
|
||||
|
||||
public function testInsert()
|
||||
{
|
||||
$d = new HarnessDriver('testInsert');
|
||||
$f = new Factory($d, 'table_name');
|
||||
//creating a new object
|
||||
$o = $f->create();
|
||||
$o->insert();
|
||||
$this->assertEquals('table_name', $d->last_insert['table']);
|
||||
$this->assertEquals($o->get('dso.id'), $d->last_insert['dso']->get('dso.id'));
|
||||
|
||||
//creating a second object to verify
|
||||
$o = $f->create();
|
||||
$o->insert();
|
||||
$this->assertEquals('table_name', $d->last_insert['table']);
|
||||
$this->assertEquals($o->get('dso.id'), $d->last_insert['dso']->get('dso.id'));
|
||||
}
|
||||
|
||||
public function testUpdate()
|
||||
{
|
||||
$d = new HarnessDriver('testUpdate');
|
||||
$f = new Factory($d, 'table_name');
|
||||
|
||||
//creatingtwo new objects
|
||||
$o1 = $f->create(['dso.id'=>'object1id']);
|
||||
$o2 = $f->create(['dso.id'=>'object2id']);
|
||||
//initially, updating shouldn't do anything because there are no changes
|
||||
$o1->update();
|
||||
$this->assertNull($d->last_update);
|
||||
//after making changes updating should do something
|
||||
$o1['foo'] = 'bar';
|
||||
$o1->update();
|
||||
$this->assertEquals('table_name', $d->last_update['table']);
|
||||
$this->assertEquals($o1->get('dso.id'), $d->last_update['dso']->get('dso.id'));
|
||||
//updating second object to verify
|
||||
$o2['foo'] = 'bar';
|
||||
$o2->update();
|
||||
$this->assertEquals('table_name', $d->last_update['table']);
|
||||
$this->assertEquals($o2->get('dso.id'), $d->last_update['dso']->get('dso.id'));
|
||||
//calling update on first object shouldn't do anything, because it hasn't changed
|
||||
$o1->update();
|
||||
$this->assertEquals('table_name', $d->last_update['table']);
|
||||
$this->assertEquals($o2->get('dso.id'), $d->last_update['dso']->get('dso.id'));
|
||||
//after making changes updating should do something
|
||||
$o1['foo'] = 'baz';
|
||||
$o1->update();
|
||||
$this->assertEquals('table_name', $d->last_update['table']);
|
||||
$this->assertEquals($o1->get('dso.id'), $d->last_update['dso']->get('dso.id'));
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
$d = new HarnessDriver('testInsert');
|
||||
$f = new Factory($d, 'table_name');
|
||||
|
||||
//non-permanent delete shouldn't do anything with deletion, but should call update
|
||||
$o = $f->create();
|
||||
$o->delete();
|
||||
$this->assertNull($d->last_delete);
|
||||
$this->assertEquals('table_name', $d->last_update['table']);
|
||||
$this->assertEquals($o->get('dso.id'), $d->last_update['dso']->get('dso.id'));
|
||||
//undelete should also not engage delete, but should call update
|
||||
$d->last_update = null;
|
||||
$o->undelete();
|
||||
$this->assertNull($d->last_delete);
|
||||
$this->assertEquals('table_name', $d->last_update['table']);
|
||||
$this->assertEquals($o->get('dso.id'), $d->last_update['dso']->get('dso.id'));
|
||||
|
||||
//non-permanent delete shouldn't do anything with update, but should call delete
|
||||
$d->last_update = null;
|
||||
$o = $f->create();
|
||||
$o->delete(true);
|
||||
$this->assertNull($d->last_update);
|
||||
$this->assertEquals('table_name', $d->last_delete['table']);
|
||||
$this->assertEquals($o->get('dso.id'), $d->last_delete['dso']->get('dso.id'));
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
namespace Destructr;
|
||||
|
||||
class HarnessDriver implements Drivers\DSODriverInterface
|
||||
{
|
||||
const EXTENSIBLE_VIRTUAL_COLUMNS = true;
|
||||
public $last_select;
|
||||
public $last_insert;
|
||||
public $last_update;
|
||||
public $last_delete;
|
||||
|
||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null)
|
||||
{
|
||||
}
|
||||
|
||||
public function createTable(string $table, array $virtualColumns) : bool
|
||||
{
|
||||
//TODO: add tests for this too
|
||||
return false;
|
||||
}
|
||||
|
||||
public function select(string $table, Search $search, array $params) : array
|
||||
{
|
||||
$this->last_select = [
|
||||
'table' => $table,
|
||||
'search' => $search,
|
||||
'params' => $params
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
public function insert(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
$this->dsn = 'inserting';
|
||||
$this->last_insert = [
|
||||
'table' => $table,
|
||||
'dso' => $dso
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
public function update(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
$this->last_update = [
|
||||
'table' => $table,
|
||||
'dso' => $dso
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
public function delete(string $table, DSOInterface $dso) : bool
|
||||
{
|
||||
$this->last_delete = [
|
||||
'table' => $table,
|
||||
'dso' => $dso
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
public function errorInfo()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr\LegacyDrivers\SQLite;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Destructr\Drivers\AbstractDriverIntegrationTest;
|
||||
use Destructr\LegacyDrivers\SQLiteDriver;
|
||||
|
||||
class MySQLDriverTest extends AbstractDriverIntegrationTest
|
||||
{
|
||||
const DRIVER_CLASS = SQLiteDriver::class;
|
||||
const DRIVER_DSN = 'sqlite:'.__DIR__.'/integration.test.sqlite';
|
||||
const DRIVER_USERNAME = null;
|
||||
const DRIVER_PASSWORD = null;
|
||||
const DRIVER_OPTIONS = null;
|
||||
const TEST_TABLE = 'sqliteintegrationtest';
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
@unlink(__DIR__.'/integration.test.sqlite');
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
||||
declare(strict_types=1);
|
||||
namespace Destructr\LegacyDrivers\SQLite;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Destructr\Drivers\AbstractDriverTest;
|
||||
use Destructr\LegacyDrivers\SQLiteDriver;
|
||||
|
||||
class SQLiteDriverTest extends AbstractDriverTest
|
||||
{
|
||||
const DRIVER_CLASS = SQLiteDriver::class;
|
||||
const DRIVER_DSN = 'sqlite:'.__DIR__.'/driver.test.sqlite';
|
||||
const DRIVER_USERNAME = null;
|
||||
const DRIVER_PASSWORD = null;
|
||||
const DRIVER_OPTIONS = null;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
@unlink(__DIR__.'/driver.test.sqlite');
|
||||
}
|
||||
}
|
16
tests/Utils/BadWordsTest.php
Normal file
16
tests/Utils/BadWordsTest.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Digraph\DataObject\Tests\Utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Digraph\DataObject\Utils\BadWords;
|
||||
|
||||
class BadWordsTest extends TestCase
|
||||
{
|
||||
public function testProfanity()
|
||||
{
|
||||
$this->assertTrue(BadWords::profane('fuck'));
|
||||
$this->assertTrue(BadWords::profane('phuxx'));
|
||||
$this->assertFalse(BadWords::profane('ABCD'));
|
||||
$this->assertTrue(BadWords::profane('secks'));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue