first commit

This commit is contained in:
2024-11-10 21:08:49 +01:00
commit 0d932ce5ee
14455 changed files with 2567501 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
Copyright (c) 2013 The Authors
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.
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.

View File

@@ -0,0 +1,32 @@
{
"name": "mrclay/props-dic",
"type": "library",
"description": "Props is a simple DI container that allows retrieving values via custom property and method names",
"keywords": ["container", "dependency injection", "di", "di container", "dependency injection container"],
"license": "MIT",
"authors": [
{
"name": "Steve Clay",
"email": "steve@mrclay.org",
"homepage": "http://www.mrclay.org/"
}
],
"require": {
"php": ">=5.3.3",
"psr/container": "^1.0|^2.0",
"pimple/pimple": "~3.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
},
"autoload": {
"psr-0": {
"Props\\": ["src/"]
}
},
"autoload-dev": {
"psr-0": {
"Props\\": ["test/"]
}
}
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="Props Test Suite">
<directory>./test/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./test</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,75 @@
<?php
/**
* Example of Props\Pimple based on official Pimple docs
*/
namespace {
require __DIR__ . '/../vendor/autoload.php';
}
namespace PropsExample {
class SessionStorage {
public function __construct($cookieName) { $this->cookieName = $cookieName; }
}
class Session {
public function __construct($storage) { $this->storage = $storage; }
}
class Zend_Mail {
public function setFrom($from) { $this->from = $from; }
}
/**
* @property-read string $cookie_name
* @property-read string $session_storage_class
* @property-read Session $session
* @property-read \Closure $random
* @property-read Zend_Mail $mail
*/
class MyContainer2 extends \Props\Pimple {
public function __construct() {
parent::__construct();
$this->cookie_name = 'SESSION_ID';
$this->session_storage_class = 'PropsExample\\SessionStorage';
$this->session_storage = function (MyContainer2 $c) {
$class = $c->session_storage_class;
return new $class($c->cookie_name);
};
$this->session = $this->factory(function (MyContainer2 $c) {
return new Session($c->session_storage);
});
$this->random = $this->protect(function () { return rand(); });
$this->mail = function (MyContainer2 $c) {
return new Zend_Mail();
};
$this->{'mail.default_from'} = 'foo@example.com';
$this->extend('mail', function($mail, MyContainer2 $c) {
$mail->setFrom($c->{'mail.default_from'});
return $mail;
});
}
}
$c = new MyContainer2;
$r1 = $c->random;
$r2 = $c->random;
echo (int)($r1 === $r2) . "<br>";
echo $r1() . "<br>";
echo get_class($c->raw('session')) . '<br>';
echo var_export($c->session, true) . '<br>';
echo var_export($c->mail, true) . '<br>';
}

View File

@@ -0,0 +1,71 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
class AAA {}
class BBB {}
class CCC {
public function __construct(BBB $bbb) {}
public function setBbb(BBB $bbb) {}
public $aaa;
}
class DDD {}
function get_a_bbb() { return new BBB; }
/**
* @property-read AAA $aaa
* @property-read BBB $bbb1
* @property-read BBB $bbb2
* @property-read BBB $bbb3
* @property-read CCC $ccc
* @property-read DDD $ddd
*
* @method AAA new_aaa()
*/
class MyContainer extends \Props\Container {
public function __construct() {
// store plain old values
$this->ddd = new DDD;
$this->{'bbb.class'} = 'BBB';
// set a factory, which will construct an object on demand
$this->aaa = function () {
return new AAA();
};
// alternative factory syntax, and using a reference to specify the class name
$this->setFactory('bbb1', function (MyContainer $c) {
return new $c->{'bbb.class'};
});
// fetch with a callback
$this->setFactory('bbb2', 'get_a_bbb');
// Closures automatically used as factories
$this->bbb3 = function (MyContainer $c) {
return $c->bbb2;
};
// more advanced factory
$this->ccc = function (MyContainer $c) {
$val = new CCC($c->bbb1);
$val->setBbb($c->bbb2);
$val->aaa = $c->aaa;
return $val;
};
}
}
$c = new MyContainer;
$c->aaa; // factory builds a AAA
$c->aaa; // the same AAA
$c->new_aaa(); // always a freshly-built AAA
$c->bbb1; // factory resolves bar.class, builds a BBB
$c->bbb2; // invoker calls get_a_bbb()
$c->bbb3; // invoker executes anon func, returning the already-cached $c->bbb2 instance
$c->ccc; // factory creates CCC, passing a new BBB object,
// calls setBbb(), passing in $c->bbb2,
// and sets the aaa property to $c->aaa

View File

@@ -0,0 +1,55 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
class Dough {}
class Cheese {}
class Slice {}
class Pizza {
function __construct($style, Cheese $cheese) {}
function setDough(Dough $dough) {}
function getSlice() { return new Slice(); }
}
class CheeseFactory {
static function getCheese() { return new Cheese(); }
}
/**
* @property-read string $style
* @property-read Dough $dough
* @property-read Cheese $cheese
* @property-read Pizza $pizza
* @method Slice new_slice()
*/
class MyDI extends \Props\Container {
public function __construct() {
$this->style = 'deluxe';
$this->dough = function (MyContainer $c) {
return new Dough();
};
$this->setFactory('cheese', 'CheeseFactory::getCheese');
$this->pizza = function (MyContainer $c) {
$pizza = new Pizza($c->style, $c->cheese);
$pizza->setDough($c->dough);
return $pizza;
};
// note 3rd argument $shared is false
$this->slice = function (MyContainer $c) {
return $c->pizza->getSlice();
};
}
}
$c = new MyContainer;
// You can request dependencies in any order. They're resolved as needed.
$slice1 = $c->new_slice(); // This first resolves and caches the cheese, dough, and pizza.
$slice2 = $c->new_slice(); // This just gets a new slice from the existing pizza.
assert($slice1 !== $slice2);
assert($c->pizza === $c->pizza);

View File

@@ -0,0 +1,9 @@
<?php
namespace Props;
use Psr\Container\ContainerExceptionInterface;
class BadMethodCallException extends \Exception implements ContainerExceptionInterface
{
}

View File

@@ -0,0 +1,249 @@
<?php
namespace Props;
use Psr\Container\ContainerInterface;
/**
* Container holding values which can be resolved upon reading and optionally stored and shared
* across reads.
*
* Values are read/set as properties.
*
* @note see scripts/example.php
*/
class Container implements ContainerInterface
{
/**
* @var callable[]
*/
private $factories = array();
/**
* @var array
*/
private $cache = array();
/**
* Fetch a value.
*
* @param string $name
* @return mixed
* @throws FactoryUncallableException|ValueUnresolvableException|NotFoundException
*/
public function __get($name)
{
if (array_key_exists($name, $this->cache)) {
return $this->cache[$name];
}
$value = $this->build($name);
$this->cache[$name] = $value;
return $value;
}
/**
* {@inheritdoc}
*/
public function get($name)
{
return $this->__get($name);
}
/**
* Set a value.
*
* @param string $name
* @param mixed $value
* @throws \InvalidArgumentException
*/
public function __set($name, $value)
{
if ($value instanceof \Closure) {
$this->setFactory($name, $value);
return;
}
$this->cache[$name] = $value;
unset($this->factories[$name]);
}
/**
* Set a value to be later returned as is. You only need to use this if you wish to store
* a Closure.
*
* @param string $name
* @param mixed $value
* @throws \InvalidArgumentException
*/
public function setValue($name, $value)
{
unset($this->factories[$name]);
$this->cache[$name] = $value;
}
/**
* @param string $name
*/
public function __unset($name)
{
unset($this->cache[$name]);
unset($this->factories[$name]);
}
/**
* @param string $name
* @return bool
*/
public function __isset($name)
{
return array_key_exists($name, $this->factories) || array_key_exists($name, $this->cache);
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return $this->__isset($name);
}
/**
* Fetch a freshly-resolved value.
*
* @param string $method method name must start with "new_"
* @param array $args
* @return mixed
* @throws BadMethodCallException
*/
public function __call($method, $args)
{
if (0 !== strpos($method, 'new_')) {
throw new BadMethodCallException("Method name must begin with 'new_'");
}
return $this->build(substr($method, 4));
}
/**
* Can we fetch a new value via new_$name()?
*
* @param string $name
* @return bool
*/
public function hasFactory($name)
{
return array_key_exists($name, $this->factories);
}
/**
* Set a factory to generate a value when the container is read.
*
* @param string $name The name of the value
* @param callable $factory Factory for the value
* @throws FactoryUncallableException
*/
public function setFactory($name, $factory)
{
if (!is_callable($factory, true)) {
throw new FactoryUncallableException('$factory must appear callable');
}
unset($this->cache[$name]);
$this->factories[$name] = $factory;
}
/**
* Get an already-set factory callable (Closure, invokable, or callback)
*
* @param string $name The name of the value
* @return callable
* @throws NotFoundException
*/
public function getFactory($name)
{
if (!array_key_exists($name, $this->factories)) {
throw new NotFoundException("No factory available for: $name");
}
return $this->factories[$name];
}
/**
* Add a function that gets applied to the return value of an existing factory
*
* @note A cached value (from a previous property read) will thrown away. The next property read
* (and all new_NAME() calls) will call the original factory.
*
* @param string $name The name of the value
* @param callable $extender Function that is applied to extend the returned value
* @return \Closure
* @throws FactoryUncallableException|NotFoundException
*/
public function extend($name, $extender)
{
if (!is_callable($extender, true)) {
throw new FactoryUncallableException('$extender must appear callable');
}
if (!array_key_exists($name, $this->factories)) {
throw new NotFoundException("No factory available for: $name");
}
$factory = $this->factories[$name];
$newFactory = function (Container $c) use ($extender, $factory) {
return call_user_func($extender, call_user_func($factory, $c), $c);
};
$this->setFactory($name, $newFactory);
return $newFactory;
}
/**
* Get all keys available
*
* @return string[]
*/
public function getKeys()
{
$keys = array_keys($this->cache) + array_keys($this->factories);
return array_unique($keys);
}
/**
* Build a value
*
* @param string $name
* @return mixed
* @throws FactoryUncallableException|ValueUnresolvableException|NotFoundException
*/
private function build($name)
{
if (!array_key_exists($name, $this->factories)) {
throw new NotFoundException("Missing value: $name");
}
$factory = $this->factories[$name];
if (is_callable($factory)) {
try {
return call_user_func($factory, $this);
} catch (\Exception $e) {
throw new ValueUnresolvableException("Factory for '$name' threw an exception.", 0, $e);
}
}
$msg = "Factory for '$name' was uncallable";
if (is_string($factory)) {
$msg .= ": '$factory'";
} elseif (is_array($factory)) {
if (is_string($factory[0])) {
$msg .= ": '{$factory[0]}::{$factory[1]}'";
} else {
$msg .= ": " . get_class($factory[0]) . "->{$factory[1]}";
}
}
throw new FactoryUncallableException($msg);
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Props;
use Psr\Container\ContainerExceptionInterface;
class FactoryUncallableException extends \Exception implements ContainerExceptionInterface
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Props;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class NotFoundException extends \Exception implements ContainerExceptionInterface, NotFoundExceptionInterface
{
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Props;
/**
* A version of Pimple that uses property access instead of array access
*
* @author Steve Clay <steve@mrclay.org>
*/
class Pimple extends \Pimple\Container
{
/**
* Sets a parameter or an object.
*
* @param string $id The unique identifier for the parameter or object
* @param mixed $value The value of the parameter or a closure to define an object
* @throws \RuntimeException Prevent override of a frozen service
*/
public function __set($id, $value)
{
$this->offsetSet($id, $value);
}
/**
* Gets a parameter or an object.
*
* @param string $id The unique identifier for the parameter or object
* @return mixed The value of the parameter or an object
* @throws \InvalidArgumentException if the identifier is not defined
*/
public function __get($id)
{
return $this->offsetGet($id);
}
/**
* Checks if a parameter or an object is set.
*
* @param string $id The unique identifier for the parameter or object
* @return Boolean
*/
public function __isset($id)
{
return $this->offsetExists($id);
}
/**
* Unsets a parameter or an object.
*
* @param string $id The unique identifier for the parameter or object
*/
public function __unset($id)
{
$this->offsetUnset($id);
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Props;
use Psr\Container\ContainerExceptionInterface;
class ValueUnresolvableException extends \Exception implements ContainerExceptionInterface
{
}

View File

@@ -0,0 +1,324 @@
<?php
namespace Props;
class ContainerTest extends \PHPUnit_Framework_TestCase
{
const TEST_CLASS = 'Props\ContainerTestObject';
public function testBasicInterop()
{
$di = new Container();
$this->assertInstanceOf('Psr\Container\ContainerInterface', $di);
$this->assertFalse($di->has('foo'));
$di->foo = 'bar';
$this->assertTrue($di->has('foo'));
}
/**
* @expectedException \Props\NotFoundException
*/
public function testInteropNotFound()
{
$di = new Container();
$di->get('foo');
}
/**
* @expectedException \Psr\Container\ContainerExceptionInterface
*/
public function testInteropException1()
{
$di = new Container();
$di->setFactory('foo', null);
}
/**
* @expectedException \Psr\Container\ContainerExceptionInterface
*/
public function testInteropException2()
{
$di = new Container();
$di->setFactory('foo', function () {
throw new \Exception();
});
$di->foo;
}
public function testEmpty()
{
$di = new Container();
$this->assertFalse(isset($di->foo));
$this->assertFalse($di->has('foo'));
}
public function testValueSetRemovesFactory()
{
$di = new Container();
$di->foo = function () {
return 'Bar';
};
$di->foo = 'Foo';
$this->assertTrue(isset($di->foo));
$this->assertFalse($di->hasFactory('foo'));
}
public function testSetResolvable()
{
$di = new Container();
$di->foo = function () {
return new ContainerTestObject();
};
$this->assertTrue(isset($di->foo));
$this->assertTrue($di->has('foo'));
$this->assertTrue($di->hasFactory('foo'));
}
/**
* @expectedException \Props\NotFoundException
*/
public function testReadMissingValue()
{
$di = new Container();
$di->foo;
}
/**
* @expectedException \Props\NotFoundException
*/
public function testGetMissingValue()
{
$di = new Container();
$di->get('foo');
}
public function testGetNewUnresolvableValue()
{
$di = new Container();
$di->foo = 'Foo';
$this->setExpectedException('Props\NotFoundException');
$di->new_foo();
}
public function testSetAfterRead()
{
$di = new Container();
$di->foo = 'Foo';
$di->foo = 'Foo2';
$this->assertEquals('Foo2', $di->foo);
}
public function testHandlesNullValue()
{
$di = new Container();
$di->null = null;
$this->assertTrue(isset($di->null));
$this->assertTrue($di->has('null'));
$this->assertNull($di->null);
$this->assertNull($di->get('null'));
}
public function testFactoryReceivesContainer()
{
$di = new Container();
$di->foo = function () {
return func_get_args();
};
$foo = $di->foo;
$this->assertSame($foo[0], $di);
$this->assertEquals(count($foo), 1);
}
public function testGetResolvables()
{
$di = new Container();
$di->foo = function () {
return new ContainerTestObject();
};
$foo1 = $di->foo;
$foo2 = $di->foo;
$this->assertInstanceOf(self::TEST_CLASS, $foo1);
$this->assertSame($foo1, $foo2);
$foo3 = $di->new_foo();
$foo4 = $di->new_foo();
$this->assertInstanceOf(self::TEST_CLASS, $foo3);
$this->assertInstanceOf(self::TEST_CLASS, $foo4);
$this->assertNotSame($foo3, $foo4);
$this->assertNotSame($foo1, $foo3);
}
public function testKeyNamespace()
{
$di = new Container();
$di->foo = function () {
return new ContainerTestObject();
};
$di->new_foo = 'Foo';
$this->assertInstanceOf(self::TEST_CLASS, $di->new_foo());
$this->assertEquals('Foo', $di->new_foo);
}
public function testUnset()
{
$di = new Container();
$di->foo = 'Foo';
unset($di->foo);
$this->assertFalse(isset($di->foo));
}
public function testAccessUnsetValue()
{
$di = new Container();
$di->foo = 'Foo';
unset($di->foo);
$this->setExpectedException('Props\NotFoundException');
$di->foo;
}
public function testSetFactory()
{
$di = new Container();
$di->setFactory('foo', function () {
$obj = new ContainerTestObject();
$obj->bar = 'bar';
return $obj;
});
$foo = $di->foo;
$this->assertInstanceOf(self::TEST_CLASS, $foo);
$this->assertEquals('bar', $foo->bar);
}
public function testSetValue()
{
$di = new Container();
$di->setValue('foo', function () {});
$this->assertInstanceOf('Closure', $di->foo);
}
/**
* @expectedException \Props\NotFoundException
*/
public function testCannotExtendValue()
{
$di = new Container();
$di->foo = 1;
$di->extend('foo', function ($value, Container $c) {
return $value + 1;
});
}
public function testExtend()
{
$di = new Container();
$di->key = 'count';
$di->counter = function (Container $c) {
static $i = 0;
$i++;
return (object)array(
$c->key => $i,
);
};
$c1 = $di->counter; // cached with $i = 1
$di->extend('counter', function ($value, Container $c) {
static $i = 0;
$i++;
$value->one = $i;
return $value;
});
$c2 = $di->counter; // because of extension, doesn't use original cached value
$this->assertEquals((object)array('count' => 2, 'one' => 1), $c2);
$this->assertNotSame($c1, $c2);
$di->key = 'total';
$c3 = $di->counter; // but caches repeat reads
$this->assertEquals((object)array('count' => 2, 'one' => 1), $c3);
$this->assertSame($c2, $c3);
$c4 = $di->new_counter();
$this->assertEquals((object)array('total' => 3, 'one' => 2), $c4);
$this->assertNotSame($c3, $c4);
$di->extend('counter', function ($value, Container $c) {
static $i = 0;
$i++;
$value->two = $i;
return $value;
});
$c5 = $di->counter; // going deep!
$this->assertEquals((object)array('total' => 4, 'one' => 3, 'two' => 1), $c5);
$c6 = $di->new_counter();
$this->assertEquals((object)array('total' => 5, 'one' => 4, 'two' => 2), $c6);
}
/**
* @expectedException \Props\NotFoundException
*/
public function testGetFactoryForValue()
{
$di = new Container();
$di->key = 'count';
$di->getFactory('key');
}
/**
* @expectedException \Props\NotFoundException
*/
public function testGetMissingFactory()
{
$di = new Container();
$di->getFactory('key');
}
public function testGetFactory()
{
$di = new Container();
$factory = function () {};
$di->foo = $factory;
$factory2 = $di->getFactory('foo');
$this->assertSame($factory, $factory2);
}
public function testGetKeys()
{
$di = new Container();
$di->foo = 'foo';
$di->bar = function () {};
$di->bar;
$this->assertEquals(array('foo', 'bar'), $di->getKeys());
}
}
class ContainerTestObject
{
public $calls;
public $args;
public function __construct()
{
$this->args = func_get_args();
}
public function __call($name, $args)
{
$this->calls[$name] = $args[0];
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Pimple.
*
* Copyright (c) 2009 Fabien Potencier
*
* 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.
*
* 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.
*/
namespace Props\Pimple\Tests;
class Invokable
{
public function __invoke($value = null)
{
$service = new Service();
$service->value = $value;
return $service;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Pimple.
*
* Copyright (c) 2009 Fabien Potencier
*
* 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.
*
* 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.
*/
namespace Props\Pimple\Tests;
class NonInvokable
{
public function __call($a, $b)
{
}
}

View File

@@ -0,0 +1,452 @@
<?php
/*
* This file is part of Pimple.
*
* Copyright (c) 2009 Fabien Potencier
*
* 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.
*
* 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.
*/
namespace Props\Pimple\Tests;
use Props\Pimple;
/**
* Pimple Test
*
* @package pimple
* @author Igor Wiedler <igor@wiedler.ch>
*/
class PimpleTest extends \PHPUnit_Framework_TestCase
{
public function testWithString()
{
$pimple = new Pimple();
$pimple->param = 'value';
$this->assertEquals('value', $pimple->param);
}
public function testWithClosure()
{
$pimple = new Pimple();
$pimple->service = function () {
return new Service();
};
$this->assertInstanceOf('Props\Pimple\Tests\Service', $pimple->service);
}
public function testServicesShouldBeDifferent()
{
$pimple = new Pimple();
$pimple->service = $pimple->factory(function () {
return new Service();
});
$serviceOne = $pimple->service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceOne);
$serviceTwo = $pimple->service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceTwo);
$this->assertNotSame($serviceOne, $serviceTwo);
}
public function testShouldPassContainerAsParameter()
{
$pimple = new Pimple();
$pimple->service = function () {
return new Service();
};
$pimple->container = function ($container) {
return $container;
};
$this->assertNotSame($pimple, $pimple->service);
$this->assertSame($pimple, $pimple->container);
}
public function testIsset()
{
$pimple = new Pimple();
$pimple->param = 'value';
$pimple->service = function () {
return new Service();
};
$pimple->null = null;
$this->assertTrue(isset($pimple->param));
$this->assertTrue(isset($pimple->service));
$this->assertTrue(isset($pimple->null));
$this->assertFalse(isset($pimple->non_existent));
}
public function testConstructorInjection()
{
$params = array("param" => "value");
$pimple = new Pimple($params);
$this->assertSame($pimple->param, $pimple->param);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Identifier "foo" is not defined.
*/
public function testOffsetGetValidatesKeyIsPresent()
{
$pimple = new Pimple();
echo $pimple->foo;
}
public function testOffsetGetHonorsNullValues()
{
$pimple = new Pimple();
$pimple->foo = null;
$this->assertNull($pimple->foo);
}
public function testUnset()
{
$pimple = new Pimple();
$pimple->param = 'value';
$pimple->service = function () {
return new Service();
};
unset($pimple->param, $pimple->service);
$this->assertFalse(isset($pimple->param));
$this->assertFalse(isset($pimple->service));
}
/**
* @dataProvider serviceDefinitionProvider
*/
public function testShare($service)
{
$pimple = new Pimple();
$pimple->shared_service = $service;
$serviceOne = $pimple->shared_service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceOne);
$serviceTwo = $pimple->shared_service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceTwo);
$this->assertSame($serviceOne, $serviceTwo);
}
/**
* @dataProvider serviceDefinitionProvider
*/
public function testProtect($service)
{
$pimple = new Pimple();
$pimple->protected = $pimple->protect($service);
$this->assertSame($service, $pimple->protected);
}
public function testGlobalFunctionNameAsParameterValue()
{
$pimple = new Pimple();
$pimple->global_function = 'strlen';
$this->assertSame('strlen', $pimple->global_function);
}
public function testRaw()
{
$pimple = new Pimple();
$pimple->service = $definition = $pimple->factory(function () { return 'foo'; });
$this->assertSame($definition, $pimple->raw('service'));
}
public function testRawHonorsNullValues()
{
$pimple = new Pimple();
$pimple->foo = null;
$this->assertNull($pimple->raw('foo'));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Identifier "foo" is not defined.
*/
public function testRawValidatesKeyIsPresent()
{
$pimple = new Pimple();
$pimple->raw('foo');
}
/**
* @dataProvider serviceDefinitionProvider
*/
public function testExtend($service)
{
$pimple = new Pimple();
$pimple->shared_service = function () {
return new Service();
};
$pimple->factory_service = $pimple->factory(function () {
return new Service();
});
$pimple->extend('shared_service', $service);
$serviceOne = $pimple->shared_service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceOne);
$serviceTwo = $pimple->shared_service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceTwo);
$this->assertSame($serviceOne, $serviceTwo);
$this->assertSame($serviceOne->value, $serviceTwo->value);
$pimple->extend('factory_service', $service);
$serviceOne = $pimple->factory_service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceOne);
$serviceTwo = $pimple->factory_service;
$this->assertInstanceOf('Props\Pimple\Tests\Service', $serviceTwo);
$this->assertNotSame($serviceOne, $serviceTwo);
$this->assertNotSame($serviceOne->value, $serviceTwo->value);
}
public function testExtendDoesNotLeakWithFactories()
{
$pimple = new Pimple();
$pimple->foo = $pimple->factory(function () { return; });
$pimple->foo = $pimple->extend('foo', function ($foo, $pimple) { return; });
unset($pimple->foo);
$class = new \ReflectionClass($pimple);
$class = $class->getParentClass();
$p = $class->getProperty('values');
$p->setAccessible(true);
$this->assertEmpty($p->getValue($pimple));
$p = $class->getProperty('factories');
$p->setAccessible(true);
$this->assertCount(0, $p->getValue($pimple));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Identifier "foo" is not defined.
*/
public function testExtendValidatesKeyIsPresent()
{
$pimple = new Pimple();
$pimple->extend('foo', function () {});
}
public function testKeys()
{
$pimple = new Pimple();
$pimple->foo = 123;
$pimple->bar = 123;
$this->assertEquals(array('foo', 'bar'), $pimple->keys());
}
/** @test */
public function settingAnInvokableObjectShouldTreatItAsFactory()
{
$pimple = new Pimple();
$pimple->invokable = new Invokable();
$this->assertInstanceOf('Props\Pimple\Tests\Service', $pimple->invokable);
}
/** @test */
public function settingNonInvokableObjectShouldTreatItAsParameter()
{
$pimple = new Pimple();
$pimple->non_invokable = new NonInvokable();
$this->assertInstanceOf('Props\Pimple\Tests\NonInvokable', $pimple->non_invokable);
}
/**
* @dataProvider badServiceDefinitionProvider
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Service definition is not a Closure or invokable object.
*/
public function testFactoryFailsForInvalidServiceDefinitions($service)
{
$pimple = new Pimple();
$pimple->factory($service);
}
/**
* @dataProvider badServiceDefinitionProvider
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Callable is not a Closure or invokable object.
*/
public function testProtectFailsForInvalidServiceDefinitions($service)
{
$pimple = new Pimple();
$pimple->protect($service);
}
/**
* @dataProvider badServiceDefinitionProvider
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Identifier "foo" does not contain an object definition.
*/
public function testExtendFailsForKeysNotContainingServiceDefinitions($service)
{
$pimple = new Pimple();
$pimple->foo = $service;
$pimple->extend('foo', function () {});
}
/**
* @dataProvider badServiceDefinitionProvider
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Extension service definition is not a Closure or invokable object.
*/
public function testExtendFailsForInvalidServiceDefinitions($service)
{
$pimple = new Pimple();
$pimple->foo = function () {};
$pimple->extend('foo', $service);
}
/**
* Provider for invalid service definitions
*/
public function badServiceDefinitionProvider()
{
return array(
array(123),
array(new NonInvokable())
);
}
/**
* Provider for service definitions
*/
public function serviceDefinitionProvider()
{
return array(
array(function ($value) {
$service = new Service();
$service->value = $value;
return $service;
}),
array(new Invokable())
);
}
public function testDefiningNewServiceAfterFreeze()
{
$pimple = new Pimple();
$pimple->foo = function () {
return 'foo';
};
$foo = $pimple->foo;
$pimple->bar = function () {
return 'bar';
};
$this->assertSame('bar', $pimple->bar);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot override frozen service "foo".
*/
public function testOverridingServiceAfterFreeze()
{
$pimple = new Pimple();
$pimple->foo = function () {
return 'foo';
};
$foo = $pimple->foo;
$pimple->foo = function () {
return 'bar';
};
}
public function testRemovingServiceAfterFreeze()
{
$pimple = new Pimple();
$pimple->foo = function () {
return 'foo';
};
$foo = $pimple->foo;
unset($pimple->foo);
$pimple->foo = function () {
return 'bar';
};
$this->assertSame('bar', $pimple->foo);
}
public function testExtendingService()
{
$pimple = new Pimple();
$pimple->foo = function () {
return 'foo';
};
$pimple->foo = $pimple->extend('foo', function ($foo, $app) {
return "$foo.bar";
});
$pimple->foo = $pimple->extend('foo', function ($foo, $app) {
return "$foo.baz";
});
$this->assertSame('foo.bar.baz', $pimple->foo);
}
public function testExtendingServiceAfterOtherServiceFreeze()
{
$pimple = new Pimple();
$pimple->foo = function () {
return 'foo';
};
$pimple->bar = function () {
return 'bar';
};
$foo = $pimple->foo;
$pimple->bar = $pimple->extend('bar', function ($bar, $app) {
return "$bar.baz";
});
$this->assertSame('bar.baz', $pimple->bar);
}
public function testNoPrivateAccess()
{
$check = new AccessCheck();
$this->assertTrue(isset($check['values']));
$this->assertEquals('values', $check['values']);
}
}
class AccessCheck extends Pimple {
function __construct()
{
$this->values = 'values';
parent::__construct();
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Pimple.
*
* Copyright (c) 2009 Fabien Potencier
*
* 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.
*
* 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.
*/
namespace Props\Pimple\Tests;
/**
* Pimple Test Service
*
* @package pimple
* @author Igor Wiedler <igor@wiedler.ch>
*/
class Service
{
public $value;
}