Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add handling of result from action* and handle* #228

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"mockery/mockery": "^1.0"
},
"conflict": {
"nette/bootstrap": "<3.0.1",
"nette/di": "<3.0-stable",
"nette/forms": "<3.0",
"nette/latte": "<3.0"
Expand Down
74 changes: 57 additions & 17 deletions src/Application/UI/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* @property-read Presenter $presenter
* @property-read bool $linkCurrent
*/
abstract class Component extends Nette\ComponentModel\Container implements ISignalReceiver, IStatePersistent, \ArrayAccess
abstract class Component extends Nette\ComponentModel\Container implements ISignalReceiverWithResult, IStatePersistent, \ArrayAccess
{
use Nette\ComponentModel\ArrayAccess;

Expand All @@ -32,6 +32,9 @@ abstract class Component extends Nette\ComponentModel\Container implements ISign
/** @var array */
protected $params = [];

/** @var HandleResult */
private $signalResult;


/**
* Returns the presenter where this component belongs to.
Expand Down Expand Up @@ -77,25 +80,37 @@ protected function validateParent(Nette\ComponentModel\IContainer $parent): void

/**
* Calls public method if exists.
* @return bool does method exist?
*
* @param string $method name of the method to be called
* @param array $params parameters for the method
*
* @return HandleResult result of the method if it exists
*/
protected function tryCall(string $method, array $params): bool
protected function tryCall(string $method, array $params): ?HandleResult
{
$rc = $this->getReflection();
if ($rc->hasMethod($method)) {
$rm = $rc->getMethod($method);
if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
$this->checkRequirements($rm);
try {
$args = $rc->combineArgs($rm, $params);
} catch (Nette\InvalidArgumentException $e) {
throw new Nette\Application\BadRequestException($e->getMessage());
}
$rm->invokeArgs($this, $args);
return true;
}
if (!$rc->hasMethod($method)) {
return null;
}
return false;

$rm = $rc->getMethod($method);
if (!$rm->isPublic() || $rm->isAbstract() || $rm->isStatic()) {
return null;
}

$this->checkRequirements($rm);
try {
$args = $rc->combineArgs($rm, $params);
} catch (Nette\InvalidArgumentException $e) {
throw new Nette\Application\BadRequestException($e->getMessage());
}

$result = $rm->invokeArgs($this, $args);
if ($result) {
return new HandleResult($result, get_class($this) . '::' . $method);
}

return null;
}


Expand Down Expand Up @@ -205,14 +220,39 @@ final public function getParam($name = null, $default = null)

/**
* Calls signal handler method.
*
* @return HandleResult - result of the signal handler
*
* @throws BadSignalException if there is not handler method
*/
public function signalReceived(string $signal): void
{
if (!$this->tryCall($this->formatSignalMethod($signal), $this->params)) {
$signalMethod = $this->formatSignalMethod($signal);
if (!method_exists($this, $signalMethod)) {
$class = get_class($this);
throw new BadSignalException("There is no handler for signal '$signal' in class $class.");
}

$result = $this->tryCall($signalMethod, $this->params);
$this->setSignalResult($result, get_class($this) . "::$signalMethod");
}

private function setSignalResult($value, $origin)
{
if ($value instanceof HandleResult) {
$result = $value;
} elseif ($value) {
$result = new HandleResult($value, $origin);
} else {
$result = null;
}

$this->signalResult = $result;
}

public function getSignalResult(): ?HandleResult
{
return $this->signalResult;
}


Expand Down
51 changes: 51 additions & 0 deletions src/Application/UI/HandleResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Application\UI;

/**
* Wrapper class of a value and origin pair.
*
* Used for action and signal result handling.
*
* @package Nette\Application\UI
*/
class HandleResult
{
/** @var mixed */
private $value;

/** @var string */
private $origin;

/**
* HandleResult constructor.
*
* @param mixed $value
* @param string $origin
*/
public function __construct($value, string $origin)
{
$this->value = $value;
$this->origin = $origin;
}

/** @return mixed */
public function getValue()
{
return $this->value;
}

/** @return string */
public function getOrigin(): string
{
return $this->origin;
}

}
15 changes: 15 additions & 0 deletions src/Application/UI/ISignalReceiverWithResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Application\UI;

interface ISignalReceiverWithResult extends ISignalReceiver
{
public function getSignalResult(): ?HandleResult;
}
38 changes: 34 additions & 4 deletions src/Application/UI/Presenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ public function run(Application\Request $request): Application\IResponse
throw new Nette\InvalidStateException("Method $class::startup() or its descendant doesn't call parent::startup().");
}
// calls $this->action<Action>()
$this->tryCall(static::formatActionMethod($this->action), $this->params);
$this->handleActionResult($this->tryCall(static::formatActionMethod($this->action), $this->params));

// autoload components
foreach ($this->globalParams as $id => $foo) {
Expand All @@ -212,7 +212,7 @@ public function run(Application\Request $request): Application\IResponse

// SIGNAL HANDLING
// calls $this->handle<Signal>()
$this->processSignal();
$this->handleActionResult($this->processSignal());

// RENDERING VIEW
$this->beforeRender();
Expand Down Expand Up @@ -271,6 +271,34 @@ protected function startup()
$this->startupCheck = true;
}

/**
* @param HandleResult $result - result of action or signal
* @throws Application\AbortException
*/
protected function handleActionResult(HandleResult $result = null): void
{
if (!$result) {
return;
}

$response = null;

$value = $result->getValue();

if ($value instanceof Application\IResponse) {
$response = $value;
} elseif (is_array($value) || is_scalar($value)) {
$response = new Responses\JsonResponse($value);
} else {
$origin = $result->getOrigin();
$type = is_object($value) ? get_class($value) : gettype($value);
throw new Nette\InvalidArgumentException("Result of $origin must be application response" .
", array or scalar. Got $type.");
}

$this->sendResponse($response);
}


/**
* Common render method.
Expand Down Expand Up @@ -329,10 +357,10 @@ public function detectedCsrf(): void
/**
* @throws BadSignalException
*/
public function processSignal(): void
public function processSignal(): ?HandleResult
{
if ($this->signal === null) {
return;
return null;
}

$component = $this->signalReceiver === '' ? $this : $this->getComponent($this->signalReceiver, false);
Expand All @@ -345,6 +373,8 @@ public function processSignal(): void

$component->signalReceived($this->signal);
$this->signal = null;

return $component instanceof ISignalReceiverWithResult ? $component->getSignalResult() : null;
}


Expand Down
30 changes: 11 additions & 19 deletions src/Bridges/ApplicationDI/ApplicationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ final class ApplicationExtension extends Nette\DI\CompilerExtension
/** @var bool */
private $debugMode;

/** @var array */
private $scanDirs;
/** @var Nette\Loaders\RobotLoader|null */
private $robotLoader;

/** @var int */
private $invalidLinkMode;
Expand All @@ -35,10 +35,10 @@ final class ApplicationExtension extends Nette\DI\CompilerExtension
private $tempDir;


public function __construct(bool $debugMode = false, array $scanDirs = null, string $tempDir = null)
public function __construct(bool $debugMode = false, Nette\Loaders\RobotLoader $robotLoader = null, string $tempDir = null)
{
$this->debugMode = $debugMode;
$this->scanDirs = (array) $scanDirs;
$this->robotLoader = $robotLoader;
$this->tempDir = $tempDir;
}

Expand All @@ -50,7 +50,7 @@ public function getConfigSchema(): Nette\Schema\Schema
'errorPresenter' => Expect::string('Nette:Error')->dynamic(),
'catchExceptions' => Expect::bool(!$this->debugMode)->dynamic(),
'mapping' => Expect::arrayOf('string|array'),
'scanDirs' => Expect::anyOf(Expect::arrayOf('string'), false)->default($this->scanDirs),
'scanDirs' => Expect::anyOf(Expect::arrayOf('string'), false), // deprecated
'scanComposer' => Expect::bool(class_exists(ClassLoader::class)),
'scanFilter' => Expect::string('Presenter'),
'silentLinks' => Expect::bool(),
Expand Down Expand Up @@ -78,7 +78,7 @@ public function loadConfiguration()
}
$this->compiler->addExportedType(Nette\Application\Application::class);

$touch = $this->debugMode && $config->scanDirs && $this->tempDir ? $this->tempDir . '/touch' : null;
$touch = $this->debugMode && $this->robotLoader && $this->tempDir ? $this->tempDir . '/touch' : null;
$presenterFactory = $builder->addDefinition($this->prefix('presenterFactory'))
->setType(Nette\Application\IPresenterFactory::class)
->setFactory(Nette\Application\PresenterFactory::class, [new Definitions\Statement(
Expand Down Expand Up @@ -136,19 +136,11 @@ private function findPresenters(): array
$classes = [];

if ($config->scanDirs) {
if (!class_exists(Nette\Loaders\RobotLoader::class)) {
throw new Nette\NotSupportedException("RobotLoader is required to find presenters, install package `nette/robot-loader` or disable option {$this->prefix('scanDirs')}: false");
}
$robot = new Nette\Loaders\RobotLoader;
$robot->addDirectory(...$config->scanDirs);
$robot->acceptFiles = ['*' . $config->scanFilter . '*.php'];
if ($this->tempDir) {
$robot->setTempDirectory($this->tempDir);
$robot->refresh();
} else {
$robot->rebuild();
}
$classes = array_keys($robot->getIndexedClasses());
trigger_error("Option 'scanDir' has no effect and is deprecated.", E_USER_DEPRECATED);
}

if ($this->robotLoader) {
$classes = array_keys($this->robotLoader->getIndexedClasses());
$this->getContainerBuilder()->addDependency($this->tempDir . '/touch');
}

Expand Down