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
4 changes: 4 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
stopOnFailure="false"
stopOnError="false"
stderr="true"
failOnDeprecation="true"
failOnPhpunitDeprecation="true"
stopOnDeprecation="true"
displayDetailsOnPhpunitDeprecations="true"
displayDetailsOnIncompleteTests="true"
displayDetailsOnSkippedTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
Expand Down
2 changes: 1 addition & 1 deletion src/Internal/Declaration/Prototype/Prototype.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?php

/**
Expand Down Expand Up @@ -58,7 +58,7 @@
{
$reflection = $prototype->getClass();

return $reflection && $reflection->getName() === \trim($class, '\\');
return $reflection->getName() === \trim($class, '\\');
}

private static function matchMethod(PrototypeInterface $prototype, string $method): bool
Expand Down
7 changes: 0 additions & 7 deletions src/Internal/Declaration/Reader/ActivityReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,6 @@ private function getMethodGroups(ClassNode $graph, \ReflectionMethod $root): arr
$contextualRetry = $contextualRetry ? $retry->mergeWith($contextualRetry) : $retry;
}

//
// In the future, activity methods are available only in
// those classes that contain the attribute:
//
// - #[ActivityInterface]
// - #[LocalActivityInterface]
//
$interface = $this->reader->firstClassMetadata($ctx->getReflection(), ActivityInterface::class);

if ($interface === null) {
Expand Down
30 changes: 21 additions & 9 deletions src/Internal/Workflow/ActivityProxy.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?php

/**
Expand All @@ -12,6 +12,7 @@
namespace Temporal\Internal\Workflow;

use React\Promise\PromiseInterface;
use Temporal\Activity\ActivityMethod;
use Temporal\Activity\ActivityOptionsInterface;
use Temporal\Interceptor\WorkflowOutboundCalls\ExecuteActivityInput;
use Temporal\Interceptor\WorkflowOutboundCalls\ExecuteLocalActivityInput;
Expand Down Expand Up @@ -61,13 +62,24 @@
*/
public function __call(string $method, array $args = []): PromiseInterface
{
$handler = $this->findPrototypeByHandlerNameOrFail($method);
$type = $handler->getHandler()->getReturnType();
$options = $this->options->mergeWith($handler->getMethodRetry());
$prototype = $this->findPrototypeByHandlerNameOrFail($method);
$type = $prototype->getHandler()->getReturnType();
$options = $this->options->mergeWith($prototype->getMethodRetry());

$args = Reflection::orderArguments($handler->getHandler(), $args);
$args = Reflection::orderArguments($prototype->getHandler(), $args);

return $handler->isLocalActivity()
if (!$prototype->getHandler()->getAttributes(ActivityMethod::class, \ReflectionAttribute::IS_INSTANCEOF)) {
\trigger_error(
\sprintf(
'Using implicit activity methods is deprecated. Explicitly mark activity method %s with #[%s] attribute instead.',
$prototype->getHandler()->getDeclaringClass()->getName() . '::' . $method,
ActivityMethod::class,
),
\E_USER_DEPRECATED,
);
}

return $prototype->isLocalActivity()
// Run local activity through an interceptor pipeline
? $this->callsInterceptor->with(
fn(ExecuteLocalActivityInput $input): PromiseInterface => $this->ctx
Expand All @@ -77,11 +89,11 @@
'executeLocalActivity',
)(
new ExecuteLocalActivityInput(
$handler->getID(),
$prototype->getID(),

Check failure on line 92 in src/Internal/Workflow/ActivityProxy.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.3, OS ubuntu-latest)

ArgumentTypeCoercion

src/Internal/Workflow/ActivityProxy.php:92:21: ArgumentTypeCoercion: Argument 1 of Temporal\Interceptor\WorkflowOutboundCalls\ExecuteLocalActivityInput::__construct expects non-empty-string, but parent type string provided (see https://psalm.dev/193)

Check failure on line 92 in src/Internal/Workflow/ActivityProxy.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.3, OS ubuntu-latest)

ArgumentTypeCoercion

src/Internal/Workflow/ActivityProxy.php:92:21: ArgumentTypeCoercion: Argument 1 of Temporal\Interceptor\WorkflowOutboundCalls\ExecuteLocalActivityInput::__construct expects non-empty-string, but parent type string provided (see https://psalm.dev/193)
$args,
$options,
$type,
$handler->getHandler(),
$prototype->getHandler(),
)
)

Expand All @@ -94,11 +106,11 @@
'executeActivity',
)(
new ExecuteActivityInput(
$handler->getID(),
$prototype->getID(),

Check failure on line 109 in src/Internal/Workflow/ActivityProxy.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.3, OS ubuntu-latest)

ArgumentTypeCoercion

src/Internal/Workflow/ActivityProxy.php:109:21: ArgumentTypeCoercion: Argument 1 of Temporal\Interceptor\WorkflowOutboundCalls\ExecuteActivityInput::__construct expects non-empty-string, but parent type string provided (see https://psalm.dev/193)

Check failure on line 109 in src/Internal/Workflow/ActivityProxy.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.3, OS ubuntu-latest)

ArgumentTypeCoercion

src/Internal/Workflow/ActivityProxy.php:109:21: ArgumentTypeCoercion: Argument 1 of Temporal\Interceptor\WorkflowOutboundCalls\ExecuteActivityInput::__construct expects non-empty-string, but parent type string provided (see https://psalm.dev/193)
$args,
$options,
$type,
$handler->getHandler(),
$prototype->getHandler(),
)
);
}
Expand Down
33 changes: 33 additions & 0 deletions testing/src/DeprecationCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Temporal\Testing;

class DeprecationCollector
{
/** @var DeprecationMessage[] */
private static array $deprecations = [];

public static function register(): void
{
\set_error_handler([self::class, 'handle'], E_USER_DEPRECATED);
}

public static function handle(int $errno, string $message, string $file, int $line): bool
{
if ($errno !== E_USER_DEPRECATED) {
return false;
}

$trace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

self::$deprecations[] = new DeprecationMessage($message, $file, $line, $trace);
return true;
}

public static function getAll(): array
{
return self::$deprecations;
}
}
15 changes: 15 additions & 0 deletions testing/src/DeprecationMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Temporal\Testing;

class DeprecationMessage
{
public function __construct(
public readonly string $message,
public readonly string $file,
public readonly int $line,
public readonly array $trace,
) {}
}
3 changes: 3 additions & 0 deletions tests/Acceptance/App/RuntimeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPUnit\Framework\Attributes\Test;
use Temporal\Activity\ActivityInterface;
use Temporal\DataConverter\PayloadConverterInterface;
use Temporal\Testing\DeprecationCollector;
use Temporal\Testing\Command;
use Temporal\Tests\Acceptance\App\Input\Feature;
use Temporal\Tests\Acceptance\App\Runtime\State;
Expand Down Expand Up @@ -69,6 +70,8 @@ public static function createState(Command $command, string $workDir, iterable $
public static function init(): void
{
\ini_set('display_errors', 'stderr');
error_reporting(-1);
DeprecationCollector::register();
// Feature flags
FeatureFlags::$workflowDeferredHandlerStart = true;
FeatureFlags::$cancelAbandonedChildWorkflows = false;
Expand Down
93 changes: 93 additions & 0 deletions tests/Acceptance/Extra/Activity/ActivityMethodTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Temporal\Tests\Acceptance\Extra\Activity\ActivityMethod;

use Temporal\Activity;
use Temporal\Activity\ActivityMethod;
use Temporal\Client\WorkflowStubInterface;
use Temporal\Exception\Client\WorkflowFailedException;
use Temporal\Testing\DeprecationCollector;
use Temporal\Tests\Acceptance\App\Attribute\Stub;
use Temporal\Tests\Acceptance\App\TestCase;
use Temporal\Workflow;
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

class ActivityMethodTest extends TestCase
{
public function testMethodWithAttribute(
#[Stub('Extra_Activity_ActivityMethod', args: ['method' => 'withAttribute'])]
WorkflowStubInterface $stub,
): void {
$result = $stub->getResult('array');
self::assertEquals(1, $result['result']);
self::assertCount(0, $result['deprecations'], \print_r($result['deprecations'], true));
}

public function testMethodWithoutAttribute(
#[Stub('Extra_Activity_ActivityMethod', args: ['method' => 'withoutAttribute'])]
WorkflowStubInterface $stub,
): void {
$result = $stub->getResult('array');
self::assertEquals(2, $result['result']);
self::assertCount(1, $result['deprecations']);
self::assertEquals(
\sprintf(
'Using implicit activity methods is deprecated. Explicitly mark activity method %s with #[%s] attribute instead.',
TestActivity::class . '::withoutAttribute',
ActivityMethod::class,
),
$result['deprecations'][0]['message'],
);
}

public function testMagicMethodIsIgnored(
#[Stub('Extra_Activity_ActivityMethod', args: ['method' => '__invoke'])]
WorkflowStubInterface $stub,
): void {
$this->expectException(WorkflowFailedException::class);
$stub->getResult(type: 'int');
}
}


#[WorkflowInterface]
class TestWorkflow
{
#[WorkflowMethod(name: "Extra_Activity_ActivityMethod")]
public function handle(string $method)
{
$activityStub = Workflow::newActivityStub(
TestActivity::class,
Activity\ActivityOptions::new()->withScheduleToCloseTimeout(10),
);
$result = yield $activityStub->{$method}();

return [
'result' => $result,
'deprecations' => DeprecationCollector::getAll(),
];
}
}

#[Activity\ActivityInterface(prefix: 'Extra_Activity_ActivityMethod.')]
class TestActivity
{
#[ActivityMethod]
public function withAttribute()
{
return 1;
}

public function withoutAttribute()
{
return 2;
}

public function __invoke()
{
return 3;
}
}
Loading