Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions fixtures/ReadOnlyClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@

readonly class ReadOnlyClass
{
public int $foo;

public function __construct()
{
$this->foo = 1;
}
}
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.1/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="vendor/autoload.php" cacheDirectory=".phpunit.cache">
<php>
<ini name="error_reporting" value="-1"/>
<ini name="display_errors" value="1" />
</php>
<testsuites>
<testsuite name="PhpSpec Test Suite">
Expand Down
10 changes: 8 additions & 2 deletions spec/Prophecy/Argument/ArgumentsWildcardSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ function it_wraps_non_token_arguments_into_ExactValueToken(\stdClass $object)
$class = get_class($object->getWrappedObject());
$id = spl_object_id($object->getWrappedObject());

$objHash = "exact(42), exact(\"zet\"), exact($class#$id Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n))";
$objHash = "exact(42), exact(\"zet\"), exact($class#$id Object (\n"
." 'objectProphecyClosureContainer' => Prophecy\Doubler\ObjectProphecyClosureContainer#%s Object (\n"
." 'closure' => Closure#%s Object (\n"
." 0 => Closure#%s Object\n"
." )\n"
." )\n"
."))";

$idRegexExpr = '[0-9]+';
$this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr)));
$this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr, $idRegexExpr)));
}

function it_generates_string_representation_from_all_tokens_imploded(
Expand Down
10 changes: 8 additions & 2 deletions spec/Prophecy/Argument/Token/ExactValueTokenSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,18 @@ function it_generates_proper_string_representation_for_object(\stdClass $object)
$objHash = sprintf('exact(%s#%s',
get_class($object->getWrappedObject()),
spl_object_id($object->getWrappedObject())
)." Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n))";
)." Object (\n"
." 'objectProphecyClosureContainer' => Prophecy\Doubler\ObjectProphecyClosureContainer#%s Object (\n"
." 'closure' => Closure#%s Object (\n"
." 0 => Closure#%s Object\n"
." )\n"
." )\n"
."))";

$this->beConstructedWith($object);

$idRegexExpr = '[0-9]+';
$this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr)));
$this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr, $idRegexExpr)));
}

function it_scores_10_if_value_an_numeric_and_equal_to_argument_as_stringable()
Expand Down
10 changes: 8 additions & 2 deletions spec/Prophecy/Argument/Token/IdenticalValueTokenSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,17 @@ function it_generates_proper_string_representation_for_object($object)
$objHash = sprintf('identical(%s#%s',
get_class($object->getWrappedObject()),
spl_object_id($object->getWrappedObject())
)." Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n))";
)." Object (\n"
." 'objectProphecyClosureContainer' => Prophecy\Doubler\ObjectProphecyClosureContainer#%s Object (\n"
." 'closure' => Closure#%s Object (\n"
." 0 => Closure#%s Object\n"
." )\n"
." )\n"
."))";

$this->beConstructedWith($object);

$idRegexExpr = '[0-9]+';
$this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr)));
$this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr, $idRegexExpr)));
}
}
19 changes: 17 additions & 2 deletions spec/Prophecy/Doubler/ClassPatch/ProphecySubjectPatchSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Prophecy\Argument;
use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\Doubler\Generator\Node\PropertyNode;
use Prophecy\Doubler\Generator\Node\PropertyTypeNode;
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
use Prophecy\Doubler\Generator\Node\Type\BuiltinType;

Expand All @@ -29,8 +31,14 @@ function it_supports_any_class(ClassNode $node)
function it_forces_class_to_implement_ProphecySubjectInterface(ClassNode $node)
{
$node->addInterface('Prophecy\Prophecy\ProphecySubjectInterface')->shouldBeCalled();
$node->addProperty(
new PropertyNode(
'objectProphecyClosureContainer',
'private',
new PropertyTypeNode('Prophecy\Doubler\ObjectProphecyClosureContainer')
)
);

$node->addProperty('objectProphecyClosure', 'private')->willReturn(null);
$node->getMethods()->willReturn(array());
$node->hasMethod(Argument::any())->willReturn(false);
$node->addMethod(Argument::type('Prophecy\Doubler\Generator\Node\MethodNode'), true)->willReturn(null);
Expand All @@ -48,7 +56,14 @@ function it_forces_all_class_methods_except_constructor_to_proxy_calls_into_prop
MethodNode $method4
) {
$node->addInterface('Prophecy\Prophecy\ProphecySubjectInterface')->willReturn(null);
$node->addProperty('objectProphecyClosure', 'private')->willReturn(null);
$node->addProperty(
new PropertyNode(
'objectProphecyClosureContainer',
'private',
new PropertyTypeNode('Prophecy\Doubler\ObjectProphecyClosureContainer')
)
);

$node->hasMethod(Argument::any())->willReturn(false);
$node->addMethod(Argument::type('Prophecy\Doubler\Generator\Node\MethodNode'), true)->willReturn(null);
$node->addMethod(Argument::type('Prophecy\Doubler\Generator\Node\MethodNode'), true)->willReturn(null);
Expand Down
25 changes: 15 additions & 10 deletions spec/Prophecy/Doubler/Generator/ClassCodeGeneratorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Prophecy\Doubler\Generator\Node\ArgumentTypeNode;
use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\Doubler\Generator\Node\PropertyNode;
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
use Prophecy\Doubler\Generator\Node\Type\BuiltinType;
use Prophecy\Doubler\Generator\Node\Type\IntersectionType;
Expand All @@ -35,7 +36,11 @@ function it_generates_proper_php_code_for_specific_ClassNode(
$class->getInterfaces()->willReturn(array(
'Prophecy\Doubler\Generator\MirroredInterface', 'ArrayAccess', 'ArrayIterator',
));
$class->getProperties()->willReturn(array('name' => 'public', 'email' => 'private'));
$name = new PropertyNode('name');
$name->setVisibility('public');
$email = new PropertyNode('email');
$email->setVisibility('private');
$class->getPropertyNodes()->willReturn(array('name' => $name, 'email' => $email));
$class->getMethods()->willReturn(array($method1, $method2, $method3, $method4, $method5));
$class->isReadOnly()->willReturn(false);

Expand Down Expand Up @@ -163,7 +168,7 @@ function it_generates_proper_php_code_for_variadics(
) {
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getPropertyNodes()->willReturn(array());
$class->getMethods()->willReturn(array(
$method1, $method2, $method3, $method4,
));
Expand Down Expand Up @@ -258,7 +263,7 @@ function it_overrides_properly_methods_with_args_passed_by_reference(
) {
$class->getParentClass()->willReturn('RuntimeException');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getPropertyNodes()->willReturn(array());
$class->getMethods()->willReturn(array($method));
$class->isReadOnly()->willReturn(false);

Expand Down Expand Up @@ -302,7 +307,7 @@ function it_generates_proper_code_for_union_return_types(
) {
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn([]);
$class->getProperties()->willReturn([]);
$class->getPropertyNodes()->willReturn([]);
$class->getMethods()->willReturn(array($method));
$class->isReadOnly()->willReturn(false);

Expand Down Expand Up @@ -342,7 +347,7 @@ function it_generates_proper_code_for_dnf_types(
) {
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn([]);
$class->getProperties()->willReturn([]);
$class->getPropertyNodes()->willReturn([]);
$class->getMethods()->willReturn(array($method));
$class->isReadOnly()->willReturn(false);

Expand Down Expand Up @@ -384,7 +389,7 @@ function it_generates_proper_code_for_intersection_return_types(
) {
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn([]);
$class->getProperties()->willReturn([]);
$class->getPropertyNodes()->willReturn([]);
$class->getMethods()->willReturn(array($method));
$class->isReadOnly()->willReturn(false);

Expand Down Expand Up @@ -423,7 +428,7 @@ function it_generates_proper_code_for_union_argument_types(
) {
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn([]);
$class->getProperties()->willReturn([]);
$class->getPropertyNodes()->willReturn([]);
$class->getMethods()->willReturn(array($method));
$class->isReadOnly()->willReturn(false);

Expand Down Expand Up @@ -467,7 +472,7 @@ function it_generates_empty_class_for_empty_ClassNode(ClassNode $class)
{
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getPropertyNodes()->willReturn(array());
$class->getMethods()->willReturn(array());
$class->isReadOnly()->willReturn(false);

Expand All @@ -488,7 +493,7 @@ function it_wraps_class_in_namespace_if_it_is_namespaced(ClassNode $class)
{
$class->getParentClass()->willReturn('stdClass');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getPropertyNodes()->willReturn(array());
$class->getMethods()->willReturn(array());
$class->isReadOnly()->willReturn(false);

Expand All @@ -509,7 +514,7 @@ function it_generates_read_only_class_if_parent_class_is_read_only(ClassNode $cl
{
$class->getParentClass()->willReturn('ReadOnlyClass');
$class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface'));
$class->getProperties()->willReturn(array());
$class->getPropertyNodes()->willReturn(array());
$class->getMethods()->willReturn(array());
$class->isReadOnly()->willReturn(true);

Expand Down
12 changes: 11 additions & 1 deletion spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpSpec\ObjectBehavior;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\Doubler\Generator\Node\PropertyNode;
use Prophecy\Exception\Doubler\MethodNotExtendableException;

class ClassNodeSpec extends ObjectBehavior
Expand Down Expand Up @@ -120,7 +121,7 @@ function it_does_not_have_properties_by_default()
$this->getProperties()->shouldHaveCount(0);
}

function it_is_able_to_have_properties()
function it_is_able_to_have_properties_deprecated_api()
{
$this->addProperty('title');
$this->addProperty('text', 'private');
Expand All @@ -130,6 +131,15 @@ function it_is_able_to_have_properties()
));
}

function it_is_able_to_have_properties()
{
$prop1 = new PropertyNode('title');
$prop2 = new PropertyNode('text', 'private');
$this->addProperty($prop1);
$this->addProperty($prop2);
$this->getPropertyNodes()->shouldReturn(['title' => $prop1, 'text' => $prop2]);
}

function its_addProperty_does_not_accept_unsupported_visibility()
{
$this->shouldThrow('InvalidArgumentException')->duringAddProperty('title', 'town');
Expand Down
10 changes: 8 additions & 2 deletions spec/Prophecy/Util/StringUtilSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,16 @@ function it_generates_proper_string_representation_for_object(\stdClass $object)
$objHash = sprintf('%s#%s',
get_class($object->getWrappedObject()),
spl_object_id($object->getWrappedObject())
)." Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n)";
)." Object (\n"
." 'objectProphecyClosureContainer' => Prophecy\Doubler\ObjectProphecyClosureContainer#%s Object (\n"
." 'closure' => Closure#%s Object (\n"
." 0 => Closure#%s Object\n"
." )\n"
." )\n"
.")";

$idRegexExpr = '[0-9]+';
$this->stringify($object)->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr)));
$this->stringify($object)->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr, $idRegexExpr)));
}

function it_generates_proper_string_representation_for_object_without_exporting(\stdClass $object)
Expand Down
24 changes: 18 additions & 6 deletions src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ArgumentNode;
use Prophecy\Doubler\Generator\Node\ArgumentTypeNode;
use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\Doubler\Generator\Node\ArgumentNode;
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
use Prophecy\Doubler\Generator\Node\PropertyNode;
use Prophecy\Doubler\Generator\Node\PropertyTypeNode;
use Prophecy\Doubler\Generator\Node\Type\ObjectType;

/**
Expand Down Expand Up @@ -46,10 +47,21 @@ public function supports(ClassNode $node)
public function apply(ClassNode $node)
{
$node->addInterface('Prophecy\Prophecy\ProphecySubjectInterface');
$node->addProperty('objectProphecyClosure', 'private');
$node->addProperty(
new PropertyNode(
'objectProphecyClosureContainer',
'private',
new PropertyTypeNode('Prophecy\Doubler\ObjectProphecyClosureContainer')
)
);

foreach ($node->getMethods() as $name => $method) {
if ('__construct' === strtolower($name)) {
$method->setCode(
'$this->objectProphecyClosureContainer = new \Prophecy\Doubler\ObjectProphecyClosureContainer();'
.$method->getCode()
);

continue;
}

Expand All @@ -69,16 +81,16 @@ public function apply(ClassNode $node)
$prophecyArgument->setTypeNode(new ArgumentTypeNode(new ObjectType('Prophecy\Prophecy\ProphecyInterface')));
$prophecySetter->addArgument($prophecyArgument);
$prophecySetter->setCode(<<<PHP
if (null === \$this->objectProphecyClosure) {
\$this->objectProphecyClosure = static function () use (\$prophecy) {
if (null === \$this->objectProphecyClosureContainer->closure) {
\$this->objectProphecyClosureContainer->closure = static function () use (\$prophecy) {
return \$prophecy;
};
}
PHP
);

$prophecyGetter = new MethodNode('getProphecy');
$prophecyGetter->setCode('return \call_user_func($this->objectProphecyClosure);');
$prophecyGetter->setCode('return \call_user_func($this->objectProphecyClosureContainer->closure);');

if ($node->hasMethod('__call')) {
$__call = $node->getMethod('__call');
Expand Down
17 changes: 15 additions & 2 deletions src/Prophecy/Doubler/Generator/ClassCodeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public function generate($classname, Node\ClassNode $class)
)
);

foreach ($class->getProperties() as $name => $visibility) {
$code .= sprintf("%s \$%s;\n", $visibility, $name);
foreach ($class->getPropertyNodes() as $propertyNode) {
$code .= $this->generateProperty($propertyNode)."\n";
}
$code .= "\n";

Expand All @@ -67,6 +67,19 @@ public function generate($classname, Node\ClassNode $class)
return sprintf("namespace %s {\n%s\n}", $namespace, $code);
}

private function generateProperty(Node\PropertyNode $property): string
{
$type = ($type = $this->generateTypes($property->getTypeNode())) ? $type.' ' : '';

$php = sprintf("%s %s%s;",
$property->getVisibility(),
$type,
'$'.$property->getName()
);

return $php;
}

private function generateMethod(Node\MethodNode $method): string
{
$php = sprintf("%s %s function %s%s(%s)%s {\n",
Expand Down
Loading