no message
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "nette/application",
|
||||
"description": "🏆 Nette Application: a full-stack component-based MVC kernel for PHP that helps you write powerful and modern web applications. Write less, have cleaner code and your work will bring you joy.",
|
||||
"keywords": ["nette", "mvc", "framework", "component-based", "routing", "seo", "mvp", "presenter", "control", "forms"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"nette/component-model": "^2.3",
|
||||
"nette/http": "^2.2",
|
||||
"nette/reflection": "^2.2",
|
||||
"nette/utils": "^2.4 || ~3.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"nette/forms": "Allows to use Nette\\Application\\UI\\Form",
|
||||
"latte/latte": "Allows using Latte in templates"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"nette/di": "^2.4",
|
||||
"nette/forms": "^2.4",
|
||||
"nette/robot-loader": "^2.4.2 || ^3.0",
|
||||
"nette/security": "^2.4",
|
||||
"latte/latte": "^2.4.3",
|
||||
"tracy/tracy": "^2.4",
|
||||
"mockery/mockery": "^0.9.5"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/nette": "<2.2",
|
||||
"nette/di": "<2.4",
|
||||
"nette/forms": "<2.4",
|
||||
"nette/latte": "<2.4"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"],
|
||||
"files": ["src/compatibility.php"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
How to contribute & use the issue tracker
|
||||
=========================================
|
||||
|
||||
Nette welcomes your contributions. There are several ways to help out:
|
||||
|
||||
* Create an issue on GitHub, if you have found a bug
|
||||
* Write test cases for open bug issues
|
||||
* Write fixes for open bug/feature issues, preferably with test cases included
|
||||
* Contribute to the [documentation](https://nette.org/en/writing)
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Please **do not use the issue tracker to ask questions**. We will be happy to help you
|
||||
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
**Feature requests** are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
|
||||
|
||||
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
|
||||
|
||||
Please do not fix whitespace, format code, or make a purely cosmetic patch.
|
||||
|
||||
Thanks! :heart:
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
Nette Application MVC
|
||||
=====================
|
||||
|
||||
[](https://packagist.org/packages/nette/application)
|
||||
[](https://travis-ci.org/nette/application)
|
||||
[](https://coveralls.io/github/nette/application?branch=master)
|
||||
[](https://github.com/nette/application/releases)
|
||||
[](https://github.com/nette/application/blob/master/license.md)
|
||||
|
||||
Model-View-Controller is a software architecture that was created to satisfy the need to separate utility code (controller) from application logic code (model) and from code for displaying data (view) in applications with graphical user interface. With this approach we make the application better understandable, simplify future development and enable testing each unit of the application separately.
|
||||
|
||||
Please, [see documentation](https://doc.nette.org/en/presenters).
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Front Controller.
|
||||
*/
|
||||
class Application
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
public static $maxLoop = 20;
|
||||
|
||||
/** @var bool enable fault barrier? */
|
||||
public $catchExceptions;
|
||||
|
||||
/** @var string|null */
|
||||
public $errorPresenter;
|
||||
|
||||
/** @var callable[] function (Application $sender); Occurs before the application loads presenter */
|
||||
public $onStartup;
|
||||
|
||||
/** @var callable[] function (Application $sender, \Exception|\Throwable $e = null); Occurs before the application shuts down */
|
||||
public $onShutdown;
|
||||
|
||||
/** @var callable[] function (Application $sender, Request $request); Occurs when a new request is received */
|
||||
public $onRequest;
|
||||
|
||||
/** @var callable[] function (Application $sender, Presenter $presenter); Occurs when a presenter is created */
|
||||
public $onPresenter;
|
||||
|
||||
/** @var callable[] function (Application $sender, IResponse $response); Occurs when a new response is ready for dispatch */
|
||||
public $onResponse;
|
||||
|
||||
/** @var callable[] function (Application $sender, \Exception|\Throwable $e); Occurs when an unhandled exception occurs in the application */
|
||||
public $onError;
|
||||
|
||||
/** @var Request[] */
|
||||
private $requests = [];
|
||||
|
||||
/** @var IPresenter|null */
|
||||
private $presenter;
|
||||
|
||||
/** @var Nette\Http\IRequest */
|
||||
private $httpRequest;
|
||||
|
||||
/** @var Nette\Http\IResponse */
|
||||
private $httpResponse;
|
||||
|
||||
/** @var IPresenterFactory */
|
||||
private $presenterFactory;
|
||||
|
||||
/** @var IRouter */
|
||||
private $router;
|
||||
|
||||
|
||||
public function __construct(IPresenterFactory $presenterFactory, IRouter $router, Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
|
||||
{
|
||||
$this->httpRequest = $httpRequest;
|
||||
$this->httpResponse = $httpResponse;
|
||||
$this->presenterFactory = $presenterFactory;
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dispatch a HTTP request to a front controller.
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
try {
|
||||
$this->onStartup($this);
|
||||
$this->processRequest($this->createInitialRequest());
|
||||
$this->onShutdown($this);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
if (isset($e)) {
|
||||
$this->onError($this, $e);
|
||||
if ($this->catchExceptions && $this->errorPresenter) {
|
||||
try {
|
||||
$this->processException($e);
|
||||
$this->onShutdown($this, $e);
|
||||
return;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->onError($this, $e);
|
||||
} catch (\Throwable $e) {
|
||||
$this->onError($this, $e);
|
||||
}
|
||||
}
|
||||
$this->onShutdown($this, $e);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Request
|
||||
*/
|
||||
public function createInitialRequest()
|
||||
{
|
||||
$request = $this->router->match($this->httpRequest);
|
||||
if (!$request instanceof Request) {
|
||||
throw new BadRequestException('No route for HTTP request.');
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function processRequest(Request $request)
|
||||
{
|
||||
process:
|
||||
if (count($this->requests) > self::$maxLoop) {
|
||||
throw new ApplicationException('Too many loops detected in application life cycle.');
|
||||
}
|
||||
|
||||
$this->requests[] = $request;
|
||||
$this->onRequest($this, $request);
|
||||
|
||||
if (!$request->isMethod($request::FORWARD) && !strcasecmp($request->getPresenterName(), $this->errorPresenter)) {
|
||||
throw new BadRequestException('Invalid request. Presenter is not achievable.');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->presenter = $this->presenterFactory->createPresenter($request->getPresenterName());
|
||||
} catch (InvalidPresenterException $e) {
|
||||
throw count($this->requests) > 1 ? $e : new BadRequestException($e->getMessage(), 0, $e);
|
||||
}
|
||||
$this->onPresenter($this, $this->presenter);
|
||||
$response = $this->presenter->run(clone $request);
|
||||
|
||||
if ($response instanceof Responses\ForwardResponse) {
|
||||
$request = $response->getRequest();
|
||||
goto process;
|
||||
|
||||
} elseif ($response) {
|
||||
$this->onResponse($this, $response);
|
||||
$response->send($this->httpRequest, $this->httpResponse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Exception|\Throwable
|
||||
* @return void
|
||||
*/
|
||||
public function processException($e)
|
||||
{
|
||||
if (!$e instanceof BadRequestException && $this->httpResponse instanceof Nette\Http\Response) {
|
||||
$this->httpResponse->warnOnBuffer = false;
|
||||
}
|
||||
if (!$this->httpResponse->isSent()) {
|
||||
$this->httpResponse->setCode($e instanceof BadRequestException ? ($e->getHttpCode() ?: 404) : 500);
|
||||
}
|
||||
|
||||
$args = ['exception' => $e, 'request' => end($this->requests) ?: null];
|
||||
if ($this->presenter instanceof UI\Presenter) {
|
||||
try {
|
||||
$this->presenter->forward(":$this->errorPresenter:", $args);
|
||||
} catch (AbortException $foo) {
|
||||
$this->processRequest($this->presenter->getLastCreatedRequest());
|
||||
}
|
||||
} else {
|
||||
$this->processRequest(new Request($this->errorPresenter, Request::FORWARD, $args));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all processed requests.
|
||||
* @return Request[]
|
||||
*/
|
||||
public function getRequests()
|
||||
{
|
||||
return $this->requests;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns current presenter.
|
||||
* @return IPresenter|null
|
||||
*/
|
||||
public function getPresenter()
|
||||
{
|
||||
return $this->presenter;
|
||||
}
|
||||
|
||||
|
||||
/********************* services ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Returns router.
|
||||
* @return IRouter
|
||||
*/
|
||||
public function getRouter()
|
||||
{
|
||||
return $this->router;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns presenter factory.
|
||||
* @return IPresenterFactory
|
||||
*/
|
||||
public function getPresenterFactory()
|
||||
{
|
||||
return $this->presenterFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace NetteModule;
|
||||
|
||||
use Nette;
|
||||
use Nette\Application;
|
||||
use Nette\Http;
|
||||
use Tracy\ILogger;
|
||||
|
||||
|
||||
/**
|
||||
* Default Error Presenter.
|
||||
*/
|
||||
class ErrorPresenter implements Application\IPresenter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var ILogger|null */
|
||||
private $logger;
|
||||
|
||||
|
||||
public function __construct(ILogger $logger = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Application\IResponse
|
||||
*/
|
||||
public function run(Application\Request $request)
|
||||
{
|
||||
$e = $request->getParameter('exception');
|
||||
if ($e instanceof Application\BadRequestException) {
|
||||
$code = $e->getHttpCode();
|
||||
} else {
|
||||
$code = 500;
|
||||
if ($this->logger) {
|
||||
$this->logger->log($e, ILogger::EXCEPTION);
|
||||
}
|
||||
}
|
||||
return new Application\Responses\CallbackResponse(function (Http\IRequest $httpRequest, Http\IResponse $httpResponse) use ($code) {
|
||||
if (preg_match('#^text/html(?:;|$)#', $httpResponse->getHeader('Content-Type'))) {
|
||||
require __DIR__ . '/templates/error.phtml';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Application helpers.
|
||||
*/
|
||||
class Helpers
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Splits name into [module, presenter] or [presenter, action]
|
||||
* @return array
|
||||
*/
|
||||
public static function splitName($name)
|
||||
{
|
||||
$pos = strrpos($name, ':');
|
||||
return $pos === false
|
||||
? ['', $name, '']
|
||||
: [substr($name, 0, $pos), (string) substr($name, $pos + 1), ':'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
|
||||
/**
|
||||
* Presenter converts Request to IResponse.
|
||||
*/
|
||||
interface IPresenter
|
||||
{
|
||||
|
||||
/**
|
||||
* @return IResponse
|
||||
*/
|
||||
function run(Request $request);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
|
||||
/**
|
||||
* Responsible for creating a new instance of given presenter.
|
||||
*/
|
||||
interface IPresenterFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* Generates and checks presenter class name.
|
||||
* @param string presenter name
|
||||
* @return string class name
|
||||
* @throws InvalidPresenterException
|
||||
*/
|
||||
function getPresenterClass(&$name);
|
||||
|
||||
/**
|
||||
* Creates new presenter instance.
|
||||
* @param string presenter name
|
||||
* @return IPresenter
|
||||
*/
|
||||
function createPresenter($name);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Any response returned by presenter.
|
||||
*/
|
||||
interface IResponse
|
||||
{
|
||||
|
||||
/**
|
||||
* Sends response to output.
|
||||
* @return void
|
||||
*/
|
||||
function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* The bi-directional router.
|
||||
*/
|
||||
interface IRouter
|
||||
{
|
||||
/** only matching route */
|
||||
const ONE_WAY = 0b0001;
|
||||
|
||||
/** @deprecated */
|
||||
const SECURED = 0b0010;
|
||||
|
||||
/**
|
||||
* Maps HTTP request to a Request object.
|
||||
* @return Request|null
|
||||
*/
|
||||
function match(Nette\Http\IRequest $httpRequest);
|
||||
|
||||
/**
|
||||
* Constructs absolute URL from Request object.
|
||||
* @return string|null
|
||||
*/
|
||||
function constructUrl(Request $appRequest, Nette\Http\Url $refUrl);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Link generator.
|
||||
*/
|
||||
class LinkGenerator
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var IRouter */
|
||||
private $router;
|
||||
|
||||
/** @var Nette\Http\Url */
|
||||
private $refUrl;
|
||||
|
||||
/** @var IPresenterFactory|null */
|
||||
private $presenterFactory;
|
||||
|
||||
|
||||
public function __construct(IRouter $router, Nette\Http\Url $refUrl, IPresenterFactory $presenterFactory = null)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->refUrl = $refUrl;
|
||||
$this->presenterFactory = $presenterFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates URL to presenter.
|
||||
* @param string destination in format "[[[module:]presenter:]action] [#fragment]"
|
||||
* @return string
|
||||
* @throws UI\InvalidLinkException
|
||||
*/
|
||||
public function link($dest, array $params = [])
|
||||
{
|
||||
if (!preg_match('~^([\w:]+):(\w*+)(#.*)?()\z~', $dest, $m)) {
|
||||
throw new UI\InvalidLinkException("Invalid link destination '$dest'.");
|
||||
}
|
||||
list(, $presenter, $action, $frag) = $m;
|
||||
|
||||
try {
|
||||
$class = $this->presenterFactory ? $this->presenterFactory->getPresenterClass($presenter) : null;
|
||||
} catch (InvalidPresenterException $e) {
|
||||
throw new UI\InvalidLinkException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if (is_subclass_of($class, UI\Presenter::class)) {
|
||||
if ($action === '') {
|
||||
$action = UI\Presenter::DEFAULT_ACTION;
|
||||
}
|
||||
if (
|
||||
method_exists($class, $method = $class::formatActionMethod($action))
|
||||
|| method_exists($class, $method = $class::formatRenderMethod($action))
|
||||
) {
|
||||
UI\Presenter::argsToParams($class, $method, $params, [], $missing);
|
||||
if ($missing) {
|
||||
$rp = $missing[0];
|
||||
throw new UI\InvalidLinkException("Missing parameter \${$rp->getName()} required by {$rp->getDeclaringClass()->getName()}::{$rp->getDeclaringFunction()->getName()}()");
|
||||
}
|
||||
|
||||
} elseif (array_key_exists(0, $params)) {
|
||||
throw new UI\InvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($action !== '') {
|
||||
$params[UI\Presenter::ACTION_KEY] = $action;
|
||||
}
|
||||
|
||||
$url = $this->router->constructUrl(new Request($presenter, null, $params), $this->refUrl);
|
||||
if ($url === null) {
|
||||
unset($params[UI\Presenter::ACTION_KEY]);
|
||||
$params = urldecode(http_build_query($params, null, ', '));
|
||||
throw new UI\InvalidLinkException("No route for $dest($params)");
|
||||
}
|
||||
return $url . $frag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace NetteModule;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
use Nette\Application;
|
||||
use Nette\Application\Responses;
|
||||
use Nette\Http;
|
||||
|
||||
|
||||
/**
|
||||
* Micro presenter.
|
||||
*/
|
||||
class MicroPresenter implements Application\IPresenter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Nette\DI\Container|null */
|
||||
private $context;
|
||||
|
||||
/** @var Nette\Http\IRequest|null */
|
||||
private $httpRequest;
|
||||
|
||||
/** @var Application\IRouter|null */
|
||||
private $router;
|
||||
|
||||
/** @var Application\Request|null */
|
||||
private $request;
|
||||
|
||||
|
||||
public function __construct(Nette\DI\Container $context = null, Http\IRequest $httpRequest = null, Application\IRouter $router = null)
|
||||
{
|
||||
$this->context = $context;
|
||||
$this->httpRequest = $httpRequest;
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the context.
|
||||
* @return Nette\DI\Container|null
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Nette\Application\IResponse
|
||||
*/
|
||||
public function run(Application\Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
if ($this->httpRequest && $this->router && !$this->httpRequest->isAjax() && ($request->isMethod('get') || $request->isMethod('head'))) {
|
||||
$refUrl = clone $this->httpRequest->getUrl();
|
||||
$url = $this->router->constructUrl($request, $refUrl->setPath($refUrl->getScriptPath()));
|
||||
if ($url !== null && !$this->httpRequest->getUrl()->isEqual($url)) {
|
||||
return new Responses\RedirectResponse($url, Http\IResponse::S301_MOVED_PERMANENTLY);
|
||||
}
|
||||
}
|
||||
|
||||
$params = $request->getParameters();
|
||||
if (!isset($params['callback'])) {
|
||||
throw new Application\BadRequestException('Parameter callback is missing.');
|
||||
}
|
||||
$callback = $params['callback'];
|
||||
$reflection = Nette\Utils\Callback::toReflection(Nette\Utils\Callback::check($callback));
|
||||
|
||||
if ($this->context) {
|
||||
foreach ($reflection->getParameters() as $param) {
|
||||
if ($param->getClass()) {
|
||||
$params[$param->getName()] = $this->context->getByType($param->getClass()->getName(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
$params['presenter'] = $this;
|
||||
$params = Application\UI\ComponentReflection::combineArgs($reflection, $params);
|
||||
|
||||
$response = call_user_func_array($callback, $params);
|
||||
|
||||
if (is_string($response)) {
|
||||
$response = [$response, []];
|
||||
}
|
||||
if (is_array($response)) {
|
||||
list($templateSource, $templateParams) = $response;
|
||||
$response = $this->createTemplate()->setParameters($templateParams);
|
||||
if (!$templateSource instanceof \SplFileInfo) {
|
||||
$response->getLatte()->setLoader(new Latte\Loaders\StringLoader);
|
||||
}
|
||||
$response->setFile($templateSource);
|
||||
}
|
||||
if ($response instanceof Application\UI\ITemplate) {
|
||||
return new Responses\TextResponse($response);
|
||||
} else {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Template factory.
|
||||
* @param string
|
||||
* @return Application\UI\ITemplate
|
||||
*/
|
||||
public function createTemplate($class = null, callable $latteFactory = null)
|
||||
{
|
||||
$latte = $latteFactory ? $latteFactory() : $this->getContext()->getByType(Nette\Bridges\ApplicationLatte\ILatteFactory::class)->create();
|
||||
$template = $class ? new $class : new Nette\Bridges\ApplicationLatte\Template($latte);
|
||||
|
||||
$template->setParameters($this->request->getParameters());
|
||||
$template->presenter = $this;
|
||||
$template->context = $this->context;
|
||||
if ($this->httpRequest) {
|
||||
$url = $this->httpRequest->getUrl();
|
||||
$template->baseUrl = rtrim($url->getBaseUrl(), '/');
|
||||
$template->basePath = rtrim($url->getBasePath(), '/');
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redirects to another URL.
|
||||
* @param string
|
||||
* @param int HTTP code
|
||||
* @return Nette\Application\Responses\RedirectResponse
|
||||
*/
|
||||
public function redirectUrl($url, $httpCode = Http\IResponse::S302_FOUND)
|
||||
{
|
||||
return new Responses\RedirectResponse($url, $httpCode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throws HTTP error.
|
||||
* @param string
|
||||
* @param int HTTP error code
|
||||
* @return void
|
||||
* @throws Nette\Application\BadRequestException
|
||||
*/
|
||||
public function error($message = null, $httpCode = Http\IResponse::S404_NOT_FOUND)
|
||||
{
|
||||
throw new Application\BadRequestException($message, $httpCode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Nette\Application\Request|null
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Default presenter loader.
|
||||
*/
|
||||
class PresenterFactory implements IPresenterFactory
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array[] of module => splited mask */
|
||||
private $mapping = [
|
||||
'*' => ['', '*Module\\', '*Presenter'],
|
||||
'Nette' => ['NetteModule\\', '*\\', '*Presenter'],
|
||||
];
|
||||
|
||||
/** @var array */
|
||||
private $cache = [];
|
||||
|
||||
/** @var callable */
|
||||
private $factory;
|
||||
|
||||
|
||||
/**
|
||||
* @param callable function (string $class): IPresenter
|
||||
*/
|
||||
public function __construct(callable $factory = null)
|
||||
{
|
||||
$this->factory = $factory ?: function ($class) { return new $class; };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates new presenter instance.
|
||||
* @param string presenter name
|
||||
* @return IPresenter
|
||||
*/
|
||||
public function createPresenter($name)
|
||||
{
|
||||
return call_user_func($this->factory, $this->getPresenterClass($name));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates and checks presenter class name.
|
||||
* @param string presenter name
|
||||
* @return string class name
|
||||
* @throws InvalidPresenterException
|
||||
*/
|
||||
public function getPresenterClass(&$name)
|
||||
{
|
||||
if (isset($this->cache[$name])) {
|
||||
return $this->cache[$name];
|
||||
}
|
||||
|
||||
if (!is_string($name) || !Nette\Utils\Strings::match($name, '#^[a-zA-Z\x7f-\xff][a-zA-Z0-9\x7f-\xff:]*\z#')) {
|
||||
throw new InvalidPresenterException("Presenter name must be alphanumeric string, '$name' is invalid.");
|
||||
}
|
||||
|
||||
$class = $this->formatPresenterClass($name);
|
||||
if (!class_exists($class)) {
|
||||
throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' was not found.");
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$class = $reflection->getName();
|
||||
|
||||
if (!$reflection->implementsInterface(IPresenter::class)) {
|
||||
throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' is not Nette\\Application\\IPresenter implementor.");
|
||||
} elseif ($reflection->isAbstract()) {
|
||||
throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' is abstract.");
|
||||
}
|
||||
|
||||
$this->cache[$name] = $class;
|
||||
|
||||
if ($name !== ($realName = $this->unformatPresenterClass($class))) {
|
||||
trigger_error("Case mismatch on presenter name '$name', correct name is '$realName'.", E_USER_WARNING);
|
||||
$name = $realName;
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets mapping as pairs [module => mask]
|
||||
* @return static
|
||||
*/
|
||||
public function setMapping(array $mapping)
|
||||
{
|
||||
foreach ($mapping as $module => $mask) {
|
||||
if (is_string($mask)) {
|
||||
if (!preg_match('#^\\\\?([\w\\\\]*\\\\)?(\w*\*\w*?\\\\)?([\w\\\\]*\*\w*)\z#', $mask, $m)) {
|
||||
throw new Nette\InvalidStateException("Invalid mapping mask '$mask'.");
|
||||
}
|
||||
$this->mapping[$module] = [$m[1], $m[2] ?: '*Module\\', $m[3]];
|
||||
} elseif (is_array($mask) && count($mask) === 3) {
|
||||
$this->mapping[$module] = [$mask[0] ? $mask[0] . '\\' : '', $mask[1] . '\\', $mask[2]];
|
||||
} else {
|
||||
throw new Nette\InvalidStateException("Invalid mapping mask for module $module.");
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats presenter class name from its name.
|
||||
* @param string
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public function formatPresenterClass($presenter)
|
||||
{
|
||||
$parts = explode(':', $presenter);
|
||||
$mapping = isset($parts[1], $this->mapping[$parts[0]])
|
||||
? $this->mapping[array_shift($parts)]
|
||||
: $this->mapping['*'];
|
||||
|
||||
while ($part = array_shift($parts)) {
|
||||
$mapping[0] .= str_replace('*', $part, $mapping[$parts ? 1 : 2]);
|
||||
}
|
||||
return $mapping[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats presenter name from class name.
|
||||
* @param string
|
||||
* @return string|null
|
||||
* @internal
|
||||
*/
|
||||
public function unformatPresenterClass($class)
|
||||
{
|
||||
foreach ($this->mapping as $module => $mapping) {
|
||||
$mapping = str_replace(['\\', '*'], ['\\\\', '(\w+)'], $mapping);
|
||||
if (preg_match("#^\\\\?$mapping[0]((?:$mapping[1])*)$mapping[2]\\z#i", $class, $matches)) {
|
||||
return ($module === '*' ? '' : $module . ':')
|
||||
. preg_replace("#$mapping[1]#iA", '$1:', $matches[1]) . $matches[3];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Presenter request.
|
||||
*
|
||||
* @property string $presenterName
|
||||
* @property array $parameters
|
||||
* @property array $post
|
||||
* @property array $files
|
||||
* @property string|null $method
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** method */
|
||||
const FORWARD = 'FORWARD';
|
||||
|
||||
/** flag */
|
||||
const SECURED = 'secured';
|
||||
|
||||
/** flag */
|
||||
const RESTORED = 'restored';
|
||||
|
||||
/** flag */
|
||||
const VARYING = 'varying';
|
||||
|
||||
/** @var string|null */
|
||||
private $method;
|
||||
|
||||
/** @var array */
|
||||
private $flags = [];
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var array */
|
||||
private $params;
|
||||
|
||||
/** @var array */
|
||||
private $post;
|
||||
|
||||
/** @var array */
|
||||
private $files;
|
||||
|
||||
|
||||
/**
|
||||
* @param string fully qualified presenter name (module:module:presenter)
|
||||
* @param string method
|
||||
* @param array variables provided to the presenter usually via URL
|
||||
* @param array variables provided to the presenter via POST
|
||||
* @param array all uploaded files
|
||||
* @param array flags
|
||||
*/
|
||||
public function __construct($name, $method = null, array $params = [], array $post = [], array $files = [], array $flags = [])
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->method = $method;
|
||||
$this->params = $params;
|
||||
$this->post = $post;
|
||||
$this->files = $files;
|
||||
$this->flags = $flags;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the presenter name.
|
||||
* @param string
|
||||
* @return static
|
||||
*/
|
||||
public function setPresenterName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the presenter name.
|
||||
* @return string
|
||||
*/
|
||||
public function getPresenterName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets variables provided to the presenter.
|
||||
* @return static
|
||||
*/
|
||||
public function setParameters(array $params)
|
||||
{
|
||||
$this->params = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all variables provided to the presenter (usually via URL).
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a parameter provided to the presenter.
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParameter($key)
|
||||
{
|
||||
return isset($this->params[$key]) ? $this->params[$key] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets variables provided to the presenter via POST.
|
||||
* @return static
|
||||
*/
|
||||
public function setPost(array $params)
|
||||
{
|
||||
$this->post = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a variable provided to the presenter via POST.
|
||||
* If no key is passed, returns the entire array.
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPost($key = null)
|
||||
{
|
||||
if (func_num_args() === 0) {
|
||||
return $this->post;
|
||||
|
||||
} elseif (isset($this->post[$key])) {
|
||||
return $this->post[$key];
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets all uploaded files.
|
||||
* @return static
|
||||
*/
|
||||
public function setFiles(array $files)
|
||||
{
|
||||
$this->files = $files;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all uploaded files.
|
||||
* @return array
|
||||
*/
|
||||
public function getFiles()
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the method.
|
||||
* @param string|null
|
||||
* @return static
|
||||
*/
|
||||
public function setMethod($method)
|
||||
{
|
||||
$this->method = $method;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the method.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the method is the given one.
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public function isMethod($method)
|
||||
{
|
||||
return strcasecmp($this->method, $method) === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the flag.
|
||||
* @param string
|
||||
* @param bool
|
||||
* @return static
|
||||
*/
|
||||
public function setFlag($flag, $value = true)
|
||||
{
|
||||
$this->flags[$flag] = (bool) $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the flag.
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFlag($flag)
|
||||
{
|
||||
return !empty($this->flags[$flag]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Responses;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Callback response.
|
||||
*/
|
||||
class CallbackResponse implements Nette\Application\IResponse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
|
||||
|
||||
/**
|
||||
* @param callable function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
|
||||
*/
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends response to output.
|
||||
* @return void
|
||||
*/
|
||||
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
|
||||
{
|
||||
call_user_func($this->callback, $httpRequest, $httpResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Responses;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* File download response.
|
||||
*/
|
||||
class FileResponse implements Nette\Application\IResponse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var bool */
|
||||
public $resuming = true;
|
||||
|
||||
/** @var string */
|
||||
private $file;
|
||||
|
||||
/** @var string */
|
||||
private $contentType;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var bool */
|
||||
private $forceDownload;
|
||||
|
||||
|
||||
/**
|
||||
* @param string file path
|
||||
* @param string imposed file name
|
||||
* @param string MIME content type
|
||||
*/
|
||||
public function __construct($file, $name = null, $contentType = null, $forceDownload = true)
|
||||
{
|
||||
if (!is_file($file)) {
|
||||
throw new Nette\Application\BadRequestException("File '$file' doesn't exist.");
|
||||
}
|
||||
|
||||
$this->file = $file;
|
||||
$this->name = $name ?: basename($file);
|
||||
$this->contentType = $contentType ?: 'application/octet-stream';
|
||||
$this->forceDownload = $forceDownload;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to a downloaded file.
|
||||
* @return string
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the file name.
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the MIME content type of a downloaded file.
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return $this->contentType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends response to output.
|
||||
* @return void
|
||||
*/
|
||||
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
|
||||
{
|
||||
$httpResponse->setContentType($this->contentType);
|
||||
$httpResponse->setHeader('Content-Disposition',
|
||||
($this->forceDownload ? 'attachment' : 'inline')
|
||||
. '; filename="' . $this->name . '"'
|
||||
. '; filename*=utf-8\'\'' . rawurlencode($this->name));
|
||||
|
||||
$filesize = $length = filesize($this->file);
|
||||
$handle = fopen($this->file, 'r');
|
||||
|
||||
if ($this->resuming) {
|
||||
$httpResponse->setHeader('Accept-Ranges', 'bytes');
|
||||
if (preg_match('#^bytes=(\d*)-(\d*)\z#', $httpRequest->getHeader('Range'), $matches)) {
|
||||
list(, $start, $end) = $matches;
|
||||
if ($start === '') {
|
||||
$start = max(0, $filesize - $end);
|
||||
$end = $filesize - 1;
|
||||
|
||||
} elseif ($end === '' || $end > $filesize - 1) {
|
||||
$end = $filesize - 1;
|
||||
}
|
||||
if ($end < $start) {
|
||||
$httpResponse->setCode(416); // requested range not satisfiable
|
||||
return;
|
||||
}
|
||||
|
||||
$httpResponse->setCode(206);
|
||||
$httpResponse->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $filesize);
|
||||
$length = $end - $start + 1;
|
||||
fseek($handle, $start);
|
||||
|
||||
} else {
|
||||
$httpResponse->setHeader('Content-Range', 'bytes 0-' . ($filesize - 1) . '/' . $filesize);
|
||||
}
|
||||
}
|
||||
|
||||
$httpResponse->setHeader('Content-Length', $length);
|
||||
while (!feof($handle) && $length > 0) {
|
||||
echo $s = fread($handle, min(4000000, $length));
|
||||
$length -= strlen($s);
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Responses;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Forwards to new request.
|
||||
*/
|
||||
class ForwardResponse implements Nette\Application\IResponse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Nette\Application\Request */
|
||||
private $request;
|
||||
|
||||
|
||||
public function __construct(Nette\Application\Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Nette\Application\Request
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends response to output.
|
||||
* @return void
|
||||
*/
|
||||
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Responses;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* JSON response used mainly for AJAX requests.
|
||||
*/
|
||||
class JsonResponse implements Nette\Application\IResponse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var mixed */
|
||||
private $payload;
|
||||
|
||||
/** @var string */
|
||||
private $contentType;
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed payload
|
||||
* @param string MIME content type
|
||||
*/
|
||||
public function __construct($payload, $contentType = null)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
$this->contentType = $contentType ?: 'application/json';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPayload()
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the MIME content type of a downloaded file.
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return $this->contentType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends response to output.
|
||||
* @return void
|
||||
*/
|
||||
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
|
||||
{
|
||||
$httpResponse->setContentType($this->contentType, 'utf-8');
|
||||
echo Nette\Utils\Json::encode($this->payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Responses;
|
||||
|
||||
use Nette;
|
||||
use Nette\Http;
|
||||
|
||||
|
||||
/**
|
||||
* Redirects to new URI.
|
||||
*/
|
||||
class RedirectResponse implements Nette\Application\IResponse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var string */
|
||||
private $url;
|
||||
|
||||
/** @var int */
|
||||
private $httpCode;
|
||||
|
||||
|
||||
/**
|
||||
* @param string URI
|
||||
* @param int HTTP code 3xx
|
||||
*/
|
||||
public function __construct($url, $httpCode = Http\IResponse::S302_FOUND)
|
||||
{
|
||||
$this->url = (string) $url;
|
||||
$this->httpCode = (int) $httpCode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCode()
|
||||
{
|
||||
return $this->httpCode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends response to output.
|
||||
* @return void
|
||||
*/
|
||||
public function send(Http\IRequest $httpRequest, Http\IResponse $httpResponse)
|
||||
{
|
||||
$httpResponse->redirect($this->url, $this->httpCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Responses;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* String output response.
|
||||
*/
|
||||
class TextResponse implements Nette\Application\IResponse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var mixed */
|
||||
private $source;
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed renderable variable
|
||||
*/
|
||||
public function __construct($source)
|
||||
{
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSource()
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends response to output.
|
||||
* @return void
|
||||
*/
|
||||
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
|
||||
{
|
||||
if ($this->source instanceof Nette\Application\UI\ITemplate) {
|
||||
$this->source->render();
|
||||
|
||||
} else {
|
||||
echo $this->source;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Responses;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* No response.
|
||||
*/
|
||||
final class VoidResponse implements Nette\Application\IResponse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Routers;
|
||||
|
||||
use Nette;
|
||||
use Nette\Application;
|
||||
|
||||
|
||||
/**
|
||||
* The unidirectional router for CLI. (experimental)
|
||||
*/
|
||||
class CliRouter implements Application\IRouter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
const PRESENTER_KEY = 'action';
|
||||
|
||||
/** @var array */
|
||||
private $defaults;
|
||||
|
||||
|
||||
/**
|
||||
* @param array default values
|
||||
*/
|
||||
public function __construct($defaults = [])
|
||||
{
|
||||
$this->defaults = $defaults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps command line arguments to a Request object.
|
||||
* @return Nette\Application\Request|null
|
||||
*/
|
||||
public function match(Nette\Http\IRequest $httpRequest)
|
||||
{
|
||||
if (empty($_SERVER['argv']) || !is_array($_SERVER['argv'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$names = [self::PRESENTER_KEY];
|
||||
$params = $this->defaults;
|
||||
$args = $_SERVER['argv'];
|
||||
array_shift($args);
|
||||
$args[] = '--';
|
||||
|
||||
foreach ($args as $arg) {
|
||||
$opt = preg_replace('#/|-+#A', '', $arg);
|
||||
if ($opt === $arg) {
|
||||
if (isset($flag) || $flag = array_shift($names)) {
|
||||
$params[$flag] = $arg;
|
||||
} else {
|
||||
$params[] = $arg;
|
||||
}
|
||||
$flag = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($flag)) {
|
||||
$params[$flag] = true;
|
||||
$flag = null;
|
||||
}
|
||||
|
||||
if ($opt !== '') {
|
||||
$pair = explode('=', $opt, 2);
|
||||
if (isset($pair[1])) {
|
||||
$params[$pair[0]] = $pair[1];
|
||||
} else {
|
||||
$flag = $pair[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($params[self::PRESENTER_KEY])) {
|
||||
throw new Nette\InvalidStateException('Missing presenter & action in route definition.');
|
||||
}
|
||||
list($module, $presenter) = Nette\Application\Helpers::splitName($params[self::PRESENTER_KEY]);
|
||||
if ($module !== '') {
|
||||
$params[self::PRESENTER_KEY] = $presenter;
|
||||
$presenter = $module;
|
||||
}
|
||||
|
||||
return new Application\Request(
|
||||
$presenter,
|
||||
'CLI',
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This router is only unidirectional.
|
||||
* @return void
|
||||
*/
|
||||
public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns default values.
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaults()
|
||||
{
|
||||
return $this->defaults;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,793 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Routers;
|
||||
|
||||
use Nette;
|
||||
use Nette\Application;
|
||||
use Nette\Utils\Strings;
|
||||
|
||||
|
||||
/**
|
||||
* The bidirectional route is responsible for mapping
|
||||
* HTTP request to a Request object for dispatch and vice-versa.
|
||||
*/
|
||||
class Route implements Application\IRouter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
const PRESENTER_KEY = 'presenter';
|
||||
|
||||
const MODULE_KEY = 'module';
|
||||
|
||||
/** @internal url type */
|
||||
const HOST = 1,
|
||||
PATH = 2,
|
||||
RELATIVE = 3;
|
||||
|
||||
/** key used in {@link Route::$styles} or metadata {@link Route::__construct} */
|
||||
const VALUE = 'value';
|
||||
|
||||
const PATTERN = 'pattern';
|
||||
|
||||
const FILTER_IN = 'filterIn';
|
||||
|
||||
const FILTER_OUT = 'filterOut';
|
||||
|
||||
const FILTER_TABLE = 'filterTable';
|
||||
|
||||
const FILTER_STRICT = 'filterStrict';
|
||||
|
||||
/** @internal fixity types - how to handle default value? {@link Route::$metadata} */
|
||||
const OPTIONAL = 0,
|
||||
PATH_OPTIONAL = 1,
|
||||
CONSTANT = 2;
|
||||
|
||||
/** @deprecated */
|
||||
public static $defaultFlags = 0;
|
||||
|
||||
/** @var array */
|
||||
public static $styles = [
|
||||
'#' => [ // default style for path parameters
|
||||
self::PATTERN => '[^/]+',
|
||||
self::FILTER_OUT => [__CLASS__, 'param2path'],
|
||||
],
|
||||
'?#' => [// default style for query parameters
|
||||
],
|
||||
'module' => [
|
||||
self::PATTERN => '[a-z][a-z0-9.-]*',
|
||||
self::FILTER_IN => [__CLASS__, 'path2presenter'],
|
||||
self::FILTER_OUT => [__CLASS__, 'presenter2path'],
|
||||
],
|
||||
'presenter' => [
|
||||
self::PATTERN => '[a-z][a-z0-9.-]*',
|
||||
self::FILTER_IN => [__CLASS__, 'path2presenter'],
|
||||
self::FILTER_OUT => [__CLASS__, 'presenter2path'],
|
||||
],
|
||||
'action' => [
|
||||
self::PATTERN => '[a-z][a-z0-9-]*',
|
||||
self::FILTER_IN => [__CLASS__, 'path2action'],
|
||||
self::FILTER_OUT => [__CLASS__, 'action2path'],
|
||||
],
|
||||
'?module' => [
|
||||
],
|
||||
'?presenter' => [
|
||||
],
|
||||
'?action' => [
|
||||
],
|
||||
];
|
||||
|
||||
/** @var string */
|
||||
private $mask;
|
||||
|
||||
/** @var array */
|
||||
private $sequence;
|
||||
|
||||
/** @var string regular expression pattern */
|
||||
private $re;
|
||||
|
||||
/** @var string[] parameter aliases in regular expression */
|
||||
private $aliases;
|
||||
|
||||
/** @var array of [value & fixity, filterIn, filterOut] */
|
||||
private $metadata = [];
|
||||
|
||||
/** @var array */
|
||||
private $xlat;
|
||||
|
||||
/** @var int HOST, PATH, RELATIVE */
|
||||
private $type;
|
||||
|
||||
/** @var string http | https */
|
||||
private $scheme;
|
||||
|
||||
/** @var int */
|
||||
private $flags;
|
||||
|
||||
/** @var Nette\Http\Url */
|
||||
private $lastRefUrl;
|
||||
|
||||
/** @var string */
|
||||
private $lastBaseUrl;
|
||||
|
||||
|
||||
/**
|
||||
* @param string URL mask, e.g. '<presenter>/<action>/<id \d{1,3}>'
|
||||
* @param array|string|\Closure default values or metadata or callback for NetteModule\MicroPresenter
|
||||
* @param int flags
|
||||
*/
|
||||
public function __construct($mask, $metadata = [], $flags = 0)
|
||||
{
|
||||
if (is_string($metadata)) {
|
||||
list($presenter, $action) = Nette\Application\Helpers::splitName($metadata);
|
||||
if (!$presenter) {
|
||||
throw new Nette\InvalidArgumentException("Second argument must be array or string in format Presenter:action, '$metadata' given.");
|
||||
}
|
||||
$metadata = [self::PRESENTER_KEY => $presenter];
|
||||
if ($action !== '') {
|
||||
$metadata['action'] = $action;
|
||||
}
|
||||
} elseif ($metadata instanceof \Closure || $metadata instanceof Nette\Callback) {
|
||||
if ($metadata instanceof Nette\Callback) {
|
||||
trigger_error('Nette\Callback is deprecated, use Nette\Utils\Callback::closure().', E_USER_DEPRECATED);
|
||||
}
|
||||
$metadata = [
|
||||
self::PRESENTER_KEY => 'Nette:Micro',
|
||||
'callback' => $metadata,
|
||||
];
|
||||
}
|
||||
|
||||
$this->flags = $flags | static::$defaultFlags;
|
||||
$this->setMask($mask, $metadata);
|
||||
if (static::$defaultFlags) {
|
||||
trigger_error('Route::$defaultFlags is deprecated, router by default keeps the used protocol.', E_USER_DEPRECATED);
|
||||
} elseif ($flags & self::SECURED) {
|
||||
trigger_error('Router::SECURED is deprecated, specify scheme in mask.', E_USER_DEPRECATED);
|
||||
$this->scheme = 'https';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps HTTP request to a Request object.
|
||||
* @return Nette\Application\Request|null
|
||||
*/
|
||||
public function match(Nette\Http\IRequest $httpRequest)
|
||||
{
|
||||
// combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults
|
||||
|
||||
// 1) URL MASK
|
||||
$url = $httpRequest->getUrl();
|
||||
$re = $this->re;
|
||||
|
||||
if ($this->type === self::HOST) {
|
||||
$host = $url->getHost();
|
||||
$path = '//' . $host . $url->getPath();
|
||||
$parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
|
||||
$re = strtr($re, [
|
||||
'/%basePath%/' => preg_quote($url->getBasePath(), '#'),
|
||||
'%tld%' => preg_quote($parts[0], '#'),
|
||||
'%domain%' => preg_quote(isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0], '#'),
|
||||
'%sld%' => preg_quote(isset($parts[1]) ? $parts[1] : '', '#'),
|
||||
'%host%' => preg_quote($host, '#'),
|
||||
]);
|
||||
|
||||
} elseif ($this->type === self::RELATIVE) {
|
||||
$basePath = $url->getBasePath();
|
||||
if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) {
|
||||
return null;
|
||||
}
|
||||
$path = (string) substr($url->getPath(), strlen($basePath));
|
||||
|
||||
} else {
|
||||
$path = $url->getPath();
|
||||
}
|
||||
|
||||
if ($path !== '') {
|
||||
$path = rtrim(rawurldecode($path), '/') . '/';
|
||||
}
|
||||
|
||||
if (!$matches = Strings::match($path, $re)) {
|
||||
// stop, not matched
|
||||
return null;
|
||||
}
|
||||
|
||||
// assigns matched values to parameters
|
||||
$params = [];
|
||||
foreach ($matches as $k => $v) {
|
||||
if (is_string($k) && $v !== '') {
|
||||
$params[$this->aliases[$k]] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 2) CONSTANT FIXITY
|
||||
foreach ($this->metadata as $name => $meta) {
|
||||
if (!isset($params[$name]) && isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) {
|
||||
$params[$name] = null; // cannot be overwriten in 3) and detected by isset() in 4)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 3) QUERY
|
||||
if ($this->xlat) {
|
||||
$params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat));
|
||||
} else {
|
||||
$params += $httpRequest->getQuery();
|
||||
}
|
||||
|
||||
|
||||
// 4) APPLY FILTERS & FIXITY
|
||||
foreach ($this->metadata as $name => $meta) {
|
||||
if (isset($params[$name])) {
|
||||
if (!is_scalar($params[$name])) {
|
||||
// do nothing
|
||||
} elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applies filterTable only to scalar parameters
|
||||
$params[$name] = $meta[self::FILTER_TABLE][$params[$name]];
|
||||
|
||||
} elseif (isset($meta[self::FILTER_TABLE]) && !empty($meta[self::FILTER_STRICT])) {
|
||||
return null; // rejected by filterTable
|
||||
|
||||
} elseif (isset($meta[self::FILTER_IN])) { // applies filterIn only to scalar parameters
|
||||
$params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]);
|
||||
if ($params[$name] === null && !isset($meta['fixity'])) {
|
||||
return null; // rejected by filter
|
||||
}
|
||||
}
|
||||
|
||||
} elseif (isset($meta['fixity'])) {
|
||||
$params[$name] = $meta[self::VALUE];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->metadata[null][self::FILTER_IN])) {
|
||||
$params = call_user_func($this->metadata[null][self::FILTER_IN], $params);
|
||||
if ($params === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 5) BUILD Request
|
||||
if (!isset($params[self::PRESENTER_KEY])) {
|
||||
throw new Nette\InvalidStateException('Missing presenter in route definition.');
|
||||
} elseif (!is_string($params[self::PRESENTER_KEY])) {
|
||||
return null;
|
||||
}
|
||||
$presenter = $params[self::PRESENTER_KEY];
|
||||
unset($params[self::PRESENTER_KEY]);
|
||||
|
||||
if (isset($this->metadata[self::MODULE_KEY])) {
|
||||
$presenter = (isset($params[self::MODULE_KEY]) ? $params[self::MODULE_KEY] . ':' : '') . $presenter;
|
||||
unset($params[self::MODULE_KEY]);
|
||||
}
|
||||
|
||||
return new Application\Request(
|
||||
$presenter,
|
||||
$httpRequest->getMethod(),
|
||||
$params,
|
||||
$httpRequest->getPost(),
|
||||
$httpRequest->getFiles(),
|
||||
[Application\Request::SECURED => $httpRequest->isSecured()]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs absolute URL from Request object.
|
||||
* @return string|null
|
||||
*/
|
||||
public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
|
||||
{
|
||||
if ($this->flags & self::ONE_WAY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$params = $appRequest->getParameters();
|
||||
$metadata = $this->metadata;
|
||||
|
||||
$presenter = $appRequest->getPresenterName();
|
||||
$params[self::PRESENTER_KEY] = $presenter;
|
||||
|
||||
if (isset($metadata[self::MODULE_KEY])) { // try split into module and [submodule:]presenter parts
|
||||
$module = $metadata[self::MODULE_KEY];
|
||||
if (isset($module['fixity']) && strncmp($presenter, $module[self::VALUE] . ':', strlen($module[self::VALUE]) + 1) === 0) {
|
||||
$a = strlen($module[self::VALUE]);
|
||||
} else {
|
||||
$a = strrpos($presenter, ':');
|
||||
}
|
||||
if ($a === false) {
|
||||
$params[self::MODULE_KEY] = isset($module[self::VALUE]) ? '' : null;
|
||||
} else {
|
||||
$params[self::MODULE_KEY] = substr($presenter, 0, $a);
|
||||
$params[self::PRESENTER_KEY] = substr($presenter, $a + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($metadata[null][self::FILTER_OUT])) {
|
||||
$params = call_user_func($metadata[null][self::FILTER_OUT], $params);
|
||||
if ($params === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($metadata as $name => $meta) {
|
||||
if (!isset($params[$name])) {
|
||||
continue; // retains null values
|
||||
}
|
||||
|
||||
if (is_scalar($params[$name])) {
|
||||
$params[$name] = $params[$name] === false ? '0' : (string) $params[$name];
|
||||
}
|
||||
|
||||
if (isset($meta['fixity'])) {
|
||||
if ($params[$name] === $meta[self::VALUE]) { // remove default values; null values are retain
|
||||
unset($params[$name]);
|
||||
continue;
|
||||
|
||||
} elseif ($meta['fixity'] === self::CONSTANT) {
|
||||
return null; // missing or wrong parameter '$name'
|
||||
}
|
||||
}
|
||||
|
||||
if (is_scalar($params[$name]) && isset($meta['filterTable2'][$params[$name]])) {
|
||||
$params[$name] = $meta['filterTable2'][$params[$name]];
|
||||
|
||||
} elseif (isset($meta['filterTable2']) && !empty($meta[self::FILTER_STRICT])) {
|
||||
return null;
|
||||
|
||||
} elseif (isset($meta[self::FILTER_OUT])) {
|
||||
$params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]);
|
||||
}
|
||||
|
||||
if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], rawurldecode($params[$name]))) {
|
||||
return null; // pattern not match
|
||||
}
|
||||
}
|
||||
|
||||
// compositing path
|
||||
$sequence = $this->sequence;
|
||||
$brackets = [];
|
||||
$required = null; // null for auto-optional
|
||||
$url = '';
|
||||
$i = count($sequence) - 1;
|
||||
do {
|
||||
$url = $sequence[$i] . $url;
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
$i--;
|
||||
|
||||
$name = $sequence[$i--]; // parameter name
|
||||
|
||||
if ($name === ']') { // opening optional part
|
||||
$brackets[] = $url;
|
||||
|
||||
} elseif ($name[0] === '[') { // closing optional part
|
||||
$tmp = array_pop($brackets);
|
||||
if ($required < count($brackets) + 1) { // is this level optional?
|
||||
if ($name !== '[!') { // and not "required"-optional
|
||||
$url = $tmp;
|
||||
}
|
||||
} else {
|
||||
$required = count($brackets);
|
||||
}
|
||||
|
||||
} elseif ($name[0] === '?') { // "foo" parameter
|
||||
continue;
|
||||
|
||||
} elseif (isset($params[$name]) && $params[$name] != '') { // intentionally ==
|
||||
$required = count($brackets); // make this level required
|
||||
$url = $params[$name] . $url;
|
||||
unset($params[$name]);
|
||||
|
||||
} elseif (isset($metadata[$name]['fixity'])) { // has default value?
|
||||
if ($required === null && !$brackets) { // auto-optional
|
||||
$url = '';
|
||||
} else {
|
||||
$url = $metadata[$name]['defOut'] . $url;
|
||||
}
|
||||
|
||||
} else {
|
||||
return null; // missing parameter '$name'
|
||||
}
|
||||
} while (true);
|
||||
|
||||
$scheme = $this->scheme ?: $refUrl->getScheme();
|
||||
|
||||
if ($this->type === self::HOST) {
|
||||
$host = $refUrl->getHost();
|
||||
$parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
|
||||
$url = strtr($url, [
|
||||
'/%basePath%/' => $refUrl->getBasePath(),
|
||||
'%tld%' => $parts[0],
|
||||
'%domain%' => isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0],
|
||||
'%sld%' => isset($parts[1]) ? $parts[1] : '',
|
||||
'%host%' => $host,
|
||||
]);
|
||||
$url = $scheme . ':' . $url;
|
||||
} else {
|
||||
if ($this->lastRefUrl !== $refUrl) {
|
||||
$basePath = ($this->type === self::RELATIVE ? $refUrl->getBasePath() : '');
|
||||
$this->lastBaseUrl = $scheme . '://' . $refUrl->getAuthority() . $basePath;
|
||||
$this->lastRefUrl = $refUrl;
|
||||
}
|
||||
$url = $this->lastBaseUrl . $url;
|
||||
}
|
||||
|
||||
if (strpos($url, '//', strlen($scheme) + 3) !== false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// build query string
|
||||
if ($this->xlat) {
|
||||
$params = self::renameKeys($params, $this->xlat);
|
||||
}
|
||||
|
||||
$sep = ini_get('arg_separator.input');
|
||||
$query = http_build_query($params, '', $sep ? $sep[0] : '&');
|
||||
if ($query != '') { // intentionally ==
|
||||
$url .= '?' . $query;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse mask and array of default values; initializes object.
|
||||
* @param string
|
||||
* @param array
|
||||
* @return void
|
||||
*/
|
||||
private function setMask($mask, array $metadata)
|
||||
{
|
||||
$this->mask = $mask;
|
||||
|
||||
// detect '//host/path' vs. '/abs. path' vs. 'relative path'
|
||||
if (preg_match('#(?:(https?):)?(//.*)#A', $mask, $m)) {
|
||||
$this->type = self::HOST;
|
||||
list(, $this->scheme, $mask) = $m;
|
||||
|
||||
} elseif (substr($mask, 0, 1) === '/') {
|
||||
$this->type = self::PATH;
|
||||
|
||||
} else {
|
||||
$this->type = self::RELATIVE;
|
||||
}
|
||||
|
||||
foreach ($metadata as $name => $meta) {
|
||||
if (!is_array($meta)) {
|
||||
$metadata[$name] = $meta = [self::VALUE => $meta];
|
||||
}
|
||||
|
||||
if (array_key_exists(self::VALUE, $meta)) {
|
||||
if (is_scalar($meta[self::VALUE])) {
|
||||
$metadata[$name][self::VALUE] = $meta[self::VALUE] === false ? '0' : (string) $meta[self::VALUE];
|
||||
}
|
||||
$metadata[$name]['fixity'] = self::CONSTANT;
|
||||
}
|
||||
}
|
||||
|
||||
if (strpbrk($mask, '?<>[]') === false) {
|
||||
$this->re = '#' . preg_quote($mask, '#') . '/?\z#A';
|
||||
$this->sequence = [$mask];
|
||||
$this->metadata = $metadata;
|
||||
return;
|
||||
}
|
||||
|
||||
// PARSE MASK
|
||||
// <parameter-name[=default] [pattern]> or [ or ] or ?...
|
||||
$parts = Strings::split($mask, '/<([^<>= ]+)(=[^<> ]*)? *([^<>]*)>|(\[!?|\]|\s*\?.*)/');
|
||||
|
||||
$this->xlat = [];
|
||||
$i = count($parts) - 1;
|
||||
|
||||
// PARSE QUERY PART OF MASK
|
||||
if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {
|
||||
// name=<parameter-name [pattern]>
|
||||
$matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^> ]+) *([^>]*)>/');
|
||||
|
||||
foreach ($matches as list(, $param, $name, $pattern)) { // $pattern is not used
|
||||
if (isset(static::$styles['?' . $name])) {
|
||||
$meta = static::$styles['?' . $name];
|
||||
} else {
|
||||
$meta = static::$styles['?#'];
|
||||
}
|
||||
|
||||
if (isset($metadata[$name])) {
|
||||
$meta = $metadata[$name] + $meta;
|
||||
}
|
||||
|
||||
if (array_key_exists(self::VALUE, $meta)) {
|
||||
$meta['fixity'] = self::OPTIONAL;
|
||||
}
|
||||
|
||||
unset($meta['pattern']);
|
||||
$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? null : array_flip($meta[self::FILTER_TABLE]);
|
||||
|
||||
$metadata[$name] = $meta;
|
||||
if ($param !== '') {
|
||||
$this->xlat[$name] = $param;
|
||||
}
|
||||
}
|
||||
$i -= 5;
|
||||
}
|
||||
|
||||
// PARSE PATH PART OF MASK
|
||||
$brackets = 0; // optional level
|
||||
$re = '';
|
||||
$sequence = [];
|
||||
$autoOptional = true;
|
||||
$aliases = [];
|
||||
do {
|
||||
$part = $parts[$i]; // part of path
|
||||
if (strpbrk($part, '<>') !== false) {
|
||||
throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
|
||||
}
|
||||
array_unshift($sequence, $part);
|
||||
$re = preg_quote($part, '#') . $re;
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
$i--;
|
||||
|
||||
$part = $parts[$i]; // [ or ]
|
||||
if ($part === '[' || $part === ']' || $part === '[!') {
|
||||
$brackets += $part[0] === '[' ? -1 : 1;
|
||||
if ($brackets < 0) {
|
||||
throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
|
||||
}
|
||||
array_unshift($sequence, $part);
|
||||
$re = ($part[0] === '[' ? '(?:' : ')?') . $re;
|
||||
$i -= 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
$pattern = trim($parts[$i--]); // validation condition (as regexp)
|
||||
$default = $parts[$i--]; // default value
|
||||
$name = $parts[$i--]; // parameter name
|
||||
array_unshift($sequence, $name);
|
||||
|
||||
if ($name[0] === '?') { // "foo" parameter
|
||||
$name = substr($name, 1);
|
||||
$re = $pattern ? '(?:' . preg_quote($name, '#') . "|$pattern)$re" : preg_quote($name, '#') . $re;
|
||||
$sequence[1] = $name . $sequence[1];
|
||||
continue;
|
||||
}
|
||||
|
||||
// pattern, condition & metadata
|
||||
if (isset(static::$styles[$name])) {
|
||||
$meta = static::$styles[$name];
|
||||
} else {
|
||||
$meta = static::$styles['#'];
|
||||
}
|
||||
|
||||
if (isset($metadata[$name])) {
|
||||
$meta = $metadata[$name] + $meta;
|
||||
}
|
||||
|
||||
if ($pattern == '' && isset($meta[self::PATTERN])) {
|
||||
$pattern = $meta[self::PATTERN];
|
||||
}
|
||||
|
||||
if ($default !== '') {
|
||||
$meta[self::VALUE] = (string) substr($default, 1);
|
||||
$meta['fixity'] = self::PATH_OPTIONAL;
|
||||
}
|
||||
|
||||
$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? null : array_flip($meta[self::FILTER_TABLE]);
|
||||
if (array_key_exists(self::VALUE, $meta)) {
|
||||
if (isset($meta['filterTable2'][$meta[self::VALUE]])) {
|
||||
$meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]];
|
||||
|
||||
} elseif (isset($meta[self::FILTER_OUT])) {
|
||||
$meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]);
|
||||
|
||||
} else {
|
||||
$meta['defOut'] = $meta[self::VALUE];
|
||||
}
|
||||
}
|
||||
$meta[self::PATTERN] = "#(?:$pattern)\\z#A";
|
||||
|
||||
// include in expression
|
||||
$aliases['p' . $i] = $name;
|
||||
$re = '(?P<p' . $i . '>(?U)' . $pattern . ')' . $re;
|
||||
if ($brackets) { // is in brackets?
|
||||
if (!isset($meta[self::VALUE])) {
|
||||
$meta[self::VALUE] = $meta['defOut'] = null;
|
||||
}
|
||||
$meta['fixity'] = self::PATH_OPTIONAL;
|
||||
|
||||
} elseif (isset($meta['fixity'])) {
|
||||
if ($autoOptional) {
|
||||
$re = '(?:' . $re . ')?';
|
||||
}
|
||||
$meta['fixity'] = self::PATH_OPTIONAL;
|
||||
|
||||
} else {
|
||||
$autoOptional = false;
|
||||
}
|
||||
|
||||
$metadata[$name] = $meta;
|
||||
} while (true);
|
||||
|
||||
if ($brackets) {
|
||||
throw new Nette\InvalidArgumentException("Missing '[' in mask '$mask'.");
|
||||
}
|
||||
|
||||
$this->aliases = $aliases;
|
||||
$this->re = '#' . $re . '/?\z#A';
|
||||
$this->metadata = $metadata;
|
||||
$this->sequence = $sequence;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns mask.
|
||||
* @return string
|
||||
*/
|
||||
public function getMask()
|
||||
{
|
||||
return $this->mask;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns default values.
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaults()
|
||||
{
|
||||
$defaults = [];
|
||||
foreach ($this->metadata as $name => $meta) {
|
||||
if (isset($meta['fixity'])) {
|
||||
$defaults[$name] = $meta[self::VALUE];
|
||||
}
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns flags.
|
||||
* @return int
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
|
||||
/********************* Utilities ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Proprietary cache aim.
|
||||
* @internal
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getTargetPresenters()
|
||||
{
|
||||
if ($this->flags & self::ONE_WAY) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$m = $this->metadata;
|
||||
$module = '';
|
||||
|
||||
if (isset($m[self::MODULE_KEY])) {
|
||||
if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) {
|
||||
$module = $m[self::MODULE_KEY][self::VALUE] . ':';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) {
|
||||
return [$module . $m[self::PRESENTER_KEY][self::VALUE]];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rename keys in array.
|
||||
* @param array
|
||||
* @param array
|
||||
* @return array
|
||||
*/
|
||||
private static function renameKeys($arr, $xlat)
|
||||
{
|
||||
if (empty($xlat)) {
|
||||
return $arr;
|
||||
}
|
||||
|
||||
$res = [];
|
||||
$occupied = array_flip($xlat);
|
||||
foreach ($arr as $k => $v) {
|
||||
if (isset($xlat[$k])) {
|
||||
$res[$xlat[$k]] = $v;
|
||||
|
||||
} elseif (!isset($occupied[$k])) {
|
||||
$res[$k] = $v;
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/********************* Inflectors ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* camelCaseAction name -> dash-separated.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public static function action2path($s)
|
||||
{
|
||||
$s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s);
|
||||
$s = strtolower($s);
|
||||
$s = rawurlencode($s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dash-separated -> camelCaseAction name.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public static function path2action($s)
|
||||
{
|
||||
$s = preg_replace('#-(?=[a-z])#', ' ', $s);
|
||||
$s = lcfirst(ucwords($s));
|
||||
$s = str_replace(' ', '', $s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PascalCase:Presenter name -> dash-and-dot-separated.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public static function presenter2path($s)
|
||||
{
|
||||
$s = strtr($s, ':', '.');
|
||||
$s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s);
|
||||
$s = strtolower($s);
|
||||
$s = rawurlencode($s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dash-and-dot-separated -> PascalCase:Presenter name.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public static function path2presenter($s)
|
||||
{
|
||||
$s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s);
|
||||
$s = ucwords($s);
|
||||
$s = str_replace('. ', ':', $s);
|
||||
$s = str_replace('- ', '', $s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Url encode.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public static function param2path($s)
|
||||
{
|
||||
return str_replace('%2F', '/', rawurlencode($s));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Routers;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* The router broker.
|
||||
*/
|
||||
class RouteList extends Nette\Utils\ArrayList implements Nette\Application\IRouter
|
||||
{
|
||||
/** @var array */
|
||||
private $cachedRoutes;
|
||||
|
||||
/** @var string|null */
|
||||
private $module;
|
||||
|
||||
|
||||
public function __construct($module = null)
|
||||
{
|
||||
$this->module = $module ? $module . ':' : '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps HTTP request to a Request object.
|
||||
* @return Nette\Application\Request|null
|
||||
*/
|
||||
public function match(Nette\Http\IRequest $httpRequest)
|
||||
{
|
||||
foreach ($this as $route) {
|
||||
$appRequest = $route->match($httpRequest);
|
||||
if ($appRequest !== null) {
|
||||
$name = $appRequest->getPresenterName();
|
||||
if (strncmp($name, 'Nette:', 6)) {
|
||||
$appRequest->setPresenterName($this->module . $name);
|
||||
}
|
||||
return $appRequest;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs absolute URL from Request object.
|
||||
* @return string|null
|
||||
*/
|
||||
public function constructUrl(Nette\Application\Request $appRequest, Nette\Http\Url $refUrl)
|
||||
{
|
||||
if ($this->cachedRoutes === null) {
|
||||
$this->warmupCache();
|
||||
}
|
||||
|
||||
if ($this->module) {
|
||||
if (strncmp($tmp = $appRequest->getPresenterName(), $this->module, strlen($this->module)) === 0) {
|
||||
$appRequest = clone $appRequest;
|
||||
$appRequest->setPresenterName(substr($tmp, strlen($this->module)));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$presenter = $appRequest->getPresenterName();
|
||||
if (!isset($this->cachedRoutes[$presenter])) {
|
||||
$presenter = '*';
|
||||
}
|
||||
|
||||
foreach ($this->cachedRoutes[$presenter] as $route) {
|
||||
$url = $route->constructUrl($appRequest, $refUrl);
|
||||
if ($url !== null) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function warmupCache()
|
||||
{
|
||||
$routes = [];
|
||||
$routes['*'] = [];
|
||||
|
||||
foreach ($this as $route) {
|
||||
$presenters = $route instanceof Route && is_array($tmp = $route->getTargetPresenters())
|
||||
? $tmp
|
||||
: array_keys($routes);
|
||||
|
||||
foreach ($presenters as $presenter) {
|
||||
if (!isset($routes[$presenter])) {
|
||||
$routes[$presenter] = $routes['*'];
|
||||
}
|
||||
$routes[$presenter][] = $route;
|
||||
}
|
||||
}
|
||||
|
||||
$this->cachedRoutes = $routes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the router.
|
||||
* @param mixed
|
||||
* @param Nette\Application\IRouter
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($index, $route)
|
||||
{
|
||||
if (!$route instanceof Nette\Application\IRouter) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be IRouter descendant.');
|
||||
}
|
||||
parent::offsetSet($index, $route);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getModule()
|
||||
{
|
||||
return $this->module;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\Routers;
|
||||
|
||||
use Nette;
|
||||
use Nette\Application;
|
||||
|
||||
|
||||
/**
|
||||
* The bidirectional route for trivial routing via query parameters.
|
||||
*/
|
||||
class SimpleRouter implements Application\IRouter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
const PRESENTER_KEY = 'presenter';
|
||||
|
||||
const MODULE_KEY = 'module';
|
||||
|
||||
/** @var string */
|
||||
private $module = '';
|
||||
|
||||
/** @var array */
|
||||
private $defaults;
|
||||
|
||||
/** @var int */
|
||||
private $flags;
|
||||
|
||||
|
||||
/**
|
||||
* @param array default values
|
||||
* @param int flags
|
||||
*/
|
||||
public function __construct($defaults = [], $flags = 0)
|
||||
{
|
||||
if (is_string($defaults)) {
|
||||
list($presenter, $action) = Nette\Application\Helpers::splitName($defaults);
|
||||
if (!$presenter) {
|
||||
throw new Nette\InvalidArgumentException("Argument must be array or string in format Presenter:action, '$defaults' given.");
|
||||
}
|
||||
$defaults = [
|
||||
self::PRESENTER_KEY => $presenter,
|
||||
'action' => $action === '' ? Application\UI\Presenter::DEFAULT_ACTION : $action,
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($defaults[self::MODULE_KEY])) {
|
||||
$this->module = $defaults[self::MODULE_KEY] . ':';
|
||||
unset($defaults[self::MODULE_KEY]);
|
||||
}
|
||||
|
||||
$this->defaults = $defaults;
|
||||
$this->flags = $flags;
|
||||
if ($flags & self::SECURED) {
|
||||
trigger_error('IRouter::SECURED is deprecated, router by default keeps the used protocol.', E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps HTTP request to a Request object.
|
||||
* @return Nette\Application\Request|null
|
||||
*/
|
||||
public function match(Nette\Http\IRequest $httpRequest)
|
||||
{
|
||||
if ($httpRequest->getUrl()->getPathInfo() !== '') {
|
||||
return null;
|
||||
}
|
||||
// combine with precedence: get, (post,) defaults
|
||||
$params = $httpRequest->getQuery();
|
||||
$params += $this->defaults;
|
||||
|
||||
if (!isset($params[self::PRESENTER_KEY]) || !is_string($params[self::PRESENTER_KEY])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$presenter = $this->module . $params[self::PRESENTER_KEY];
|
||||
unset($params[self::PRESENTER_KEY]);
|
||||
|
||||
return new Application\Request(
|
||||
$presenter,
|
||||
$httpRequest->getMethod(),
|
||||
$params,
|
||||
$httpRequest->getPost(),
|
||||
$httpRequest->getFiles(),
|
||||
[Application\Request::SECURED => $httpRequest->isSecured()]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs absolute URL from Request object.
|
||||
* @return string|null
|
||||
*/
|
||||
public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
|
||||
{
|
||||
if ($this->flags & self::ONE_WAY) {
|
||||
return null;
|
||||
}
|
||||
$params = $appRequest->getParameters();
|
||||
|
||||
// presenter name
|
||||
$presenter = $appRequest->getPresenterName();
|
||||
if (strncmp($presenter, $this->module, strlen($this->module)) === 0) {
|
||||
$params[self::PRESENTER_KEY] = substr($presenter, strlen($this->module));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// remove default values; null values are retain
|
||||
foreach ($this->defaults as $key => $value) {
|
||||
if (isset($params[$key]) && $params[$key] == $value) { // intentionally ==
|
||||
unset($params[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$url = ($this->flags & self::SECURED ? 'https://' : $refUrl->getScheme() . '://') . $refUrl->getAuthority() . $refUrl->getPath();
|
||||
$sep = ini_get('arg_separator.input');
|
||||
$query = http_build_query($params, '', $sep ? $sep[0] : '&');
|
||||
if ($query != '') { // intentionally ==
|
||||
$url .= '?' . $query;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns default values.
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaults()
|
||||
{
|
||||
return $this->defaults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns flags.
|
||||
* @return int
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Signal exception.
|
||||
*/
|
||||
class BadSignalException extends Nette\Application\BadRequestException
|
||||
{
|
||||
/** @var int */
|
||||
protected $code = 403;
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Component is the base class for all Presenter components.
|
||||
*
|
||||
* Components are persistent objects located on a presenter. They have ability to own
|
||||
* other child components, and interact with user. Components have properties
|
||||
* for storing their status, and responds to user command.
|
||||
*
|
||||
* @property-read Presenter $presenter
|
||||
* @property-read bool $linkCurrent
|
||||
*/
|
||||
abstract class Component extends Nette\ComponentModel\Container implements ISignalReceiver, IStatePersistent, \ArrayAccess
|
||||
{
|
||||
/** @var callable[] function (self $sender); Occurs when component is attached to presenter */
|
||||
public $onAnchor;
|
||||
|
||||
/** @var array */
|
||||
protected $params = [];
|
||||
|
||||
|
||||
/**
|
||||
* Returns the presenter where this component belongs to.
|
||||
* @param bool throw exception if presenter doesn't exist?
|
||||
* @return Presenter|null
|
||||
*/
|
||||
public function getPresenter($throw = true)
|
||||
{
|
||||
return $this->lookup(Presenter::class, $throw);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a fully-qualified name that uniquely identifies the component
|
||||
* within the presenter hierarchy.
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId()
|
||||
{
|
||||
return $this->lookupPath(Presenter::class, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will be called when the component (or component's parent)
|
||||
* becomes attached to a monitored object. Do not call this method yourself.
|
||||
* @param Nette\ComponentModel\IComponent
|
||||
* @return void
|
||||
*/
|
||||
protected function attached($presenter)
|
||||
{
|
||||
if ($presenter instanceof Presenter) {
|
||||
$this->loadState($presenter->popGlobalParameters($this->getUniqueId()));
|
||||
$this->onAnchor($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function validateParent(Nette\ComponentModel\IContainer $parent)
|
||||
{
|
||||
parent::validateParent($parent);
|
||||
$this->monitor(Presenter::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls public method if exists.
|
||||
* @param string
|
||||
* @param array
|
||||
* @return bool does method exist?
|
||||
*/
|
||||
protected function tryCall($method, array $params)
|
||||
{
|
||||
$rc = $this->getReflection();
|
||||
if ($rc->hasMethod($method)) {
|
||||
$rm = $rc->getMethod($method);
|
||||
if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
|
||||
$this->checkRequirements($rm);
|
||||
$rm->invokeArgs($this, $rc->combineArgs($rm, $params));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks for requirements such as authorization.
|
||||
* @return void
|
||||
*/
|
||||
public function checkRequirements($element)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Access to reflection.
|
||||
* @return ComponentReflection
|
||||
*/
|
||||
public static function getReflection()
|
||||
{
|
||||
return new ComponentReflection(get_called_class());
|
||||
}
|
||||
|
||||
|
||||
/********************* interface IStatePersistent ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Loads state informations.
|
||||
* @param array
|
||||
* @return void
|
||||
*/
|
||||
public function loadState(array $params)
|
||||
{
|
||||
$reflection = $this->getReflection();
|
||||
foreach ($reflection->getPersistentParams() as $name => $meta) {
|
||||
if (isset($params[$name])) { // nulls are ignored
|
||||
$type = gettype($meta['def']);
|
||||
if (!$reflection->convertType($params[$name], $type)) {
|
||||
throw new Nette\Application\BadRequestException(sprintf(
|
||||
"Value passed to persistent parameter '%s' in %s must be %s, %s given.",
|
||||
$name,
|
||||
$this instanceof Presenter ? 'presenter ' . $this->getName() : "component '{$this->getUniqueId()}'",
|
||||
$type === 'NULL' ? 'scalar' : $type,
|
||||
is_object($params[$name]) ? get_class($params[$name]) : gettype($params[$name])
|
||||
));
|
||||
}
|
||||
$this->$name = $params[$name];
|
||||
} else {
|
||||
$params[$name] = $this->$name;
|
||||
}
|
||||
}
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves state informations for next request.
|
||||
* @return void
|
||||
*/
|
||||
public function saveState(array &$params)
|
||||
{
|
||||
$this->getReflection()->saveState($this, $params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns component param.
|
||||
* @param string key
|
||||
* @param mixed default value
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParameter($name, $default = null)
|
||||
{
|
||||
if (isset($this->params[$name])) {
|
||||
return $this->params[$name];
|
||||
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns component parameters.
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a fully-qualified name that uniquely identifies the parameter.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public function getParameterId($name)
|
||||
{
|
||||
$uid = $this->getUniqueId();
|
||||
return $uid === '' ? $name : $uid . self::NAME_SEPARATOR . $name;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function getParam($name = null, $default = null)
|
||||
{
|
||||
//trigger_error(__METHOD__ . '() is deprecated; use getParameter() instead.', E_USER_DEPRECATED);
|
||||
return func_num_args() ? $this->getParameter($name, $default) : $this->getParameters();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns array of classes persistent parameters. They have public visibility and are non-static.
|
||||
* This default implementation detects persistent parameters by annotation @persistent.
|
||||
* @return array
|
||||
*/
|
||||
public static function getPersistentParams()
|
||||
{
|
||||
$rc = new \ReflectionClass(get_called_class());
|
||||
$params = [];
|
||||
foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $rp) {
|
||||
if (!$rp->isStatic() && ComponentReflection::parseAnnotation($rp, 'persistent')) {
|
||||
$params[] = $rp->getName();
|
||||
}
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/********************* interface ISignalReceiver ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Calls signal handler method.
|
||||
* @param string
|
||||
* @return void
|
||||
* @throws BadSignalException if there is not handler method
|
||||
*/
|
||||
public function signalReceived($signal)
|
||||
{
|
||||
if (!$this->tryCall($this->formatSignalMethod($signal), $this->params)) {
|
||||
$class = get_class($this);
|
||||
throw new BadSignalException("There is no handler for signal '$signal' in class $class.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats signal handler method name -> case sensitivity doesn't matter.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public static function formatSignalMethod($signal)
|
||||
{
|
||||
return $signal == null ? null : 'handle' . $signal; // intentionally ==
|
||||
}
|
||||
|
||||
|
||||
/********************* navigation ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Generates URL to presenter, action or signal.
|
||||
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
|
||||
* @param array|mixed
|
||||
* @return string
|
||||
* @throws InvalidLinkException
|
||||
*/
|
||||
public function link($destination, $args = [])
|
||||
{
|
||||
try {
|
||||
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
|
||||
return $this->getPresenter()->createRequest($this, $destination, $args, 'link');
|
||||
|
||||
} catch (InvalidLinkException $e) {
|
||||
return $this->getPresenter()->handleInvalidLink($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns destination as Link object.
|
||||
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
|
||||
* @param array|mixed
|
||||
* @return Link
|
||||
*/
|
||||
public function lazyLink($destination, $args = [])
|
||||
{
|
||||
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
|
||||
return new Link($this, $destination, $args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether it links to the current page.
|
||||
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
|
||||
* @param array|mixed
|
||||
* @return bool
|
||||
* @throws InvalidLinkException
|
||||
*/
|
||||
public function isLinkCurrent($destination = null, $args = [])
|
||||
{
|
||||
if ($destination !== null) {
|
||||
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
|
||||
$this->getPresenter()->createRequest($this, $destination, $args, 'test');
|
||||
}
|
||||
return $this->getPresenter()->getLastCreatedRequestFlag('current');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redirect to another presenter, action or signal.
|
||||
* @param int [optional] HTTP error code
|
||||
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
|
||||
* @param array|mixed
|
||||
* @return void
|
||||
* @throws Nette\Application\AbortException
|
||||
*/
|
||||
public function redirect($code, $destination = null, $args = [])
|
||||
{
|
||||
if (!is_numeric($code)) { // first parameter is optional
|
||||
$args = func_num_args() < 3 && is_array($destination) ? $destination : array_slice(func_get_args(), 1);
|
||||
$destination = $code;
|
||||
$code = null;
|
||||
|
||||
} elseif (func_num_args() > 3 || !is_array($args)) {
|
||||
$args = array_slice(func_get_args(), 2);
|
||||
}
|
||||
|
||||
$presenter = $this->getPresenter();
|
||||
$presenter->redirectUrl($presenter->createRequest($this, $destination, $args, 'redirect'), $code);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Permanently redirects to presenter, action or signal.
|
||||
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
|
||||
* @param array|mixed
|
||||
* @return void
|
||||
* @throws Nette\Application\AbortException
|
||||
*/
|
||||
public function redirectPermanent($destination = null, $args = [])
|
||||
{
|
||||
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
|
||||
$presenter = $this->getPresenter();
|
||||
$presenter->redirectUrl(
|
||||
$presenter->createRequest($this, $destination, $args, 'redirect'),
|
||||
Nette\Http\IResponse::S301_MOVED_PERMANENTLY
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throws HTTP error.
|
||||
* @param string
|
||||
* @param int HTTP error code
|
||||
* @return void
|
||||
* @throws Nette\Application\BadRequestException
|
||||
*/
|
||||
public function error($message = null, $httpCode = Nette\Http\IResponse::S404_NOT_FOUND)
|
||||
{
|
||||
throw new Nette\Application\BadRequestException($message, $httpCode);
|
||||
}
|
||||
|
||||
|
||||
/********************* interface \ArrayAccess ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Adds the component to the container.
|
||||
* @param string component name
|
||||
* @param Nette\ComponentModel\IComponent
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($name, $component)
|
||||
{
|
||||
$this->addComponent($component, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns component specified by name. Throws exception if component doesn't exist.
|
||||
* @param string component name
|
||||
* @return Nette\ComponentModel\IComponent
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public function offsetGet($name)
|
||||
{
|
||||
return $this->getComponent($name, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does component specified by name exists?
|
||||
* @param string component name
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($name)
|
||||
{
|
||||
return $this->getComponent($name, false) !== null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes component from the container.
|
||||
* @param string component name
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($name)
|
||||
{
|
||||
$component = $this->getComponent($name, false);
|
||||
if ($component !== null) {
|
||||
$this->removeComponent($component);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
use Nette;
|
||||
use Nette\Application\BadRequestException;
|
||||
use Nette\Reflection\ClassType;
|
||||
|
||||
|
||||
/**
|
||||
* Helpers for Presenter & Component.
|
||||
* @property-read string $name
|
||||
* @property-read string $fileName
|
||||
* @internal
|
||||
*/
|
||||
class ComponentReflection extends \ReflectionClass
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array getPersistentParams cache */
|
||||
private static $ppCache = [];
|
||||
|
||||
/** @var array getPersistentComponents cache */
|
||||
private static $pcCache = [];
|
||||
|
||||
/** @var array isMethodCallable cache */
|
||||
private static $mcCache = [];
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null
|
||||
* @return array of persistent parameters.
|
||||
*/
|
||||
public function getPersistentParams($class = null)
|
||||
{
|
||||
$class = $class === null ? $this->getName() : $class;
|
||||
$params = &self::$ppCache[$class];
|
||||
if ($params !== null) {
|
||||
return $params;
|
||||
}
|
||||
$params = [];
|
||||
if (is_subclass_of($class, Component::class)) {
|
||||
$isPresenter = is_subclass_of($class, Presenter::class);
|
||||
$defaults = get_class_vars($class);
|
||||
foreach ($class::getPersistentParams() as $name => $default) {
|
||||
if (is_int($name)) {
|
||||
$name = $default;
|
||||
$default = $defaults[$name];
|
||||
}
|
||||
$params[$name] = [
|
||||
'def' => $default,
|
||||
'since' => $isPresenter ? $class : null,
|
||||
];
|
||||
}
|
||||
foreach ($this->getPersistentParams(get_parent_class($class)) as $name => $param) {
|
||||
if (isset($params[$name])) {
|
||||
$params[$name]['since'] = $param['since'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$params[$name] = $param;
|
||||
}
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null
|
||||
* @return array of persistent components.
|
||||
*/
|
||||
public function getPersistentComponents($class = null)
|
||||
{
|
||||
$class = $class === null ? $this->getName() : $class;
|
||||
$components = &self::$pcCache[$class];
|
||||
if ($components !== null) {
|
||||
return $components;
|
||||
}
|
||||
$components = [];
|
||||
if (is_subclass_of($class, Presenter::class)) {
|
||||
foreach ($class::getPersistentComponents() as $name => $meta) {
|
||||
if (is_string($meta)) {
|
||||
$name = $meta;
|
||||
}
|
||||
$components[$name] = ['since' => $class];
|
||||
}
|
||||
$components = $this->getPersistentComponents(get_parent_class($class)) + $components;
|
||||
}
|
||||
return $components;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves state informations for next request.
|
||||
*/
|
||||
public function saveState(Component $component, array &$params)
|
||||
{
|
||||
foreach ($this->getPersistentParams() as $name => $meta) {
|
||||
if (isset($params[$name])) {
|
||||
// injected value
|
||||
|
||||
} elseif (
|
||||
array_key_exists($name, $params) // nulls are skipped
|
||||
|| (isset($meta['since']) && !$component instanceof $meta['since']) // not related
|
||||
|| !isset($component->$name)
|
||||
) {
|
||||
continue;
|
||||
|
||||
} else {
|
||||
$params[$name] = $component->$name; // object property value
|
||||
}
|
||||
|
||||
$type = gettype($meta['def']);
|
||||
if (!self::convertType($params[$name], $type)) {
|
||||
throw new InvalidLinkException(sprintf(
|
||||
"Value passed to persistent parameter '%s' in %s must be %s, %s given.",
|
||||
$name,
|
||||
$component instanceof Presenter ? 'presenter ' . $component->getName() : "component '{$component->getUniqueId()}'",
|
||||
$type === 'NULL' ? 'scalar' : $type,
|
||||
is_object($params[$name]) ? get_class($params[$name]) : gettype($params[$name])
|
||||
));
|
||||
}
|
||||
|
||||
if ($params[$name] === $meta['def'] || ($meta['def'] === null && $params[$name] === '')) {
|
||||
$params[$name] = null; // value transmit is unnecessary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is a method callable? It means class is instantiable and method has
|
||||
* public visibility, is non-static and non-abstract.
|
||||
* @param string method name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCallableMethod($method)
|
||||
{
|
||||
$class = $this->getName();
|
||||
$cache = &self::$mcCache[strtolower($class . ':' . $method)];
|
||||
if ($cache === null) {
|
||||
try {
|
||||
$cache = false;
|
||||
$rm = new \ReflectionMethod($class, $method);
|
||||
$cache = $this->isInstantiable() && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic();
|
||||
} catch (\ReflectionException $e) {
|
||||
}
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function combineArgs(\ReflectionFunctionAbstract $method, $args)
|
||||
{
|
||||
$res = [];
|
||||
foreach ($method->getParameters() as $i => $param) {
|
||||
$name = $param->getName();
|
||||
list($type, $isClass) = self::getParameterType($param);
|
||||
if (isset($args[$name])) {
|
||||
$res[$i] = $args[$name];
|
||||
if (!self::convertType($res[$i], $type, $isClass)) {
|
||||
throw new BadRequestException(sprintf(
|
||||
'Argument $%s passed to %s() must be %s, %s given.',
|
||||
$name,
|
||||
($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName(),
|
||||
$type === 'NULL' ? 'scalar' : $type,
|
||||
is_object($args[$name]) ? get_class($args[$name]) : gettype($args[$name])
|
||||
));
|
||||
}
|
||||
} elseif ($param->isDefaultValueAvailable()) {
|
||||
$res[$i] = $param->getDefaultValue();
|
||||
} elseif ($type === 'NULL' || $param->allowsNull()) {
|
||||
$res[$i] = null;
|
||||
} elseif ($type === 'array') {
|
||||
$res[$i] = [];
|
||||
} else {
|
||||
throw new BadRequestException(sprintf(
|
||||
'Missing parameter $%s required by %s()',
|
||||
$name,
|
||||
($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Non data-loss type conversion.
|
||||
* @param mixed
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public static function convertType(&$val, $type, $isClass = false)
|
||||
{
|
||||
if ($isClass) {
|
||||
return $val instanceof $type;
|
||||
|
||||
} elseif ($type === 'callable') {
|
||||
return false;
|
||||
|
||||
} elseif ($type === 'NULL') { // means 'not array'
|
||||
return !is_array($val);
|
||||
|
||||
} elseif ($type === 'array') {
|
||||
return is_array($val);
|
||||
|
||||
} elseif (!is_scalar($val)) { // array, resource, null, etc.
|
||||
return false;
|
||||
|
||||
} else {
|
||||
$tmp = ($val === false ? '0' : (string) $val);
|
||||
if ($type === 'double' || $type === 'float') {
|
||||
$tmp = preg_replace('#\.0*\z#', '', $tmp);
|
||||
}
|
||||
$orig = $tmp;
|
||||
settype($tmp, $type);
|
||||
if ($orig !== ($tmp === false ? '0' : (string) $tmp)) {
|
||||
return false; // data-loss occurs
|
||||
}
|
||||
$val = $tmp;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
* @return array|false
|
||||
*/
|
||||
public static function parseAnnotation(\Reflector $ref, $name)
|
||||
{
|
||||
if (!preg_match_all('#[\\s*]@' . preg_quote($name, '#') . '(?:\(\\s*([^)]*)\\s*\)|\\s|$)#', $ref->getDocComment(), $m)) {
|
||||
return false;
|
||||
}
|
||||
static $tokens = ['true' => true, 'false' => false, 'null' => null];
|
||||
$res = [];
|
||||
foreach ($m[1] as $s) {
|
||||
foreach (preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY) ?: ['true'] as $item) {
|
||||
$res[] = array_key_exists($tmp = strtolower($item), $tokens) ? $tokens[$tmp] : $item;
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array [string|null, bool]
|
||||
*/
|
||||
public static function getParameterType(\ReflectionParameter $param)
|
||||
{
|
||||
$def = gettype($param->isDefaultValueAvailable() ? $param->getDefaultValue() : null);
|
||||
if (PHP_VERSION_ID >= 70000) {
|
||||
return $param->hasType()
|
||||
? [PHP_VERSION_ID >= 70100 ? $param->getType()->getName() : (string) $param->getType(), !$param->getType()->isBuiltin()]
|
||||
: [$def, false];
|
||||
} elseif ($param->isArray() || $param->isCallable()) {
|
||||
return [$param->isArray() ? 'array' : 'callable', false];
|
||||
} else {
|
||||
try {
|
||||
return ($ref = $param->getClass()) ? [$ref->getName(), true] : [$def, false];
|
||||
} catch (\ReflectionException $e) {
|
||||
if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
|
||||
throw new \LogicException(sprintf(
|
||||
"Class %s not found. Check type hint of parameter $%s in %s() or 'use' statements.",
|
||||
$m[1],
|
||||
$param->getName(),
|
||||
$param->getDeclaringFunction()->getDeclaringClass()->getName() . '::' . $param->getDeclaringFunction()->getName()
|
||||
));
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* compatiblity with Nette\Reflection ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Has class specified annotation?
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAnnotation($name)
|
||||
{
|
||||
return (bool) self::parseAnnotation($this, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAnnotation($name)
|
||||
{
|
||||
$res = self::parseAnnotation($this, $name);
|
||||
return $res ? end($res) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return MethodReflection
|
||||
*/
|
||||
public function getMethod($name)
|
||||
{
|
||||
return new MethodReflection($this->getName(), $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return MethodReflection[]
|
||||
*/
|
||||
public function getMethods($filter = -1)
|
||||
{
|
||||
foreach ($res = parent::getMethods($filter) as $key => $val) {
|
||||
$res[$key] = new MethodReflection($this->getName(), $val->getName());
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
trigger_error("getReflection()->$name is deprecated.", E_USER_DEPRECATED);
|
||||
return (new ClassType($this->getName()))->$name;
|
||||
}
|
||||
|
||||
|
||||
public function __call($name, $args)
|
||||
{
|
||||
if (method_exists(ClassType::class, $name)) {
|
||||
trigger_error("getReflection()->$name() is deprecated, use Nette\\Reflection\\ClassType::from(\$presenter)->$name()", E_USER_DEPRECATED);
|
||||
return call_user_func_array([new ClassType($this->getName()), $name], $args);
|
||||
}
|
||||
Nette\Utils\ObjectMixin::strictCall(get_class($this), $name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Control is renderable Presenter component.
|
||||
*
|
||||
* @property-read ITemplate|Nette\Bridges\ApplicationLatte\Template|\stdClass $template
|
||||
*/
|
||||
abstract class Control extends Component implements IRenderable
|
||||
{
|
||||
/** @var bool */
|
||||
public $snippetMode;
|
||||
|
||||
/** @var ITemplateFactory */
|
||||
private $templateFactory;
|
||||
|
||||
/** @var ITemplate */
|
||||
private $template;
|
||||
|
||||
/** @var array */
|
||||
private $invalidSnippets = [];
|
||||
|
||||
|
||||
/********************* template factory ****************d*g**/
|
||||
|
||||
|
||||
public function setTemplateFactory(ITemplateFactory $templateFactory)
|
||||
{
|
||||
$this->templateFactory = $templateFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return ITemplate
|
||||
*/
|
||||
public function getTemplate()
|
||||
{
|
||||
if ($this->template === null) {
|
||||
$value = $this->createTemplate();
|
||||
if (!$value instanceof ITemplate && $value !== null) {
|
||||
$class2 = get_class($value);
|
||||
$class = get_class($this);
|
||||
throw new Nette\UnexpectedValueException("Object returned by $class::createTemplate() must be instance of Nette\\Application\\UI\\ITemplate, '$class2' given.");
|
||||
}
|
||||
$this->template = $value;
|
||||
}
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return ITemplate
|
||||
*/
|
||||
protected function createTemplate()
|
||||
{
|
||||
$templateFactory = $this->templateFactory ?: $this->getPresenter()->getTemplateFactory();
|
||||
return $templateFactory->createTemplate($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Descendant can override this method to customize template compile-time filters.
|
||||
* @param ITemplate
|
||||
* @return void
|
||||
*/
|
||||
public function templatePrepareFilters($template)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the message to template, that can be displayed after redirect.
|
||||
* @param string
|
||||
* @param string
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function flashMessage($message, $type = 'info')
|
||||
{
|
||||
$id = $this->getParameterId('flash');
|
||||
$messages = $this->getPresenter()->getFlashSession()->$id;
|
||||
$messages[] = $flash = (object) [
|
||||
'message' => $message,
|
||||
'type' => $type,
|
||||
];
|
||||
$this->getTemplate()->flashes = $messages;
|
||||
$this->getPresenter()->getFlashSession()->$id = $messages;
|
||||
return $flash;
|
||||
}
|
||||
|
||||
|
||||
/********************* rendering ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Forces control or its snippet to repaint.
|
||||
* @return void
|
||||
*/
|
||||
public function redrawControl($snippet = null, $redraw = true)
|
||||
{
|
||||
if ($redraw) {
|
||||
$this->invalidSnippets[$snippet === null ? "\0" : $snippet] = true;
|
||||
|
||||
} elseif ($snippet === null) {
|
||||
$this->invalidSnippets = [];
|
||||
|
||||
} else {
|
||||
$this->invalidSnippets[$snippet] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function invalidateControl($snippet = null)
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated; use $this->redrawControl($snippet) instead.', E_USER_DEPRECATED);
|
||||
$this->redrawControl($snippet);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function validateControl($snippet = null)
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated; use $this->redrawControl($snippet, false) instead.', E_USER_DEPRECATED);
|
||||
$this->redrawControl($snippet, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is required to repaint the control or its snippet?
|
||||
* @param string snippet name
|
||||
* @return bool
|
||||
*/
|
||||
public function isControlInvalid($snippet = null)
|
||||
{
|
||||
if ($snippet === null) {
|
||||
if (count($this->invalidSnippets) > 0) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
$queue = [$this];
|
||||
do {
|
||||
foreach (array_shift($queue)->getComponents() as $component) {
|
||||
if ($component instanceof IRenderable) {
|
||||
if ($component->isControlInvalid()) {
|
||||
// $this->invalidSnippets['__child'] = true; // as cache
|
||||
return true;
|
||||
}
|
||||
|
||||
} elseif ($component instanceof Nette\ComponentModel\IContainer) {
|
||||
$queue[] = $component;
|
||||
}
|
||||
}
|
||||
} while ($queue);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} elseif (isset($this->invalidSnippets[$snippet])) {
|
||||
return $this->invalidSnippets[$snippet];
|
||||
} else {
|
||||
return isset($this->invalidSnippets["\0"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns snippet HTML ID.
|
||||
* @param string snippet name
|
||||
* @return string
|
||||
*/
|
||||
public function getSnippetId($name = null)
|
||||
{
|
||||
// HTML 4 ID & NAME: [A-Za-z][A-Za-z0-9:_.-]*
|
||||
return 'snippet-' . $this->getUniqueId() . '-' . $name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Web form adapted for Presenter.
|
||||
*/
|
||||
class Form extends Nette\Forms\Form implements ISignalReceiver
|
||||
{
|
||||
/** @var callable[] function (self $sender); Occurs when form is attached to presenter */
|
||||
public $onAnchor;
|
||||
|
||||
|
||||
/**
|
||||
* Application form constructor.
|
||||
*/
|
||||
public function __construct(Nette\ComponentModel\IContainer $parent = null, $name = null)
|
||||
{
|
||||
parent::__construct();
|
||||
if ($parent !== null) {
|
||||
$parent->addComponent($this, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function validateParent(Nette\ComponentModel\IContainer $parent)
|
||||
{
|
||||
parent::validateParent($parent);
|
||||
$this->monitor(Presenter::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the presenter where this component belongs to.
|
||||
* @param bool throw exception if presenter doesn't exist?
|
||||
* @return Presenter|null
|
||||
*/
|
||||
public function getPresenter($throw = true)
|
||||
{
|
||||
return $this->lookup(Presenter::class, $throw);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will be called when the component (or component's parent)
|
||||
* becomes attached to a monitored object. Do not call this method yourself.
|
||||
* @param Nette\ComponentModel\IComponent
|
||||
* @return void
|
||||
*/
|
||||
protected function attached($presenter)
|
||||
{
|
||||
if ($presenter instanceof Presenter) {
|
||||
if (!isset($this->getElementPrototype()->id)) {
|
||||
$this->getElementPrototype()->id = 'frm-' . $this->lookupPath(Presenter::class);
|
||||
}
|
||||
if (!$this->getAction()) {
|
||||
$this->setAction(new Link($presenter, 'this'));
|
||||
}
|
||||
|
||||
$controls = $this->getControls();
|
||||
if (iterator_count($controls) && $this->isSubmitted()) {
|
||||
foreach ($controls as $control) {
|
||||
if (!$control->isDisabled()) {
|
||||
$control->loadHttpData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->onAnchor($this);
|
||||
}
|
||||
parent::attached($presenter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells if the form is anchored.
|
||||
* @return bool
|
||||
*/
|
||||
public function isAnchored()
|
||||
{
|
||||
return (bool) $this->getPresenter(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal: returns submitted HTTP data or null when form was not submitted.
|
||||
* @return array|null
|
||||
*/
|
||||
protected function receiveHttpData()
|
||||
{
|
||||
$presenter = $this->getPresenter();
|
||||
if (!$presenter->isSignalReceiver($this, 'submit')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = $presenter->getRequest();
|
||||
if ($request->isMethod('forward') || $request->isMethod('post') !== $this->isMethod('post')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->isMethod('post')) {
|
||||
return Nette\Utils\Arrays::mergeTree($request->getPost(), $request->getFiles());
|
||||
} else {
|
||||
return $request->getParameters();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function beforeRender()
|
||||
{
|
||||
parent::beforeRender();
|
||||
$key = ($this->isMethod('post') ? '_' : '') . Presenter::SIGNAL_KEY;
|
||||
if (!isset($this[$key])) {
|
||||
$do = $this->lookupPath(Presenter::class) . self::NAME_SEPARATOR . 'submit';
|
||||
$this[$key] = (new Nette\Forms\Controls\HiddenField($do))->setOmitted()->setHtmlId(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* interface ISignalReceiver ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* This method is called by presenter.
|
||||
* @param string
|
||||
* @return void
|
||||
*/
|
||||
public function signalReceived($signal)
|
||||
{
|
||||
if ($signal === 'submit') {
|
||||
if (!$this->getPresenter()->getRequest()->hasFlag(Nette\Application\Request::RESTORED)) {
|
||||
$this->fireEvents();
|
||||
}
|
||||
} else {
|
||||
$class = get_class($this);
|
||||
throw new BadSignalException("Missing handler for signal '$signal' in $class.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
|
||||
/**
|
||||
* Component with ability to repaint.
|
||||
*/
|
||||
interface IRenderable
|
||||
{
|
||||
|
||||
/**
|
||||
* Forces control to repaint.
|
||||
* @return void
|
||||
*/
|
||||
function redrawControl();
|
||||
|
||||
/**
|
||||
* Is required to repaint the control?
|
||||
* @return bool
|
||||
*/
|
||||
function isControlInvalid();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
|
||||
/**
|
||||
* Component with ability to receive signal.
|
||||
*/
|
||||
interface ISignalReceiver
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return void
|
||||
*/
|
||||
function signalReceived($signal); // handleSignal
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
|
||||
/**
|
||||
* Component with ability to save and load its state.
|
||||
*/
|
||||
interface IStatePersistent
|
||||
{
|
||||
|
||||
/**
|
||||
* Loads state informations.
|
||||
* @return void
|
||||
*/
|
||||
function loadState(array $params);
|
||||
|
||||
/**
|
||||
* Saves state informations for next request.
|
||||
* @return void
|
||||
*/
|
||||
function saveState(array &$params);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
|
||||
/**
|
||||
* Defines template.
|
||||
*/
|
||||
interface ITemplate
|
||||
{
|
||||
|
||||
/**
|
||||
* Renders template to output.
|
||||
* @return void
|
||||
*/
|
||||
function render();
|
||||
|
||||
/**
|
||||
* Sets the path to the template file.
|
||||
* @param string
|
||||
* @return static
|
||||
*/
|
||||
function setFile($file);
|
||||
|
||||
/**
|
||||
* Returns the path to the template file.
|
||||
* @return string|null
|
||||
*/
|
||||
function getFile();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
|
||||
/**
|
||||
* Defines ITemplate factory.
|
||||
*/
|
||||
interface ITemplateFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* @return ITemplate
|
||||
*/
|
||||
function createTemplate(Control $control = null);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
|
||||
/**
|
||||
* Link generation exception.
|
||||
*/
|
||||
class InvalidLinkException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Lazy encapsulation of Component::link().
|
||||
* Do not instantiate directly, use Component::lazyLink()
|
||||
*/
|
||||
class Link
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Component */
|
||||
private $component;
|
||||
|
||||
/** @var string */
|
||||
private $destination;
|
||||
|
||||
/** @var array */
|
||||
private $params;
|
||||
|
||||
|
||||
/**
|
||||
* Link specification.
|
||||
*/
|
||||
public function __construct(Component $component, $destination, array $params = [])
|
||||
{
|
||||
$this->component = $component;
|
||||
$this->destination = $destination;
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns link destination.
|
||||
* @return string
|
||||
*/
|
||||
public function getDestination()
|
||||
{
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes link parameter.
|
||||
* @param string
|
||||
* @param mixed
|
||||
* @return static
|
||||
*/
|
||||
public function setParameter($key, $value)
|
||||
{
|
||||
$this->params[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns link parameter.
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParameter($key)
|
||||
{
|
||||
return isset($this->params[$key]) ? $this->params[$key] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns link parameters.
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts link to URL.
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
try {
|
||||
return $this->component->link($this->destination, $this->params);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
if (isset($e)) {
|
||||
if (func_num_args()) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
use Nette;
|
||||
use Nette\Reflection\Method;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MethodReflection extends \ReflectionMethod
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/**
|
||||
* Has method specified annotation?
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAnnotation($name)
|
||||
{
|
||||
return (bool) ComponentReflection::parseAnnotation($this, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAnnotation($name)
|
||||
{
|
||||
$res = ComponentReflection::parseAnnotation($this, $name);
|
||||
return $res ? end($res) : null;
|
||||
}
|
||||
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
|
||||
return parent::getDeclaringClass()->getName() . '::' . $this->getName() . '()';
|
||||
}
|
||||
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
trigger_error("getMethod('{$this->getName()}')->$name is deprecated.", E_USER_DEPRECATED);
|
||||
return (new Method(parent::getDeclaringClass()->getName(), $this->getName()))->$name;
|
||||
}
|
||||
|
||||
|
||||
public function __call($name, $args)
|
||||
{
|
||||
trigger_error("getMethod('{$this->getName()}')->$name() is deprecated, use Nette\\Reflection\\Method::from(\$presenter, '{$this->getName()}')->$name()", E_USER_DEPRECATED);
|
||||
return call_user_func_array([new Method(parent::getDeclaringClass()->getName(), $this->getName()), $name], $args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application\UI;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Component multiplier.
|
||||
*/
|
||||
class Multiplier extends Component
|
||||
{
|
||||
/** @var callable */
|
||||
private $factory;
|
||||
|
||||
|
||||
public function __construct(callable $factory)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
|
||||
protected function createComponent($name)
|
||||
{
|
||||
return call_user_func($this->factory, $name, $this);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Application;
|
||||
|
||||
use Nette\Http;
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when user attempts to terminate the current presenter or application.
|
||||
* This is special "silent exception" with no error message or code.
|
||||
*/
|
||||
class AbortException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Application fatal error.
|
||||
*/
|
||||
class ApplicationException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a presenter cannot be loaded.
|
||||
*/
|
||||
class InvalidPresenterException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates client error with HTTP code 4xx.
|
||||
*/
|
||||
class BadRequestException extends \Exception
|
||||
{
|
||||
/** @var int */
|
||||
protected $code = Http\IResponse::S404_NOT_FOUND;
|
||||
|
||||
|
||||
public function __construct($message = '', $httpCode = 0, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $httpCode ?: $this->code, $previous);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHttpCode()
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forbidden request exception - access denied.
|
||||
*/
|
||||
class ForbiddenRequestException extends BadRequestException
|
||||
{
|
||||
/** @var int */
|
||||
protected $code = Http\IResponse::S403_FORBIDDEN;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Default error page.
|
||||
* @param int $code
|
||||
*/
|
||||
|
||||
namespace NetteModule;
|
||||
|
||||
$messages = [
|
||||
0 => ['Oops...', 'Your browser sent a request that this server could not understand or process.'],
|
||||
403 => ['Access Denied', 'You do not have permission to view this page. Please try contact the web site administrator if you believe you should be able to view this page.'],
|
||||
404 => ['Page Not Found', 'The page you requested could not be found. It is possible that the address is incorrect, or that the page no longer exists. Please use a search engine to find what you are looking for.'],
|
||||
405 => ['Method Not Allowed', 'The requested method is not allowed for the URL.'],
|
||||
410 => ['Page Not Found', 'The page you requested has been taken off the site. We apologize for the inconvenience.'],
|
||||
500 => ['Server Error', 'We\'re sorry! The server encountered an internal error and was unable to complete your request. Please try again later.'],
|
||||
];
|
||||
$message = isset($messages[$code]) ? $messages[$code] : $messages[0];
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<meta name=robots content=noindex>
|
||||
<meta name=generator content="Nette Framework">
|
||||
<style>
|
||||
body { color: #333; background: white; width: 500px; margin: 100px auto }
|
||||
h1 { font: bold 47px/1.5 sans-serif; margin: .6em 0 }
|
||||
p { font: 21px/1.5 Georgia,serif; margin: 1.5em 0 }
|
||||
small { font-size: 70%; color: gray }
|
||||
</style>
|
||||
|
||||
<title><?= $message[0] ?></title>
|
||||
|
||||
<h1><?= $message[0] ?></h1>
|
||||
|
||||
<p><?= $message[1] ?></p>
|
||||
|
||||
<?php if ($code): ?><p><small>error <?= $code ?></small></p><?php endif ?>
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationDI;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Nette;
|
||||
use Nette\Application\UI;
|
||||
use Tracy;
|
||||
|
||||
|
||||
/**
|
||||
* Application extension for Nette DI.
|
||||
*/
|
||||
class ApplicationExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public $defaults = [
|
||||
'debugger' => null,
|
||||
'errorPresenter' => 'Nette:Error',
|
||||
'catchExceptions' => null,
|
||||
'mapping' => null,
|
||||
'scanDirs' => [],
|
||||
'scanComposer' => null,
|
||||
'scanFilter' => 'Presenter',
|
||||
'silentLinks' => false,
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
private $debugMode;
|
||||
|
||||
/** @var int */
|
||||
private $invalidLinkMode;
|
||||
|
||||
/** @var string */
|
||||
private $tempFile;
|
||||
|
||||
|
||||
public function __construct($debugMode = false, array $scanDirs = null, $tempDir = null)
|
||||
{
|
||||
$this->defaults['debugger'] = interface_exists(Tracy\IBarPanel::class);
|
||||
$this->defaults['scanDirs'] = (array) $scanDirs;
|
||||
$this->defaults['scanComposer'] = class_exists(ClassLoader::class);
|
||||
$this->defaults['catchExceptions'] = !$debugMode;
|
||||
$this->debugMode = $debugMode;
|
||||
$this->tempFile = $tempDir ? $tempDir . '/' . urlencode(__CLASS__) : null;
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
$config = $this->validateConfig($this->defaults);
|
||||
$builder = $this->getContainerBuilder();
|
||||
$builder->addExcludedClasses([UI\Presenter::class]);
|
||||
|
||||
$this->invalidLinkMode = $this->debugMode
|
||||
? UI\Presenter::INVALID_LINK_TEXTUAL | ($config['silentLinks'] ? 0 : UI\Presenter::INVALID_LINK_WARNING)
|
||||
: UI\Presenter::INVALID_LINK_WARNING;
|
||||
|
||||
$application = $builder->addDefinition($this->prefix('application'))
|
||||
->setFactory(Nette\Application\Application::class)
|
||||
->addSetup('$catchExceptions', [$config['catchExceptions']])
|
||||
->addSetup('$errorPresenter', [$config['errorPresenter']]);
|
||||
|
||||
if ($config['debugger']) {
|
||||
$application->addSetup('Nette\Bridges\ApplicationTracy\RoutingPanel::initializePanel');
|
||||
}
|
||||
|
||||
$touch = $this->debugMode && $config['scanDirs'] ? $this->tempFile : null;
|
||||
$presenterFactory = $builder->addDefinition($this->prefix('presenterFactory'))
|
||||
->setClass(Nette\Application\IPresenterFactory::class)
|
||||
->setFactory(Nette\Application\PresenterFactory::class, [new Nette\DI\Statement(
|
||||
Nette\Bridges\ApplicationDI\PresenterFactoryCallback::class, [1 => $this->invalidLinkMode, $touch]
|
||||
)]);
|
||||
|
||||
if ($config['mapping']) {
|
||||
$presenterFactory->addSetup('setMapping', [$config['mapping']]);
|
||||
}
|
||||
|
||||
$builder->addDefinition($this->prefix('linkGenerator'))
|
||||
->setFactory(Nette\Application\LinkGenerator::class, [
|
||||
1 => new Nette\DI\Statement('@Nette\Http\IRequest::getUrl'),
|
||||
]);
|
||||
|
||||
if ($this->name === 'application') {
|
||||
$builder->addAlias('application', $this->prefix('application'));
|
||||
$builder->addAlias('nette.presenterFactory', $this->prefix('presenterFactory'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function beforeCompile()
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
$all = [];
|
||||
|
||||
foreach ($builder->findByType(Nette\Application\IPresenter::class) as $def) {
|
||||
$all[$def->getClass()] = $def;
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
foreach ($this->findPresenters() as $class) {
|
||||
if (empty($all[$class])) {
|
||||
$all[$class] = $builder->addDefinition($this->prefix(++$counter))->setClass($class);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($all as $def) {
|
||||
$def->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT)
|
||||
->addTag('nette.presenter', $def->getClass());
|
||||
|
||||
if (is_subclass_of($def->getClass(), UI\Presenter::class)) {
|
||||
$def->addSetup('$invalidLinkMode', [$this->invalidLinkMode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @return string[] */
|
||||
private function findPresenters()
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
$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'];
|
||||
$robot->rebuild();
|
||||
$classes = array_keys($robot->getIndexedClasses());
|
||||
$this->getContainerBuilder()->addDependency($this->tempFile);
|
||||
}
|
||||
|
||||
if ($config['scanComposer']) {
|
||||
$rc = new \ReflectionClass(ClassLoader::class);
|
||||
$classFile = dirname($rc->getFileName()) . '/autoload_classmap.php';
|
||||
if (is_file($classFile)) {
|
||||
$this->getContainerBuilder()->addDependency($classFile);
|
||||
$classes = array_merge($classes, array_keys(call_user_func(function ($path) {
|
||||
return require $path;
|
||||
}, $classFile)));
|
||||
}
|
||||
}
|
||||
|
||||
$presenters = [];
|
||||
foreach (array_unique($classes) as $class) {
|
||||
if (
|
||||
strpos($class, $config['scanFilter']) !== false
|
||||
&& class_exists($class)
|
||||
&& ($rc = new \ReflectionClass($class))
|
||||
&& $rc->implementsInterface(Nette\Application\IPresenter::class)
|
||||
&& !$rc->isAbstract()
|
||||
) {
|
||||
$presenters[] = $rc->getName();
|
||||
}
|
||||
}
|
||||
return $presenters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationDI;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Latte extension for Nette DI.
|
||||
*/
|
||||
class LatteExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public $defaults = [
|
||||
'xhtml' => false,
|
||||
'macros' => [],
|
||||
'templateClass' => null,
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
private $debugMode;
|
||||
|
||||
/** @var string */
|
||||
private $tempDir;
|
||||
|
||||
|
||||
public function __construct($tempDir, $debugMode = false)
|
||||
{
|
||||
$this->tempDir = $tempDir;
|
||||
$this->debugMode = $debugMode;
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
if (!class_exists(Latte\Engine::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $this->validateConfig($this->defaults);
|
||||
$builder = $this->getContainerBuilder();
|
||||
|
||||
$builder->addDefinition($this->prefix('latteFactory'))
|
||||
->setFactory(Latte\Engine::class)
|
||||
->addSetup('setTempDirectory', [$this->tempDir])
|
||||
->addSetup('setAutoRefresh', [$this->debugMode])
|
||||
->addSetup('setContentType', [$config['xhtml'] ? Latte\Compiler::CONTENT_XHTML : Latte\Compiler::CONTENT_HTML])
|
||||
->addSetup('Nette\Utils\Html::$xhtml = ?', [(bool) $config['xhtml']])
|
||||
->setImplement(Nette\Bridges\ApplicationLatte\ILatteFactory::class);
|
||||
|
||||
$builder->addDefinition($this->prefix('templateFactory'))
|
||||
->setClass(Nette\Application\UI\ITemplateFactory::class)
|
||||
->setFactory(Nette\Bridges\ApplicationLatte\TemplateFactory::class)
|
||||
->setArguments(['templateClass' => $config['templateClass']]);
|
||||
|
||||
foreach ($config['macros'] as $macro) {
|
||||
$this->addMacro($macro);
|
||||
}
|
||||
|
||||
if ($this->name === 'latte') {
|
||||
$builder->addAlias('nette.latteFactory', $this->prefix('latteFactory'));
|
||||
$builder->addAlias('nette.templateFactory', $this->prefix('templateFactory'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return void
|
||||
*/
|
||||
public function addMacro($macro)
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
$definition = $builder->getDefinition($this->prefix('latteFactory'));
|
||||
|
||||
if (isset($macro[0]) && $macro[0] === '@') {
|
||||
if (strpos($macro, '::') === false) {
|
||||
$method = 'install';
|
||||
} else {
|
||||
list($macro, $method) = explode('::', $macro);
|
||||
}
|
||||
$definition->addSetup('?->onCompile[] = function ($engine) { ?->' . $method . '($engine->getCompiler()); }', ['@self', $macro]);
|
||||
|
||||
} else {
|
||||
if (strpos($macro, '::') === false && class_exists($macro)) {
|
||||
$macro .= '::install';
|
||||
}
|
||||
$definition->addSetup('?->onCompile[] = function ($engine) { ' . $macro . '($engine->getCompiler()); }', ['@self']);
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationDI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PresenterFactory callback.
|
||||
* @internal
|
||||
*/
|
||||
class PresenterFactoryCallback
|
||||
{
|
||||
/** @var Nette\DI\Container */
|
||||
private $container;
|
||||
|
||||
/** @var int */
|
||||
private $invalidLinkMode;
|
||||
|
||||
/** @var string|null */
|
||||
private $touchToRefresh;
|
||||
|
||||
|
||||
public function __construct(Nette\DI\Container $container, $invalidLinkMode, $touchToRefresh)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->invalidLinkMode = $invalidLinkMode;
|
||||
$this->touchToRefresh = $touchToRefresh;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Nette\Application\IPresenter
|
||||
*/
|
||||
public function __invoke($class)
|
||||
{
|
||||
$services = array_keys($this->container->findByTag('nette.presenter'), $class, true);
|
||||
if (count($services) > 1) {
|
||||
throw new Nette\Application\InvalidPresenterException("Multiple services of type $class found: " . implode(', ', $services) . '.');
|
||||
|
||||
} elseif (!$services) {
|
||||
if ($this->touchToRefresh) {
|
||||
touch($this->touchToRefresh);
|
||||
}
|
||||
|
||||
$presenter = $this->container->createInstance($class);
|
||||
$this->container->callInjects($presenter);
|
||||
if ($presenter instanceof Nette\Application\UI\Presenter && $presenter->invalidLinkMode === null) {
|
||||
$presenter->invalidLinkMode = $this->invalidLinkMode;
|
||||
}
|
||||
return $presenter;
|
||||
}
|
||||
|
||||
return $this->container->createService($services[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationDI;
|
||||
|
||||
use Nette;
|
||||
use Tracy;
|
||||
|
||||
|
||||
/**
|
||||
* Routing extension for Nette DI.
|
||||
*/
|
||||
class RoutingExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public $defaults = [
|
||||
'debugger' => null,
|
||||
'routes' => [], // of [mask => action]
|
||||
'routeClass' => null,
|
||||
'cache' => false,
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
private $debugMode;
|
||||
|
||||
|
||||
public function __construct($debugMode = false)
|
||||
{
|
||||
$this->defaults['debugger'] = interface_exists(Tracy\IBarPanel::class);
|
||||
$this->debugMode = $debugMode;
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
$config = $this->validateConfig($this->defaults);
|
||||
$builder = $this->getContainerBuilder();
|
||||
|
||||
$router = $builder->addDefinition($this->prefix('router'))
|
||||
->setClass(Nette\Application\IRouter::class)
|
||||
->setFactory(Nette\Application\Routers\RouteList::class);
|
||||
|
||||
$routeClass = $config['routeClass'] ?: 'Nette\Application\Routers\Route';
|
||||
foreach ($config['routes'] as $mask => $action) {
|
||||
$router->addSetup('$service[] = new ' . $routeClass . '(?, ?)', [$mask, $action]);
|
||||
}
|
||||
|
||||
if ($this->name === 'routing') {
|
||||
$builder->addAlias('router', $this->prefix('router'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function beforeCompile()
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
|
||||
if ($this->debugMode && $this->config['debugger'] && $application = $builder->getByType(Nette\Application\Application::class)) {
|
||||
$builder->getDefinition($application)->addSetup('@Tracy\Bar::addPanel', [
|
||||
new Nette\DI\Statement(Nette\Bridges\ApplicationTracy\RoutingPanel::class),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function afterCompile(Nette\PhpGenerator\ClassType $class)
|
||||
{
|
||||
if (!empty($this->config['cache'])) {
|
||||
$method = $class->getMethod(Nette\DI\Container::getMethodName($this->prefix('router')));
|
||||
try {
|
||||
$router = eval($method->getBody());
|
||||
if ($router instanceof Nette\Application\Routers\RouteList) {
|
||||
$router->warmupCache();
|
||||
}
|
||||
$s = serialize($router);
|
||||
} catch (\Exception $e) {
|
||||
throw new Nette\DI\ServiceCreationException('Unable to cache router due to error: ' . $e->getMessage(), 0, $e);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Nette\DI\ServiceCreationException('Unable to cache router due to error: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
$method->setBody('return unserialize(?);', [$s]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationLatte;
|
||||
|
||||
use Latte;
|
||||
|
||||
|
||||
interface ILatteFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* @return Latte\Engine
|
||||
*/
|
||||
function create();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationLatte;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Template loader.
|
||||
*/
|
||||
class Loader extends Latte\Loaders\FileLoader
|
||||
{
|
||||
/** @var Nette\Application\UI\Presenter */
|
||||
private $presenter;
|
||||
|
||||
|
||||
public function __construct(Nette\Application\UI\Presenter $presenter)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->presenter = $presenter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationLatte;
|
||||
|
||||
use Latte\Runtime\ISnippetBridge;
|
||||
use Nette;
|
||||
use Nette\Application\UI\Control;
|
||||
use Nette\Application\UI\IRenderable;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SnippetBridge implements ISnippetBridge
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Control */
|
||||
private $control;
|
||||
|
||||
/** @var \stdClass|null */
|
||||
private $payload;
|
||||
|
||||
|
||||
public function __construct(Control $control)
|
||||
{
|
||||
$this->control = $control;
|
||||
}
|
||||
|
||||
|
||||
public function isSnippetMode()
|
||||
{
|
||||
return $this->control->snippetMode;
|
||||
}
|
||||
|
||||
|
||||
public function setSnippetMode($snippetMode)
|
||||
{
|
||||
$this->control->snippetMode = $snippetMode;
|
||||
}
|
||||
|
||||
|
||||
public function needsRedraw($name)
|
||||
{
|
||||
return $this->control->isControlInvalid($name);
|
||||
}
|
||||
|
||||
|
||||
public function markRedrawn($name)
|
||||
{
|
||||
if ($name !== '') {
|
||||
$this->control->redrawControl($name, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getHtmlId($name)
|
||||
{
|
||||
return $this->control->getSnippetId($name);
|
||||
}
|
||||
|
||||
|
||||
public function addSnippet($name, $content)
|
||||
{
|
||||
if ($this->payload === null) {
|
||||
$this->payload = $this->control->getPresenter()->getPayload();
|
||||
}
|
||||
$this->payload->snippets[$this->control->getSnippetId($name)] = $content;
|
||||
}
|
||||
|
||||
|
||||
public function renderChildren()
|
||||
{
|
||||
$queue = [$this->control];
|
||||
do {
|
||||
foreach (array_shift($queue)->getComponents() as $child) {
|
||||
if ($child instanceof IRenderable) {
|
||||
if ($child->isControlInvalid()) {
|
||||
$child->snippetMode = true;
|
||||
$child->render();
|
||||
$child->snippetMode = false;
|
||||
}
|
||||
} elseif ($child instanceof Nette\ComponentModel\IContainer) {
|
||||
$queue[] = $child;
|
||||
}
|
||||
}
|
||||
} while ($queue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationLatte;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Latte powered template.
|
||||
*/
|
||||
class Template implements Nette\Application\UI\ITemplate
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Latte\Engine */
|
||||
private $latte;
|
||||
|
||||
/** @var string */
|
||||
private $file;
|
||||
|
||||
/** @var array */
|
||||
private $params = [];
|
||||
|
||||
|
||||
public function __construct(Latte\Engine $latte)
|
||||
{
|
||||
$this->latte = $latte;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Latte\Engine
|
||||
*/
|
||||
public function getLatte()
|
||||
{
|
||||
return $this->latte;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders template to output.
|
||||
* @return void
|
||||
*/
|
||||
public function render($file = null, array $params = [])
|
||||
{
|
||||
$this->latte->render($file ?: $this->file, $params + $this->params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders template to output.
|
||||
* @return string
|
||||
*/
|
||||
public function renderToString($file = null, array $params = [])
|
||||
{
|
||||
return $this->latte->renderToString($file ?: $this->file, $params + $this->params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders template to string.
|
||||
* @param can throw exceptions? (hidden parameter)
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
try {
|
||||
return $this->latte->renderToString($this->file, $this->params);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
if (isset($e)) {
|
||||
if (func_num_args()) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* template filters & helpers ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Registers run-time filter.
|
||||
* @param string|null
|
||||
* @param callable
|
||||
* @return static
|
||||
*/
|
||||
public function addFilter($name, $callback)
|
||||
{
|
||||
$this->latte->addFilter($name, $callback);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Alias for addFilter()
|
||||
* @deprecated
|
||||
*/
|
||||
public function registerHelper($name, $callback)
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getLatte()->addFilter().', E_USER_DEPRECATED);
|
||||
return $this->latte->addFilter($name, $callback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets translate adapter.
|
||||
* @return static
|
||||
*/
|
||||
public function setTranslator(Nette\Localization\ITranslator $translator = null)
|
||||
{
|
||||
$this->latte->addFilter('translate', $translator === null ? null : function (Latte\Runtime\FilterInfo $fi, ...$args) use ($translator) {
|
||||
return $translator->translate(...$args);
|
||||
});
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/********************* template parameters ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Sets the path to the template file.
|
||||
* @param string
|
||||
* @return static
|
||||
*/
|
||||
public function setFile($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new template parameter.
|
||||
* @return static
|
||||
*/
|
||||
public function add($name, $value)
|
||||
{
|
||||
if (array_key_exists($name, $this->params)) {
|
||||
throw new Nette\InvalidStateException("The variable '$name' already exists.");
|
||||
}
|
||||
$this->params[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets all parameters.
|
||||
* @param array
|
||||
* @return static
|
||||
*/
|
||||
public function setParameters(array $params)
|
||||
{
|
||||
$this->params = $params + $this->params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns array of all parameters.
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function __call($name, $args)
|
||||
{
|
||||
trigger_error('Invoking filters on Template object is deprecated, use getLatte()->invokeFilter().', E_USER_DEPRECATED);
|
||||
return $this->latte->invokeFilter($name, $args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a template parameter. Do not call directly.
|
||||
* @return void
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->params[$name] = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a template parameter. Do not call directly.
|
||||
* @return mixed value
|
||||
*/
|
||||
public function &__get($name)
|
||||
{
|
||||
if (!array_key_exists($name, $this->params)) {
|
||||
trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
|
||||
}
|
||||
|
||||
return $this->params[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether parameter is defined. Do not call directly.
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->params[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a template parameter. Do not call directly.
|
||||
* @param string name
|
||||
* @return void
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($this->params[$name]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationLatte;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
use Nette\Application\UI;
|
||||
|
||||
|
||||
/**
|
||||
* Latte powered template factory.
|
||||
*/
|
||||
class TemplateFactory implements UI\ITemplateFactory
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var callable[] function (Template $template); Occurs when a new template is created */
|
||||
public $onCreate;
|
||||
|
||||
/** @var ILatteFactory */
|
||||
private $latteFactory;
|
||||
|
||||
/** @var Nette\Http\IRequest */
|
||||
private $httpRequest;
|
||||
|
||||
/** @var Nette\Security\User */
|
||||
private $user;
|
||||
|
||||
/** @var Nette\Caching\IStorage */
|
||||
private $cacheStorage;
|
||||
|
||||
/** @var string */
|
||||
private $templateClass;
|
||||
|
||||
|
||||
public function __construct(ILatteFactory $latteFactory, Nette\Http\IRequest $httpRequest = null,
|
||||
Nette\Security\User $user = null, Nette\Caching\IStorage $cacheStorage = null, $templateClass = null)
|
||||
{
|
||||
$this->latteFactory = $latteFactory;
|
||||
$this->httpRequest = $httpRequest;
|
||||
$this->user = $user;
|
||||
$this->cacheStorage = $cacheStorage;
|
||||
if ($templateClass && (!class_exists($templateClass) || !is_a($templateClass, Template::class, true))) {
|
||||
throw new Nette\InvalidArgumentException("Class $templateClass does not extend " . Template::class . ' or it does not exist.');
|
||||
}
|
||||
$this->templateClass = $templateClass ?: Template::class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Template
|
||||
*/
|
||||
public function createTemplate(UI\Control $control = null)
|
||||
{
|
||||
$latte = $this->latteFactory->create();
|
||||
$template = new $this->templateClass($latte);
|
||||
$presenter = $control ? $control->getPresenter(false) : null;
|
||||
|
||||
if ($control instanceof UI\Presenter) {
|
||||
$latte->setLoader(new Loader($control));
|
||||
}
|
||||
|
||||
if ($latte->onCompile instanceof \Traversable) {
|
||||
$latte->onCompile = iterator_to_array($latte->onCompile);
|
||||
}
|
||||
|
||||
array_unshift($latte->onCompile, function ($latte) use ($control, $template) {
|
||||
if ($this->cacheStorage) {
|
||||
$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
|
||||
}
|
||||
UIMacros::install($latte->getCompiler());
|
||||
if (class_exists(Nette\Bridges\FormsLatte\FormMacros::class)) {
|
||||
Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
|
||||
}
|
||||
if ($control) {
|
||||
$control->templatePrepareFilters($template);
|
||||
}
|
||||
});
|
||||
|
||||
$latte->addFilter('url', 'rawurlencode'); // back compatiblity
|
||||
foreach (['normalize', 'toAscii', 'webalize', 'reverse'] as $name) {
|
||||
$latte->addFilter($name, 'Nette\Utils\Strings::' . $name);
|
||||
}
|
||||
$latte->addFilter('null', function () {});
|
||||
$latte->addFilter('modifyDate', function ($time, $delta, $unit = null) {
|
||||
return $time == null ? null : Nette\Utils\DateTime::from($time)->modify($delta . $unit); // intentionally ==
|
||||
});
|
||||
|
||||
if (!isset($latte->getFilters()['translate'])) {
|
||||
$latte->addFilter('translate', function (Latte\Runtime\FilterInfo $fi) {
|
||||
throw new Nette\InvalidStateException('Translator has not been set. Set translator using $template->setTranslator().');
|
||||
});
|
||||
}
|
||||
|
||||
// default parameters
|
||||
$template->user = $this->user;
|
||||
$template->baseUri = $template->baseUrl = $this->httpRequest ? rtrim($this->httpRequest->getUrl()->getBaseUrl(), '/') : null;
|
||||
$template->basePath = preg_replace('#https?://[^/]+#A', '', $template->baseUrl);
|
||||
$template->flashes = [];
|
||||
if ($control) {
|
||||
$template->control = $control;
|
||||
$template->presenter = $presenter;
|
||||
$latte->addProvider('uiControl', $control);
|
||||
$latte->addProvider('uiPresenter', $presenter);
|
||||
$latte->addProvider('snippetBridge', new Nette\Bridges\ApplicationLatte\SnippetBridge($control));
|
||||
if ($presenter) {
|
||||
$header = $presenter->getHttpResponse()->getHeader('Content-Security-Policy')
|
||||
?: $presenter->getHttpResponse()->getHeader('Content-Security-Policy-Report-Only');
|
||||
}
|
||||
$nonce = $presenter && preg_match('#\s\'nonce-([\w+/]+=*)\'#', (string) $header, $m) ? $m[1] : null;
|
||||
$latte->addProvider('uiNonce', $nonce);
|
||||
}
|
||||
$latte->addProvider('cacheStorage', $this->cacheStorage);
|
||||
|
||||
// back compatibility
|
||||
$template->_control = $control;
|
||||
$template->_presenter = $presenter;
|
||||
$template->netteCacheStorage = $this->cacheStorage;
|
||||
|
||||
if ($presenter instanceof UI\Presenter && $presenter->hasFlashSession()) {
|
||||
$id = $control->getParameterId('flash');
|
||||
$template->flashes = (array) $presenter->getFlashSession()->$id;
|
||||
}
|
||||
|
||||
$this->onCreate($template);
|
||||
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationLatte;
|
||||
|
||||
use Latte;
|
||||
use Latte\CompileException;
|
||||
use Latte\MacroNode;
|
||||
use Latte\PhpWriter;
|
||||
use Nette;
|
||||
use Nette\Utils\Strings;
|
||||
|
||||
|
||||
/**
|
||||
* Macros for Nette\Application\UI.
|
||||
*
|
||||
* - {link destination ...} control link
|
||||
* - {plink destination ...} presenter link
|
||||
* - {snippet ?} ... {/snippet ?} control snippet
|
||||
* - n:nonce
|
||||
*/
|
||||
class UIMacros extends Latte\Macros\MacroSet
|
||||
{
|
||||
/** @var bool|string */
|
||||
private $extends;
|
||||
|
||||
|
||||
public static function install(Latte\Compiler $compiler)
|
||||
{
|
||||
$me = new static($compiler);
|
||||
$me->addMacro('control', [$me, 'macroControl']);
|
||||
|
||||
$me->addMacro('href', null, null, function (MacroNode $node, PhpWriter $writer) use ($me) {
|
||||
return ' ?> href="<?php ' . $me->macroLink($node, $writer) . ' ?>"<?php ';
|
||||
});
|
||||
$me->addMacro('plink', [$me, 'macroLink']);
|
||||
$me->addMacro('link', [$me, 'macroLink']);
|
||||
$me->addMacro('ifCurrent', [$me, 'macroIfCurrent'], '}'); // deprecated; use n:class="$presenter->linkCurrent ? ..."
|
||||
$me->addMacro('extends', [$me, 'macroExtends']);
|
||||
$me->addMacro('layout', [$me, 'macroExtends']);
|
||||
$me->addMacro('nonce', null, null, 'echo $this->global->uiNonce ? " nonce=\"{$this->global->uiNonce}\"" : "";');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes before template parsing.
|
||||
* @return void
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$this->extends = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finishes template parsing.
|
||||
* @return array(prolog, epilog)
|
||||
*/
|
||||
public function finalize()
|
||||
{
|
||||
return [$this->extends . 'Nette\Bridges\ApplicationLatte\UIRuntime::initialize($this, $this->parentName, $this->blocks);'];
|
||||
}
|
||||
|
||||
|
||||
/********************* macros ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* {control name[:method] [params]}
|
||||
*/
|
||||
public function macroControl(MacroNode $node, PhpWriter $writer)
|
||||
{
|
||||
$words = $node->tokenizer->fetchWords();
|
||||
if (!$words) {
|
||||
throw new CompileException('Missing control name in {control}');
|
||||
}
|
||||
$name = $writer->formatWord($words[0]);
|
||||
$method = isset($words[1]) ? ucfirst($words[1]) : '';
|
||||
$method = Strings::match($method, '#^\w*\z#') ? "render$method" : "{\"render$method\"}";
|
||||
|
||||
$tokens = $node->tokenizer;
|
||||
$pos = $tokens->position;
|
||||
$param = $writer->formatArray();
|
||||
$tokens->position = $pos;
|
||||
while ($tokens->nextToken()) {
|
||||
if ($tokens->isCurrent('=>') && !$tokens->depth) {
|
||||
$wrap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($wrap)) {
|
||||
$param = substr($param, 1, -1); // removes array() or []
|
||||
}
|
||||
return "/* line $node->startLine */ "
|
||||
. ($name[0] === '$' ? "if (is_object($name)) \$_tmp = $name; else " : '')
|
||||
. '$_tmp = $this->global->uiControl->getComponent(' . $name . '); '
|
||||
. 'if ($_tmp instanceof Nette\Application\UI\IRenderable) $_tmp->redrawControl(null, false); '
|
||||
. ($node->modifiers === ''
|
||||
? "\$_tmp->$method($param);"
|
||||
: $writer->write("ob_start(function () {}); \$_tmp->$method($param); echo %modify(ob_get_clean());")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {link destination [,] [params]}
|
||||
* {plink destination [,] [params]}
|
||||
* n:href="destination [,] [params]"
|
||||
*/
|
||||
public function macroLink(MacroNode $node, PhpWriter $writer)
|
||||
{
|
||||
$node->modifiers = preg_replace('#\|safeurl\s*(?=\||\z)#i', '', $node->modifiers);
|
||||
return $writer->using($node)
|
||||
->write('echo %escape(%modify('
|
||||
. ($node->name === 'plink' ? '$this->global->uiPresenter' : '$this->global->uiControl')
|
||||
. '->link(%node.word, %node.array?)))'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {ifCurrent destination [,] [params]}
|
||||
*/
|
||||
public function macroIfCurrent(MacroNode $node, PhpWriter $writer)
|
||||
{
|
||||
if ($node->modifiers) {
|
||||
throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
|
||||
}
|
||||
return $writer->write($node->args
|
||||
? 'if ($this->global->uiPresenter->isLinkCurrent(%node.word, %node.array?)) {'
|
||||
: 'if ($this->global->uiPresenter->getLastCreatedRequestFlag("current")) {'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {extends auto}
|
||||
*/
|
||||
public function macroExtends(MacroNode $node, PhpWriter $writer)
|
||||
{
|
||||
if ($node->modifiers || $node->parentNode || $node->args !== 'auto') {
|
||||
return $this->extends = false;
|
||||
}
|
||||
$this->extends = $writer->write('$this->parentName = $this->global->uiPresenter->findLayoutTemplateFile();');
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public static function renderSnippets(Nette\Application\UI\Control $control, \stdClass $local, array $params)
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
|
||||
UIRuntime::renderSnippets($control, $local, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationLatte;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Runtime helpers for UI macros.
|
||||
* @internal
|
||||
*/
|
||||
class UIRuntime
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function initialize(Latte\Runtime\Template $template, &$parentName, array $blocks)
|
||||
{
|
||||
$providers = $template->global;
|
||||
$blocks = array_filter(array_keys($blocks), function ($s) { return $s[0] !== '_'; });
|
||||
if (
|
||||
$parentName === null
|
||||
&& $blocks
|
||||
&& !$template->getReferringTemplate()
|
||||
&& isset($providers->uiControl) && $providers->uiControl instanceof Nette\Application\UI\Presenter
|
||||
) {
|
||||
$parentName = $providers->uiControl->findLayoutTemplateFile();
|
||||
}
|
||||
|
||||
// back compatiblity
|
||||
$params = $template->getParameters();
|
||||
if (empty($providers->uiControl) && isset($params['_control'])) {
|
||||
trigger_error('Replace template variable $_control with provider: $latte->addProvider("uiControl", ...)', E_USER_DEPRECATED);
|
||||
$providers->uiControl = $params['_control'];
|
||||
}
|
||||
if (empty($providers->uiPresenter) && isset($params['_presenter'])) {
|
||||
trigger_error('Replace template variable $_presenter with provider: $latte->addProvider("uiPresenter", ...)', E_USER_DEPRECATED);
|
||||
$providers->uiPresenter = $params['_presenter'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\ApplicationTracy;
|
||||
|
||||
use Nette;
|
||||
use Nette\Application\Routers;
|
||||
use Nette\Application\UI\Presenter;
|
||||
use Tracy;
|
||||
use Tracy\Dumper;
|
||||
|
||||
|
||||
/**
|
||||
* Routing debugger for Debug Bar.
|
||||
*/
|
||||
class RoutingPanel implements Tracy\IBarPanel
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Nette\Application\IRouter */
|
||||
private $router;
|
||||
|
||||
/** @var Nette\Http\IRequest */
|
||||
private $httpRequest;
|
||||
|
||||
/** @var Nette\Application\IPresenterFactory */
|
||||
private $presenterFactory;
|
||||
|
||||
/** @var array */
|
||||
private $routers = [];
|
||||
|
||||
/** @var Nette\Application\Request */
|
||||
private $request;
|
||||
|
||||
/** @var \ReflectionClass|\ReflectionMethod */
|
||||
private $source;
|
||||
|
||||
|
||||
public static function initializePanel(Nette\Application\Application $application)
|
||||
{
|
||||
Tracy\Debugger::getBlueScreen()->addPanel(function ($e) use ($application) {
|
||||
return $e ? null : [
|
||||
'tab' => 'Nette Application',
|
||||
'panel' => '<h3>Requests</h3>' . Dumper::toHtml($application->getRequests(), [Dumper::LIVE => true])
|
||||
. '<h3>Presenter</h3>' . Dumper::toHtml($application->getPresenter(), [Dumper::LIVE => true]),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function __construct(Nette\Application\IRouter $router, Nette\Http\IRequest $httpRequest, Nette\Application\IPresenterFactory $presenterFactory)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->httpRequest = $httpRequest;
|
||||
$this->presenterFactory = $presenterFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders tab.
|
||||
* @return string
|
||||
*/
|
||||
public function getTab()
|
||||
{
|
||||
$this->analyse($this->router);
|
||||
ob_start(function () {});
|
||||
$request = $this->request;
|
||||
require __DIR__ . '/templates/RoutingPanel.tab.phtml';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders panel.
|
||||
* @return string
|
||||
*/
|
||||
public function getPanel()
|
||||
{
|
||||
ob_start(function () {});
|
||||
$request = $this->request;
|
||||
$routers = $this->routers;
|
||||
$source = $this->source;
|
||||
$hasModule = (bool) array_filter($routers, function ($rq) { return $rq['module']; });
|
||||
$url = $this->httpRequest->getUrl();
|
||||
$method = $this->httpRequest->getMethod();
|
||||
require __DIR__ . '/templates/RoutingPanel.panel.phtml';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Analyses simple route.
|
||||
* @param Nette\Application\IRouter
|
||||
* @return void
|
||||
*/
|
||||
private function analyse($router, $module = '')
|
||||
{
|
||||
if ($router instanceof Routers\RouteList) {
|
||||
foreach ($router as $subRouter) {
|
||||
$this->analyse($subRouter, $module . $router->getModule());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$matched = 'no';
|
||||
$request = $router->match($this->httpRequest);
|
||||
if ($request) {
|
||||
$request->setPresenterName($module . $request->getPresenterName());
|
||||
$matched = 'may';
|
||||
if (empty($this->request)) {
|
||||
$this->request = $request;
|
||||
$this->findSource();
|
||||
$matched = 'yes';
|
||||
}
|
||||
}
|
||||
|
||||
$this->routers[] = [
|
||||
'matched' => $matched,
|
||||
'class' => get_class($router),
|
||||
'defaults' => $router instanceof Routers\Route || $router instanceof Routers\SimpleRouter ? $router->getDefaults() : [],
|
||||
'mask' => $router instanceof Routers\Route ? $router->getMask() : null,
|
||||
'request' => $request,
|
||||
'module' => rtrim($module, ':'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
private function findSource()
|
||||
{
|
||||
$request = $this->request;
|
||||
$presenter = $request->getPresenterName();
|
||||
try {
|
||||
$class = $this->presenterFactory->getPresenterClass($presenter);
|
||||
} catch (Nette\Application\InvalidPresenterException $e) {
|
||||
return;
|
||||
}
|
||||
$rc = new \ReflectionClass($class);
|
||||
|
||||
if ($rc->isSubclassOf(Nette\Application\UI\Presenter::class)) {
|
||||
if ($request->getParameter(Presenter::SIGNAL_KEY)) {
|
||||
$method = $class::formatSignalMethod($request->getParameter(Presenter::SIGNAL_KEY));
|
||||
|
||||
} elseif ($request->getParameter(Presenter::ACTION_KEY)) {
|
||||
$action = $request->getParameter(Presenter::ACTION_KEY);
|
||||
$method = $class::formatActionMethod($action);
|
||||
if (!$rc->hasMethod($method)) {
|
||||
$method = $class::formatRenderMethod($action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->source = isset($method) && $rc->hasMethod($method) ? $rc->getMethod($method) : $rc;
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Nette\Bridges\ApplicationTracy;
|
||||
|
||||
use Nette\Application\UI\Presenter;
|
||||
use Tracy;
|
||||
use Tracy\Dumper;
|
||||
|
||||
?>
|
||||
<style class="tracy-debug">
|
||||
#tracy-debug .nette-RoutingPanel table {
|
||||
font: 9pt/1.5 Consolas, monospace;
|
||||
}
|
||||
|
||||
#tracy-debug .nette-RoutingPanel .yes td {
|
||||
background: #BDE678 !important;
|
||||
}
|
||||
|
||||
#tracy-debug .nette-RoutingPanel .may td {
|
||||
background: #C1D3FF !important;
|
||||
}
|
||||
|
||||
#tracy-debug .nette-RoutingPanel pre, #tracy-debug .nette-RoutingPanel code {
|
||||
display: inline;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#tracy-debug .nette-RoutingPanel-rel {
|
||||
background: #eee;
|
||||
white-space: nowrap
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>
|
||||
<?php if (empty($request)): ?>
|
||||
no route
|
||||
<?php else: ?>
|
||||
<?= htmlspecialchars($request->getPresenterName() . ':' . (isset($request->parameters[Presenter::ACTION_KEY]) ? $request->parameters[Presenter::ACTION_KEY] : Presenter::DEFAULT_ACTION) . (isset($request->parameters[Presenter::SIGNAL_KEY]) ? " {$request->parameters[Presenter::SIGNAL_KEY]}!" : ''), ENT_NOQUOTES, 'UTF-8') ?>
|
||||
<?php endif ?>
|
||||
</h1>
|
||||
|
||||
<div class="tracy-inner nette-RoutingPanel">
|
||||
<div class="tracy-inner-container">
|
||||
<?php if (empty($routers)): ?>
|
||||
<p>No routers defined.</p>
|
||||
|
||||
<?php else: ?>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Mask / Class</th>
|
||||
<th>Defaults</th>
|
||||
<?php if ($hasModule): ?><th>Module</th><?php endif ?>
|
||||
<th>Matched as</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<?php foreach ($routers as $router): ?>
|
||||
<tr class="<?= $router['matched'] ?>">
|
||||
<td><?= $router['matched'] === 'yes' ? '✓' : ($router['matched'] === 'may' ? '≈' : '') ?></td>
|
||||
|
||||
<td><code title="<?= htmlspecialchars($router['class'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars(isset($router['mask']) ? $router['mask'] : $router['class'], ENT_NOQUOTES, 'UTF-8') ?></code></td>
|
||||
|
||||
<td><code>
|
||||
<?php foreach ($router['defaults'] as $key => $value): ?>
|
||||
<?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8'), ' = ', is_string($value) ? htmlspecialchars($value, ENT_IGNORE, 'UTF-8') . '<br />' : Dumper::toHtml($value, [Dumper::COLLAPSE => true, Dumper::LIVE => true]) ?>
|
||||
<?php endforeach ?>
|
||||
</code></td>
|
||||
|
||||
<?php if ($hasModule): ?><td><code><?= htmlspecialchars($router['module'], ENT_NOQUOTES, 'UTF-8') ?></code></td><?php endif ?>
|
||||
|
||||
<td><?php if ($router['request']): ?><code>
|
||||
<?php $params = $router['request']->getParameters(); ?>
|
||||
<strong><?= htmlspecialchars($router['request']->getPresenterName() . ':' . (isset($params[Presenter::ACTION_KEY]) ? $params[Presenter::ACTION_KEY] : Presenter::DEFAULT_ACTION), ENT_NOQUOTES, 'UTF-8') ?></strong><br />
|
||||
<?php unset($params[Presenter::ACTION_KEY]) ?>
|
||||
<?php foreach ($params as $key => $value): ?>
|
||||
<?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8'), ' = ', is_string($value) ? htmlspecialchars($value, ENT_IGNORE, 'UTF-8') . '<br />' : Dumper::toHtml($value, [Dumper::COLLAPSE => true, Dumper::LIVE => true]) ?>
|
||||
<?php endforeach ?>
|
||||
</code><?php endif ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
|
||||
<p><code><?= htmlspecialchars($method, ENT_IGNORE, 'UTF-8') ?></code>
|
||||
<code><?= htmlspecialchars($url->getBaseUrl(), ENT_IGNORE, 'UTF-8') ?><span class="nette-RoutingPanel-rel"><?= htmlspecialchars($url->getRelativeUrl(), ENT_IGNORE, 'UTF-8') ?></span></code></p>
|
||||
|
||||
<?php if ($source): ?>
|
||||
<p><a href="<?= htmlspecialchars(Tracy\Helpers::editorUri($source->getFileName(), $source->getStartLine()), ENT_QUOTES, 'UTF-8') ?>"><?= $source instanceof \ReflectionClass ? $source->getName() : $source->getDeclaringClass()->getName() . '::' . $source->getName() . '()' ?></a></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Nette\Bridges\ApplicationTracy;
|
||||
|
||||
use Nette\Application\UI\Presenter;
|
||||
|
||||
?>
|
||||
<span title="Router">
|
||||
<svg viewBox="0 0 2048 2048">
|
||||
<path fill="#d86b01" d="m1559.7 1024c0 17-6 32-19 45l-670 694.48c-13 13-28 19-45 19s-32-6-45-19-19-28-19-45v-306.48h-438.52c-17 0-32-6-45-19s-19-28-19-45v-642c0-17 6-32 19-45s28-19 45-19h438.52v-309.41c0-17 6-32 19-45s28-19 45-19 32 6 45 19l670 691.41c13 13 19 28 19 45z"/>
|
||||
<path d="m1914.7 1505c0 79-31 147-87 204-56 56-124 85-203 85h-320c-9 0-16-3-22-9-14-23-21-90 3-110 5-4 12-6 21-6h320c44 0 82-16 113-47s47-69 47-113v-962c0-44-16-82-47-113s-69-47-113-47h-312c-11 0-21-3-30-9-15-25-21-90 3-110 5-4 12-6 21-6h320c79 0 147 28 204 85 56 56 82 124 82 204-9 272 9 649 0 954z" fill-opacity=".5" fill="#d86b01"/>
|
||||
</svg><span class="tracy-label"><?php if (empty($request)): ?>no route<?php else: echo htmlspecialchars($request->getPresenterName() . ':' . (isset($request->parameters[Presenter::ACTION_KEY]) ? $request->parameters[Presenter::ACTION_KEY] : Presenter::DEFAULT_ACTION) . (isset($request->parameters[Presenter::SIGNAL_KEY]) ? " {$request->parameters[Presenter::SIGNAL_KEY]}!" : ''), ENT_NOQUOTES, 'UTF-8'); endif ?></span>
|
||||
</span>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
class_alias('Nette\Application\UI\Component', 'Nette\Application\UI\PresenterComponent');
|
||||
class_alias('Nette\Application\UI\ComponentReflection', 'Nette\Application\UI\PresenterComponentReflection');
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "nette/bootstrap",
|
||||
"description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.",
|
||||
"keywords": ["nette", "configurator", "bootstrapping"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"nette/di": "~2.4.7",
|
||||
"nette/utils": "~2.4"
|
||||
},
|
||||
"suggest": {
|
||||
"nette/robot-loader": "to use Configurator::createRobotLoader()",
|
||||
"tracy/tracy": "to use Configurator::enableTracy()"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/application": "~2.3",
|
||||
"nette/caching": "~2.3",
|
||||
"nette/database": "~2.3",
|
||||
"nette/forms": "~2.3",
|
||||
"nette/http": "~2.4.0",
|
||||
"nette/mail": "~2.3",
|
||||
"nette/robot-loader": "^2.4.2 || ^3.0",
|
||||
"nette/safe-stream": "~2.2",
|
||||
"nette/security": "~2.3",
|
||||
"nette/tester": "~2.0",
|
||||
"latte/latte": "~2.2",
|
||||
"tracy/tracy": "^2.4.1"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/nette": "<2.2"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
How to contribute & use the issue tracker
|
||||
=========================================
|
||||
|
||||
The issue tracker is the preferred channel for bug reports, features requests
|
||||
and submitting pull requests, but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for personal support requests (use
|
||||
[Nette forum](https://forum.nette.org) or [Stack Overflow](http://stackoverflow.com)).
|
||||
|
||||
* Please **do not** derail or troll issues. Keep the discussion on topic and
|
||||
respect the opinions of others.
|
||||
|
||||
* Use the GitHub **issue search** — check if the issue has already been
|
||||
reported.
|
||||
|
||||
A good **bug report** shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
**Feature requests** are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature.
|
||||
|
||||
Nette welcomes **pull requests**. If you'd like to contribute, please take a moment
|
||||
to [read the guidelines](https://nette.org/en/contributing) in order to make
|
||||
the contribution process easy and effective for everyone involved.
|
||||
|
||||
Thanks!
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
Nette Bootstrap
|
||||
===============
|
||||
|
||||
[](https://packagist.org/packages/nette/bootstrap)
|
||||
[](https://travis-ci.org/nette/bootstrap)
|
||||
[](https://coveralls.io/github/nette/bootstrap?branch=master)
|
||||
[](https://github.com/nette/bootstrap/releases)
|
||||
[](https://github.com/nette/bootstrap/blob/master/license.md)
|
||||
|
||||
File `bootstrap.php` loads Nette Framework and all libraries that we depend on:
|
||||
|
||||
```php
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
```
|
||||
|
||||
Class `Configurator` creates so called DI container and handles application initialization.
|
||||
|
||||
```php
|
||||
$configurator = new Nette\Configurator;
|
||||
```
|
||||
|
||||
Activates Tracy in strict mode:
|
||||
|
||||
```php
|
||||
//$configurator->setDebugMode(true);
|
||||
$configurator->enableTracy(__DIR__ . '/../log');
|
||||
```
|
||||
|
||||
Setup directory for temporary files
|
||||
|
||||
```php
|
||||
$configurator->setTempDirectory(__DIR__ . '/../temp');
|
||||
```
|
||||
|
||||
Activate [autoloading](https://doc.nette.org/en/auto-loading#toc-nette-loaders-robotloader), that will automatically load all the files with our classes:
|
||||
|
||||
```php
|
||||
$configurator->createRobotLoader()
|
||||
->addDirectory(__DIR__)
|
||||
->addDirectory(__DIR__ . '/../vendor/others')
|
||||
->register();
|
||||
```
|
||||
|
||||
And according to the configuration files it generates a DI container. Everything else depends on it.
|
||||
|
||||
We will return this DI Container as a result of calling `app/boostrap.php`
|
||||
|
||||
```php
|
||||
$configurator->addConfig(__DIR__ . '/config/config.neon');
|
||||
$configurator->addConfig(__DIR__ . '/config/config.local.neon');
|
||||
return $configurator->createContainer();
|
||||
```
|
||||
|
||||
and we will store it as local variable in `www/index.php` and run the application:
|
||||
|
||||
```php
|
||||
$container = require __DIR__ . '/../app/bootstrap.php';
|
||||
$container->getService('application')->run();
|
||||
```
|
||||
|
||||
That's it.
|
||||
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI;
|
||||
use Tracy;
|
||||
|
||||
|
||||
/**
|
||||
* Initial system DI container generator.
|
||||
*/
|
||||
class Configurator
|
||||
{
|
||||
use SmartObject;
|
||||
|
||||
const AUTO = true,
|
||||
NONE = false;
|
||||
|
||||
const COOKIE_SECRET = 'nette-debug';
|
||||
|
||||
/** @var callable[] function (Configurator $sender, DI\Compiler $compiler); Occurs after the compiler is created */
|
||||
public $onCompile;
|
||||
|
||||
/** @var array */
|
||||
public $defaultExtensions = [
|
||||
'php' => Nette\DI\Extensions\PhpExtension::class,
|
||||
'constants' => Nette\DI\Extensions\ConstantsExtension::class,
|
||||
'extensions' => Nette\DI\Extensions\ExtensionsExtension::class,
|
||||
'application' => [Nette\Bridges\ApplicationDI\ApplicationExtension::class, ['%debugMode%', ['%appDir%'], '%tempDir%/cache']],
|
||||
'decorator' => Nette\DI\Extensions\DecoratorExtension::class,
|
||||
'cache' => [Nette\Bridges\CacheDI\CacheExtension::class, ['%tempDir%']],
|
||||
'database' => [Nette\Bridges\DatabaseDI\DatabaseExtension::class, ['%debugMode%']],
|
||||
'di' => [Nette\DI\Extensions\DIExtension::class, ['%debugMode%']],
|
||||
'forms' => Nette\Bridges\FormsDI\FormsExtension::class,
|
||||
'http' => [Nette\Bridges\HttpDI\HttpExtension::class, ['%consoleMode%']],
|
||||
'latte' => [Nette\Bridges\ApplicationDI\LatteExtension::class, ['%tempDir%/cache/latte', '%debugMode%']],
|
||||
'mail' => Nette\Bridges\MailDI\MailExtension::class,
|
||||
'routing' => [Nette\Bridges\ApplicationDI\RoutingExtension::class, ['%debugMode%']],
|
||||
'security' => [Nette\Bridges\SecurityDI\SecurityExtension::class, ['%debugMode%']],
|
||||
'session' => [Nette\Bridges\HttpDI\SessionExtension::class, ['%debugMode%', '%consoleMode%']],
|
||||
'tracy' => [Tracy\Bridges\Nette\TracyExtension::class, ['%debugMode%', '%consoleMode%']],
|
||||
'inject' => Nette\DI\Extensions\InjectExtension::class,
|
||||
];
|
||||
|
||||
/** @var string[] of classes which shouldn't be autowired */
|
||||
public $autowireExcludedClasses = [
|
||||
'stdClass',
|
||||
];
|
||||
|
||||
/** @var array */
|
||||
protected $parameters;
|
||||
|
||||
/** @var array */
|
||||
protected $dynamicParameters = [];
|
||||
|
||||
/** @var array */
|
||||
protected $services = [];
|
||||
|
||||
/** @var array [file|array, section] */
|
||||
protected $files = [];
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->parameters = $this->getDefaultParameters();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set parameter %debugMode%.
|
||||
* @param bool|string|array $value
|
||||
* @return static
|
||||
*/
|
||||
public function setDebugMode($value)
|
||||
{
|
||||
if (is_string($value) || is_array($value)) {
|
||||
$value = static::detectDebugMode($value);
|
||||
} elseif (!is_bool($value)) {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Value must be either a string, array, or boolean, %s given.', gettype($value)));
|
||||
}
|
||||
$this->parameters['debugMode'] = $value;
|
||||
$this->parameters['productionMode'] = !$this->parameters['debugMode']; // compatibility
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDebugMode()
|
||||
{
|
||||
return $this->parameters['debugMode'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets path to temporary directory.
|
||||
* @param string $path
|
||||
* @return static
|
||||
*/
|
||||
public function setTempDirectory($path)
|
||||
{
|
||||
$this->parameters['tempDir'] = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the default timezone.
|
||||
* @param string $timezone
|
||||
* @return static
|
||||
*/
|
||||
public function setTimeZone($timezone)
|
||||
{
|
||||
date_default_timezone_set($timezone);
|
||||
@ini_set('date.timezone', $timezone); // @ - function may be disabled
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new parameters. The %params% will be expanded.
|
||||
* @return static
|
||||
*/
|
||||
public function addParameters(array $params)
|
||||
{
|
||||
$this->parameters = DI\Config\Helpers::merge($params, $this->parameters);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new dynamic parameters.
|
||||
* @return static
|
||||
*/
|
||||
public function addDynamicParameters(array $params)
|
||||
{
|
||||
$this->dynamicParameters = $params + $this->dynamicParameters;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add instances of services.
|
||||
* @return static
|
||||
*/
|
||||
public function addServices(array $services)
|
||||
{
|
||||
$this->services = $services + $this->services;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultParameters()
|
||||
{
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$last = end($trace);
|
||||
$debugMode = static::detectDebugMode();
|
||||
return [
|
||||
'appDir' => isset($trace[1]['file']) ? dirname($trace[1]['file']) : null,
|
||||
'wwwDir' => isset($last['file']) ? dirname($last['file']) : null,
|
||||
'debugMode' => $debugMode,
|
||||
'productionMode' => !$debugMode,
|
||||
'consoleMode' => PHP_SAPI === 'cli',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $logDirectory
|
||||
* @param string $email
|
||||
* @return void
|
||||
*/
|
||||
public function enableTracy($logDirectory = null, $email = null)
|
||||
{
|
||||
$this->enableDebugger($logDirectory, $email);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Alias for enableTracy()
|
||||
* @param string $logDirectory
|
||||
* @param string $email
|
||||
* @return void
|
||||
*/
|
||||
public function enableDebugger($logDirectory = null, $email = null)
|
||||
{
|
||||
Tracy\Debugger::$strictMode = true;
|
||||
Tracy\Debugger::enable(!$this->parameters['debugMode'], $logDirectory, $email);
|
||||
Nette\Bridges\Framework\TracyBridge::initialize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Nette\Loaders\RobotLoader
|
||||
* @throws Nette\NotSupportedException if RobotLoader is not available
|
||||
*/
|
||||
public function createRobotLoader()
|
||||
{
|
||||
if (!class_exists(Nette\Loaders\RobotLoader::class)) {
|
||||
throw new Nette\NotSupportedException('RobotLoader not found, do you have `nette/robot-loader` package installed?');
|
||||
}
|
||||
|
||||
$loader = new Nette\Loaders\RobotLoader;
|
||||
$loader->setTempDirectory($this->getCacheDirectory() . '/Nette.RobotLoader');
|
||||
$loader->setAutoRefresh($this->parameters['debugMode']);
|
||||
return $loader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds configuration file.
|
||||
* @param string|array $file
|
||||
* @return static
|
||||
*/
|
||||
public function addConfig($file)
|
||||
{
|
||||
$section = func_num_args() > 1 ? func_get_arg(1) : null;
|
||||
if ($section !== null) {
|
||||
trigger_error('Sections in config file are deprecated.', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->files[] = [$file, $section === self::AUTO ? ($this->parameters['debugMode'] ? 'development' : 'production') : $section];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns system DI container.
|
||||
* @return DI\Container
|
||||
*/
|
||||
public function createContainer()
|
||||
{
|
||||
$class = $this->loadContainer();
|
||||
$container = new $class($this->dynamicParameters);
|
||||
foreach ($this->services as $name => $service) {
|
||||
$container->addService($name, $service);
|
||||
}
|
||||
$container->initialize();
|
||||
if (class_exists(Nette\Environment::class)) {
|
||||
Nette\Environment::setContext($container); // back compatibility
|
||||
}
|
||||
return $container;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads system DI container class and returns its name.
|
||||
* @return string
|
||||
*/
|
||||
public function loadContainer()
|
||||
{
|
||||
$loader = new DI\ContainerLoader(
|
||||
$this->getCacheDirectory() . '/Nette.Configurator',
|
||||
$this->parameters['debugMode']
|
||||
);
|
||||
$class = $loader->load(
|
||||
[$this, 'generateContainer'],
|
||||
[$this->parameters, array_keys($this->dynamicParameters), $this->files, PHP_VERSION_ID - PHP_RELEASE_VERSION]
|
||||
);
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public function generateContainer(DI\Compiler $compiler)
|
||||
{
|
||||
$compiler->addConfig(['parameters' => $this->parameters]);
|
||||
$compiler->setDynamicParameterNames(array_keys($this->dynamicParameters));
|
||||
|
||||
$loader = $this->createLoader();
|
||||
$fileInfo = [];
|
||||
foreach ($this->files as $info) {
|
||||
if (is_scalar($info[0])) {
|
||||
$fileInfo[] = "// source: $info[0] $info[1]";
|
||||
$info[0] = $loader->load($info[0], $info[1]);
|
||||
}
|
||||
$compiler->addConfig($this->fixCompatibility($info[0]));
|
||||
}
|
||||
$compiler->addDependencies($loader->getDependencies());
|
||||
|
||||
$builder = $compiler->getContainerBuilder();
|
||||
$builder->addExcludedClasses($this->autowireExcludedClasses);
|
||||
|
||||
foreach ($this->defaultExtensions as $name => $extension) {
|
||||
list($class, $args) = is_string($extension) ? [$extension, []] : $extension;
|
||||
if (class_exists($class)) {
|
||||
$args = DI\Helpers::expand($args, $this->parameters, true);
|
||||
$compiler->addExtension($name, (new \ReflectionClass($class))->newInstanceArgs($args));
|
||||
}
|
||||
}
|
||||
|
||||
$this->onCompile($this, $compiler);
|
||||
|
||||
$classes = $compiler->compile();
|
||||
return implode("\n", $fileInfo) . "\n\n" . $classes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return DI\Config\Loader
|
||||
*/
|
||||
protected function createLoader()
|
||||
{
|
||||
return new DI\Config\Loader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheDirectory()
|
||||
{
|
||||
if (empty($this->parameters['tempDir'])) {
|
||||
throw new Nette\InvalidStateException('Set path to temporary directory using setTempDirectory().');
|
||||
}
|
||||
$dir = $this->parameters['tempDir'] . '/cache';
|
||||
Nette\Utils\FileSystem::createDir($dir);
|
||||
return $dir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Back compatibility with < v2.3
|
||||
* @return array
|
||||
*/
|
||||
protected function fixCompatibility($config)
|
||||
{
|
||||
if (isset($config['nette']['security']['frames'])) {
|
||||
$config['nette']['http']['frames'] = $config['nette']['security']['frames'];
|
||||
unset($config['nette']['security']['frames']);
|
||||
}
|
||||
foreach (['application', 'cache', 'database', 'di' => 'container', 'forms', 'http',
|
||||
'latte', 'mail' => 'mailer', 'routing', 'security', 'session', 'tracy' => 'debugger', ] as $new => $old) {
|
||||
if (isset($config['nette'][$old])) {
|
||||
$new = is_int($new) ? $old : $new;
|
||||
if (isset($config[$new])) {
|
||||
throw new Nette\DeprecatedException("You can use (deprecated) section 'nette.$old' or new section '$new', but not both of them.");
|
||||
} else {
|
||||
trigger_error("Configuration section 'nette.$old' is deprecated, use section '$new' (without 'nette')", E_USER_DEPRECATED);
|
||||
}
|
||||
$config[$new] = $config['nette'][$old];
|
||||
unset($config['nette'][$old]);
|
||||
}
|
||||
}
|
||||
if (isset($config['nette']['xhtml'])) {
|
||||
trigger_error("Configuration option 'nette.xhtml' is deprecated, use section 'latte.xhtml' instead.", E_USER_DEPRECATED);
|
||||
$config['latte']['xhtml'] = $config['nette']['xhtml'];
|
||||
unset($config['nette']['xhtml']);
|
||||
}
|
||||
|
||||
if (empty($config['nette'])) {
|
||||
unset($config['nette']);
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
||||
/********************* tools ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Detects debug mode by IP addresses or computer names whitelist detection.
|
||||
* @param string|array $list
|
||||
* @return bool
|
||||
*/
|
||||
public static function detectDebugMode($list = null)
|
||||
{
|
||||
$addr = isset($_SERVER['REMOTE_ADDR'])
|
||||
? $_SERVER['REMOTE_ADDR']
|
||||
: php_uname('n');
|
||||
$secret = isset($_COOKIE[self::COOKIE_SECRET]) && is_string($_COOKIE[self::COOKIE_SECRET])
|
||||
? $_COOKIE[self::COOKIE_SECRET]
|
||||
: null;
|
||||
$list = is_string($list)
|
||||
? preg_split('#[,\s]+#', $list)
|
||||
: (array) $list;
|
||||
if (!isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !isset($_SERVER['HTTP_FORWARDED'])) {
|
||||
$list[] = '127.0.0.1';
|
||||
$list[] = '::1';
|
||||
}
|
||||
return in_array($addr, $list, true) || in_array("$secret@$addr", $list, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\Framework;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
use Nette\Framework;
|
||||
use Tracy;
|
||||
use Tracy\BlueScreen;
|
||||
use Tracy\Helpers;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes Tracy.
|
||||
*/
|
||||
class TracyBridge
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
public static function initialize()
|
||||
{
|
||||
$blueScreen = Tracy\Debugger::getBlueScreen();
|
||||
|
||||
if (class_exists(Nette\Framework::class)) {
|
||||
$version = Framework::VERSION . (Framework::REVISION ? ' (' . Framework::REVISION . ')' : '');
|
||||
Tracy\Debugger::getBar()->getPanel('Tracy:info')->data['Nette Framework'] = $version;
|
||||
$blueScreen->info[] = "Nette Framework $version";
|
||||
}
|
||||
|
||||
if (class_exists(Tracy\Bridges\Nette\Bridge::class)) {
|
||||
Tracy\Bridges\Nette\Bridge::initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
$blueScreen->addPanel(function ($e) {
|
||||
if ($e instanceof Latte\CompileException) {
|
||||
return [
|
||||
'tab' => 'Template',
|
||||
'panel' => (preg_match('#\n|\?#', $e->sourceName)
|
||||
? ''
|
||||
: '<p>'
|
||||
. (@is_file($e->sourceName) // @ - may trigger error
|
||||
? '<b>File:</b> ' . Helpers::editorLink($e->sourceName, $e->sourceLine)
|
||||
: '<b>' . htmlspecialchars($e->sourceName . ($e->sourceLine ? ':' . $e->sourceLine : '')) . '</b>')
|
||||
. '</p>')
|
||||
. '<pre class=code><div>'
|
||||
. BlueScreen::highlightLine(htmlspecialchars($e->sourceCode, ENT_IGNORE, 'UTF-8'), $e->sourceLine)
|
||||
. '</div></pre>',
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$blueScreen->addPanel(function ($e) {
|
||||
if (
|
||||
$e instanceof Nette\Neon\Exception
|
||||
&& preg_match('#line (\d+)#', $e->getMessage(), $m)
|
||||
&& ($trace = Helpers::findTrace($e->getTrace(), 'Nette\Neon\Decoder::decode'))
|
||||
) {
|
||||
return [
|
||||
'tab' => 'NEON',
|
||||
'panel' => ($trace2 = Helpers::findTrace($e->getTrace(), 'Nette\DI\Config\Adapters\NeonAdapter::load'))
|
||||
? '<p><b>File:</b> ' . Helpers::editorLink($trace2['args'][0], $m[1]) . '</p>'
|
||||
. BlueScreen::highlightFile($trace2['args'][0], $m[1])
|
||||
: BlueScreen::highlightPhp($trace['args'][0], $m[1]),
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "nette/caching",
|
||||
"description": "⏱ Nette Caching: library with easy-to-use API and many cache backends.",
|
||||
"keywords": ["nette", "cache", "journal", "sqlite", "memcached"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"nette/finder": "^2.2 || ~3.0.0",
|
||||
"nette/utils": "^2.4 || ~3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"nette/di": "^2.4 || ~3.0.0",
|
||||
"latte/latte": "^2.4",
|
||||
"tracy/tracy": "^2.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pdo_sqlite": "to use SQLiteStorage or SQLiteJournal"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/nette": "<2.2"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.5-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
How to contribute & use the issue tracker
|
||||
=========================================
|
||||
|
||||
Nette welcomes your contributions. There are several ways to help out:
|
||||
|
||||
* Create an issue on GitHub, if you have found a bug
|
||||
* Write test cases for open bug issues
|
||||
* Write fixes for open bug/feature issues, preferably with test cases included
|
||||
* Contribute to the [documentation](https://nette.org/en/writing)
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Please **do not use the issue tracker to ask questions**. We will be happy to help you
|
||||
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
**Feature requests** are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
|
||||
|
||||
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
|
||||
|
||||
Please do not fix whitespace, format code, or make a purely cosmetic patch.
|
||||
|
||||
Thanks! :heart:
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
Vendored
+239
@@ -0,0 +1,239 @@
|
||||
Nette Caching
|
||||
=============
|
||||
|
||||
[](https://packagist.org/packages/nette/caching)
|
||||
[](https://travis-ci.org/nette/caching)
|
||||
[](https://coveralls.io/github/nette/caching?branch=master)
|
||||
[](https://github.com/nette/caching/releases)
|
||||
[](https://github.com/nette/caching/blob/master/license.md)
|
||||
|
||||
Cache accelerates your application by storing data - once hardly retrieved - for future use.
|
||||
|
||||
Nette offers a very intuitive API for cache manipulation. After all, you wouldn't expect anything else, right? ;-)
|
||||
Before we show you the first example, we need to think about place where to store data physically. We can use a database, //Memcached// server,
|
||||
or the most available storage - hard drive:
|
||||
|
||||
```php
|
||||
// the `temp` directory will be the storage
|
||||
$storage = new Nette\Caching\Storages\FileStorage('temp');
|
||||
```
|
||||
|
||||
The `Nette\Caching\Storages\FileStorage` storage is very well optimized for performance and in the first place,
|
||||
it provides full atomicity of operations. What does that mean? When we use cache we can be sure we are not reading a file that is not fully
|
||||
written yet (by another thread) or that the file gets deleted "under our hands". Using the cache is therefore completely safe.
|
||||
|
||||
For manipulation with cache, we use the `Nette\Caching\Cache`:
|
||||
|
||||
```php
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
$cache = new Cache($storage); // $storage from the previous example
|
||||
```
|
||||
|
||||
Let's save the contents of the '`$data`' variable under the '`$key`' key:
|
||||
|
||||
```php
|
||||
$cache->save($key, $data);
|
||||
```
|
||||
|
||||
This way, we can read from the cache: (if there is no such item in the cache, the `null` value is returned)
|
||||
|
||||
```php
|
||||
$value = $cache->load($key);
|
||||
if ($value === null) ...
|
||||
```
|
||||
|
||||
Method `load()` has second parameter `callable` `$fallback`, which is called when there is no such item in the cache. This callback receives the array *$dependencies* by reference, which you can use for setting expiration rules.
|
||||
|
||||
```php
|
||||
$value = $cache->load($key, function(& $dependencies) {
|
||||
// some calculation
|
||||
return 15;
|
||||
});
|
||||
```
|
||||
|
||||
We could delete the item from the cache either by saving null or by calling `remove()` method:
|
||||
|
||||
```php
|
||||
$cache->save($key, null);
|
||||
// or
|
||||
$cache->remove($key);
|
||||
```
|
||||
|
||||
It's possible to save any structure to the cache, not only strings. The same applies for keys.
|
||||
|
||||
|
||||
Web applications typically consist of a number of independent parts, and if they all cache data in the same storage (for example the same directory),
|
||||
sooner or later there would be collisions in names. Nette Framework solves this by splitting the whole storage to sections
|
||||
(in the `FileStorage` case using subdirectories). Each part of the application uses it's own section with unique name, therefore no collision can occur.
|
||||
The name of the section can be passed as the second parameter to the `Cache` class constructor. (These sections are often refered to as //cache namespaces//.)
|
||||
|
||||
```php
|
||||
$cache = new Cache($storage, 'htmlOutput');
|
||||
```
|
||||
|
||||
|
||||
Caching Function Results
|
||||
-------------------------
|
||||
|
||||
Caching the result of a function or method call can be achieved using the `call()` method:
|
||||
|
||||
```php
|
||||
$name = $cache->call('gethostbyaddr', $ip);
|
||||
```
|
||||
|
||||
The `gethostbyaddr($ip)` will therefore be called only once and next time, only the value from cache will be returned. Of course, for different `$ip`,
|
||||
different results are cached.
|
||||
|
||||
Output Caching
|
||||
------------------
|
||||
|
||||
The output can be cached not only in templates:
|
||||
|
||||
```php
|
||||
if ($block = $cache->start($key)) {
|
||||
|
||||
... printing some data ...
|
||||
|
||||
$block->end(); // save the output to the cache
|
||||
}
|
||||
```
|
||||
|
||||
In case that the output is already present in the cache, the `start()` method prints it and return `null`. Otherwise, it starts to buffer the output and
|
||||
returns the `$block` object using which we finally save the data to the cache.
|
||||
|
||||
|
||||
Expiration and Invalidation
|
||||
---------------------------
|
||||
Here come two problems of storing data in the cache. First, there is a possibility that the storage is completely filled and you cannot save more data inside.
|
||||
And it may happen that some od the previously saved data will become invalid over time. Therefore, Nette Framework provides a mechanism,
|
||||
how to limit the validity of data and how to delete them in a controlled way ("to invalidate them", using the framework's terminology).
|
||||
|
||||
Data validity is set when saving the data using the third parameter of the `save()` method:
|
||||
|
||||
```php
|
||||
$cache->save($key, $data, array(
|
||||
Cache::EXPIRE => '20 minutes', // accepts also seconds or a timestamp.
|
||||
));
|
||||
```
|
||||
|
||||
It's obvious from the code itself, that we saved the data for next 20 minutes. After this period, the cache will report that there is no record
|
||||
under the '`$key`' key (ie, will return `null`). In fact, you can use any time value that is a valid value for PHP function strToTime().
|
||||
If we want to extend the validity period with each reading, it can be achieved this way:
|
||||
|
||||
```php
|
||||
$cache->save($key, $data, array(
|
||||
Cache::EXPIRE => '20 minutes',
|
||||
Cache::SLIDING => true,
|
||||
));
|
||||
```
|
||||
|
||||
Very handy is also the ability to let the data expire when a particular file is changed or one of several files.
|
||||
That can be used for stroring data resulting from parsing these files to the cache. For trouble-free functionality, it's recommended to use absolute paths.
|
||||
|
||||
```php
|
||||
$cache->save($key, $data, array(
|
||||
Cache::FILES => 'data.yaml', // an array of files can also be specified
|
||||
));
|
||||
```
|
||||
|
||||
The `Cache::FILES` criteria, of course, can be combined with the time expiration using `Cache::EXPIRE` etc.
|
||||
|
||||
The cache can also depend on other cached items. That can be used when we save the whole HTML page in the cache and under different keys, some of its fragments.
|
||||
As soon as a part changes, the whole page is invalidated.
|
||||
|
||||
```php
|
||||
$cache->save('page', $html, array(
|
||||
// will expire if frag1 or frag2 expires
|
||||
Cache::ITEMS => array('frag1', 'frag2'),
|
||||
));
|
||||
```
|
||||
|
||||
Expiration can be controlled even by your own callbacks:
|
||||
|
||||
```php
|
||||
function controlExpiration($val)
|
||||
{
|
||||
return $val;
|
||||
}
|
||||
|
||||
$cache->save($key, $value, array(
|
||||
Cache::CALLBACKS => array(array('controlExpiration', 1)),
|
||||
));
|
||||
```
|
||||
|
||||
|
||||
Expiration Using Tags and Priority
|
||||
----------------------------------
|
||||
|
||||
Very useful invalidation tool are so called //tags//. We can assign a list of tags to each item. For example, suppose we have an HTML page with an article and
|
||||
comments, which we want to cache. So we specify tags when saving to cache:
|
||||
|
||||
```php
|
||||
$cache->save($articleId, $html, array(
|
||||
Cache::TAGS => array("article/$articleId", "comments/$articleId"),
|
||||
));
|
||||
```
|
||||
|
||||
Now, let's move to the administration. Here we have a form for article editing. Together with saving the article to a database, we call the `clean()` command,
|
||||
which will delete cached items by tag:
|
||||
|
||||
```php
|
||||
$cache->clean(array(
|
||||
Cache::TAGS => array("article/$articleId"),
|
||||
));
|
||||
```
|
||||
|
||||
And in the place for adding new comments (or editing them), don't forget to invalidate appropriate tag:
|
||||
|
||||
```php
|
||||
$cache->clean(array(
|
||||
Cache::TAGS => array("comments/$articleId"),
|
||||
));
|
||||
```
|
||||
|
||||
What we have achieved? That the HTML cache will invalidate automatically. Whenever someone changes the article with ID of 10, it will force the `article/10`
|
||||
tag to invalidate and the tagged HTML page in cache is cleared. The same will happen when someone inserts a new comment below the article.
|
||||
|
||||
Similar to tags, you can control expiration by priority:
|
||||
|
||||
```php
|
||||
$cache->save($key, $value, array(
|
||||
Cache::PRIORITY => 50,
|
||||
));
|
||||
|
||||
// all cached items with priority less than or equal to 100 will be removed.
|
||||
$cache->clean(array(
|
||||
Cache::PRIORITY => 100,
|
||||
));
|
||||
```
|
||||
|
||||
|
||||
|
||||
Cache Storage
|
||||
--------
|
||||
In addition to already mentioned `FileStorage`, Nette Framework also provides `MemcachedStorage` which stores
|
||||
data to the `Memcached` server, and also `MemoryStorage` for storing data in memory for duration of the request.
|
||||
The special `DevNullStorage`, which does precisely nothing, can be used for testing, when we want to eliminate the influence of caching.
|
||||
|
||||
Of course, it's possible to create your own storage. The only requirement is to implement the `IStorage` interface.
|
||||
|
||||
|
||||
Concurrent Caching
|
||||
------------------
|
||||
|
||||
Deleting the cache is a common operation when uploading a new application version to the server. At that moment, however, the server gets pretty hard time,
|
||||
because it has to build a complete new cache. Retrieving some data can be quite difficult, for example the RobotLoader cache building.
|
||||
And moreover, if, say, 30 requests come in a short period, the resource consumption is even higher.
|
||||
|
||||
The solution is to modify application behaviour so that data are created only by one thread and others are waiting. To do this, specify the value as a callback
|
||||
or use an anonymous function:
|
||||
|
||||
```php
|
||||
$result = $cache->save($key, function() { // or callback(...)
|
||||
return buildData(); // difficult operation
|
||||
});
|
||||
```
|
||||
|
||||
Framework will ensure that the body of the function will be called only by one thread at once, and other threads will be waiting.
|
||||
If the thread fails for some reason, another gets chance.
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\CacheDI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Cache extension for Nette DI.
|
||||
*/
|
||||
class CacheExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
/** @var string */
|
||||
private $tempDir;
|
||||
|
||||
|
||||
public function __construct($tempDir)
|
||||
{
|
||||
$this->tempDir = $tempDir;
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
|
||||
if (extension_loaded('pdo_sqlite')) {
|
||||
$builder->addDefinition($this->prefix('journal'))
|
||||
->setClass(Nette\Caching\Storages\IJournal::class)
|
||||
->setFactory(Nette\Caching\Storages\SQLiteJournal::class, [$this->tempDir . '/cache/journal.s3db']);
|
||||
}
|
||||
|
||||
$builder->addDefinition($this->prefix('storage'))
|
||||
->setClass(Nette\Caching\IStorage::class)
|
||||
->setFactory(Nette\Caching\Storages\FileStorage::class, [$this->tempDir . '/cache']);
|
||||
|
||||
if ($this->name === 'cache') {
|
||||
if (extension_loaded('pdo_sqlite')) {
|
||||
$builder->addAlias('nette.cacheJournal', $this->prefix('journal'));
|
||||
}
|
||||
$builder->addAlias('cacheStorage', $this->prefix('storage'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function afterCompile(Nette\PhpGenerator\ClassType $class)
|
||||
{
|
||||
if (!$this->checkTempDir($this->tempDir . '/cache')) {
|
||||
$class->getMethod('initialize')->addBody('Nette\Caching\Storages\FileStorage::$useDirectories = false;');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function checkTempDir($dir)
|
||||
{
|
||||
Nette\Utils\FileSystem::createDir($dir);
|
||||
|
||||
// checks whether directory is writable
|
||||
$uniq = uniqid('_', true);
|
||||
if (!@mkdir("$dir/$uniq")) { // @ - is escalated to exception
|
||||
throw new Nette\InvalidStateException("Unable to write to directory '$dir'. Make this directory writable.");
|
||||
}
|
||||
|
||||
// checks whether subdirectory is writable
|
||||
$isWritable = @file_put_contents("$dir/$uniq/_", '') !== false; // @ - error is expected
|
||||
if ($isWritable) {
|
||||
unlink("$dir/$uniq/_");
|
||||
}
|
||||
rmdir("$dir/$uniq");
|
||||
return $isWritable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\CacheLatte;
|
||||
|
||||
use Latte;
|
||||
use Nette;
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
|
||||
/**
|
||||
* Macro {cache} ... {/cache}
|
||||
*/
|
||||
class CacheMacro implements Latte\IMacro
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var bool */
|
||||
private $used;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes before template parsing.
|
||||
* @return void
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$this->used = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finishes template parsing.
|
||||
* @return array(prolog, epilog)
|
||||
*/
|
||||
public function finalize()
|
||||
{
|
||||
if ($this->used) {
|
||||
return ['Nette\Bridges\CacheLatte\CacheMacro::initRuntime($this);'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* New node is found.
|
||||
* @return bool
|
||||
*/
|
||||
public function nodeOpened(Latte\MacroNode $node)
|
||||
{
|
||||
if ($node->modifiers) {
|
||||
throw new Latte\CompileException('Modifiers are not allowed in ' . $node->getNotation());
|
||||
}
|
||||
$this->used = true;
|
||||
$node->empty = false;
|
||||
$node->openingCode = Latte\PhpWriter::using($node)
|
||||
->write('<?php if (Nette\Bridges\CacheLatte\CacheMacro::createCache($this->global->cacheStorage, %var, $this->global->cacheStack, %node.array?)) { ?>',
|
||||
Nette\Utils\Random::generate()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Node is closed.
|
||||
* @return void
|
||||
*/
|
||||
public function nodeClosed(Latte\MacroNode $node)
|
||||
{
|
||||
$node->closingCode = Latte\PhpWriter::using($node)
|
||||
->write('<?php Nette\Bridges\CacheLatte\CacheMacro::endCache($this->global->cacheStack, %node.array?); } ?>');
|
||||
}
|
||||
|
||||
|
||||
/********************* run-time helpers ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function initRuntime(Latte\Runtime\Template $template)
|
||||
{
|
||||
if (!empty($template->global->cacheStack)) {
|
||||
$file = (new \ReflectionClass($template))->getFileName();
|
||||
if (@is_file($file)) { // @ - may trigger error
|
||||
end($template->global->cacheStack)->dependencies[Cache::FILES][] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the output cache. Returns Nette\Caching\OutputHelper object if buffering was started.
|
||||
* @param string $key
|
||||
* @param Nette\Caching\OutputHelper[] $parents
|
||||
* @return Nette\Caching\OutputHelper|\stdClass
|
||||
*/
|
||||
public static function createCache(Nette\Caching\IStorage $cacheStorage, $key, &$parents, array $args = null)
|
||||
{
|
||||
if ($args) {
|
||||
if (array_key_exists('if', $args) && !$args['if']) {
|
||||
return $parents[] = new \stdClass;
|
||||
}
|
||||
$key = array_merge([$key], array_intersect_key($args, range(0, count($args))));
|
||||
}
|
||||
if ($parents) {
|
||||
end($parents)->dependencies[Cache::ITEMS][] = $key;
|
||||
}
|
||||
|
||||
$cache = new Cache($cacheStorage, 'Nette.Templating.Cache');
|
||||
if ($helper = $cache->start($key)) {
|
||||
$parents[] = $helper;
|
||||
}
|
||||
return $helper;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ends the output cache.
|
||||
* @param Nette\Caching\OutputHelper[] $parents
|
||||
* @return void
|
||||
*/
|
||||
public static function endCache(&$parents, array $args = null)
|
||||
{
|
||||
$helper = array_pop($parents);
|
||||
if ($helper instanceof Nette\Caching\OutputHelper) {
|
||||
if (isset($args['dependencies'])) {
|
||||
$args += call_user_func($args['dependencies']);
|
||||
}
|
||||
if (isset($args['expire'])) {
|
||||
$args['expiration'] = $args['expire']; // back compatibility
|
||||
}
|
||||
$helper->dependencies[Cache::TAGS] = isset($args['tags']) ? $args['tags'] : null;
|
||||
$helper->dependencies[Cache::EXPIRATION] = isset($args['expiration']) ? $args['expiration'] : '+ 7 days';
|
||||
$helper->end();
|
||||
}
|
||||
}
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Implements the cache for a application.
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** dependency */
|
||||
const
|
||||
PRIORITY = 'priority',
|
||||
EXPIRATION = 'expire',
|
||||
EXPIRE = 'expire',
|
||||
SLIDING = 'sliding',
|
||||
TAGS = 'tags',
|
||||
FILES = 'files',
|
||||
ITEMS = 'items',
|
||||
CONSTS = 'consts',
|
||||
CALLBACKS = 'callbacks',
|
||||
NAMESPACES = 'namespaces',
|
||||
ALL = 'all';
|
||||
|
||||
/** @internal */
|
||||
const NAMESPACE_SEPARATOR = "\x00";
|
||||
|
||||
/** @var IStorage */
|
||||
private $storage;
|
||||
|
||||
/** @var string */
|
||||
private $namespace;
|
||||
|
||||
|
||||
public function __construct(IStorage $storage, $namespace = null)
|
||||
{
|
||||
$this->storage = $storage;
|
||||
$this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns cache storage.
|
||||
* @return IStorage
|
||||
*/
|
||||
public function getStorage()
|
||||
{
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns cache namespace.
|
||||
* @return string
|
||||
*/
|
||||
public function getNamespace()
|
||||
{
|
||||
return (string) substr($this->namespace, 0, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns new nested cache object.
|
||||
* @param string $namespace
|
||||
* @return static
|
||||
*/
|
||||
public function derive($namespace)
|
||||
{
|
||||
$derived = new static($this->storage, $this->namespace . $namespace);
|
||||
return $derived;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the specified item from the cache or generate it.
|
||||
* @param mixed $key
|
||||
* @param callable $fallback
|
||||
* @return mixed
|
||||
*/
|
||||
public function load($key, $fallback = null)
|
||||
{
|
||||
$data = $this->storage->read($this->generateKey($key));
|
||||
if ($data === null && $fallback) {
|
||||
return $this->save($key, function (&$dependencies) use ($fallback) {
|
||||
return call_user_func_array($fallback, [&$dependencies]);
|
||||
});
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads multiple items from the cache.
|
||||
* @param callable $fallback
|
||||
* @return array
|
||||
*/
|
||||
public function bulkLoad(array $keys, $fallback = null)
|
||||
{
|
||||
if (count($keys) === 0) {
|
||||
return [];
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
if (!is_scalar($key)) {
|
||||
throw new Nette\InvalidArgumentException('Only scalar keys are allowed in bulkLoad()');
|
||||
}
|
||||
}
|
||||
$storageKeys = array_map([$this, 'generateKey'], $keys);
|
||||
if (!$this->storage instanceof IBulkReader) {
|
||||
$result = array_combine($keys, array_map([$this->storage, 'read'], $storageKeys));
|
||||
if ($fallback !== null) {
|
||||
foreach ($result as $key => $value) {
|
||||
if ($value === null) {
|
||||
$result[$key] = $this->save($key, function (&$dependencies) use ($key, $fallback) {
|
||||
return call_user_func_array($fallback, [$key, &$dependencies]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
$cacheData = $this->storage->bulkRead($storageKeys);
|
||||
$result = [];
|
||||
foreach ($keys as $i => $key) {
|
||||
$storageKey = $storageKeys[$i];
|
||||
if (isset($cacheData[$storageKey])) {
|
||||
$result[$key] = $cacheData[$storageKey];
|
||||
} elseif ($fallback) {
|
||||
$result[$key] = $this->save($key, function (&$dependencies) use ($key, $fallback) {
|
||||
return call_user_func_array($fallback, [$key, &$dependencies]);
|
||||
});
|
||||
} else {
|
||||
$result[$key] = null;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes item into the cache.
|
||||
* Dependencies are:
|
||||
* - Cache::PRIORITY => (int) priority
|
||||
* - Cache::EXPIRATION => (timestamp) expiration
|
||||
* - Cache::SLIDING => (bool) use sliding expiration?
|
||||
* - Cache::TAGS => (array) tags
|
||||
* - Cache::FILES => (array|string) file names
|
||||
* - Cache::ITEMS => (array|string) cache items
|
||||
* - Cache::CONSTS => (array|string) cache items
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $data
|
||||
* @return mixed value itself
|
||||
*/
|
||||
public function save($key, $data, array $dependencies = null)
|
||||
{
|
||||
$key = $this->generateKey($key);
|
||||
|
||||
if ($data instanceof Nette\Callback || $data instanceof \Closure) {
|
||||
if ($data instanceof Nette\Callback) {
|
||||
trigger_error('Nette\Callback is deprecated, use closure or Nette\Utils\Callback::toClosure().', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->storage->lock($key);
|
||||
try {
|
||||
$data = call_user_func_array($data, [&$dependencies]);
|
||||
} catch (\Exception $e) {
|
||||
$this->storage->remove($key);
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
$this->storage->remove($key);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if ($data === null) {
|
||||
$this->storage->remove($key);
|
||||
} else {
|
||||
$dependencies = $this->completeDependencies($dependencies);
|
||||
if (isset($dependencies[self::EXPIRATION]) && $dependencies[self::EXPIRATION] <= 0) {
|
||||
$this->storage->remove($key);
|
||||
} else {
|
||||
$this->storage->write($key, $data, $dependencies);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function completeDependencies($dp)
|
||||
{
|
||||
// convert expire into relative amount of seconds
|
||||
if (isset($dp[self::EXPIRATION])) {
|
||||
$dp[self::EXPIRATION] = Nette\Utils\DateTime::from($dp[self::EXPIRATION])->format('U') - time();
|
||||
}
|
||||
|
||||
// make list from TAGS
|
||||
if (isset($dp[self::TAGS])) {
|
||||
$dp[self::TAGS] = array_values((array) $dp[self::TAGS]);
|
||||
}
|
||||
|
||||
// make list from NAMESPACES
|
||||
if (isset($dp[self::NAMESPACES])) {
|
||||
$dp[self::NAMESPACES] = array_values((array) $dp[self::NAMESPACES]);
|
||||
}
|
||||
|
||||
// convert FILES into CALLBACKS
|
||||
if (isset($dp[self::FILES])) {
|
||||
foreach (array_unique((array) $dp[self::FILES]) as $item) {
|
||||
$dp[self::CALLBACKS][] = [[__CLASS__, 'checkFile'], $item, @filemtime($item) ?: null]; // @ - stat may fail
|
||||
}
|
||||
unset($dp[self::FILES]);
|
||||
}
|
||||
|
||||
// add namespaces to items
|
||||
if (isset($dp[self::ITEMS])) {
|
||||
$dp[self::ITEMS] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::ITEMS]));
|
||||
}
|
||||
|
||||
// convert CONSTS into CALLBACKS
|
||||
if (isset($dp[self::CONSTS])) {
|
||||
foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
|
||||
$dp[self::CALLBACKS][] = [[__CLASS__, 'checkConst'], $item, constant($item)];
|
||||
}
|
||||
unset($dp[self::CONSTS]);
|
||||
}
|
||||
|
||||
if (!is_array($dp)) {
|
||||
$dp = [];
|
||||
}
|
||||
return $dp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes item from the cache.
|
||||
* @param mixed $key
|
||||
* @return void
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
$this->save($key, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes items from the cache by conditions.
|
||||
* Conditions are:
|
||||
* - Cache::PRIORITY => (int) priority
|
||||
* - Cache::TAGS => (array) tags
|
||||
* - Cache::ALL => true
|
||||
* @return void
|
||||
*/
|
||||
public function clean(array $conditions = null)
|
||||
{
|
||||
$conditions = (array) $conditions;
|
||||
if (isset($conditions[self::TAGS])) {
|
||||
$conditions[self::TAGS] = array_values((array) $conditions[self::TAGS]);
|
||||
}
|
||||
$this->storage->clean($conditions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Caches results of function/method calls.
|
||||
* @param callable $function
|
||||
* @return mixed
|
||||
*/
|
||||
public function call($function)
|
||||
{
|
||||
$key = func_get_args();
|
||||
if (is_array($function) && is_object($function[0])) {
|
||||
$key[0][0] = get_class($function[0]);
|
||||
}
|
||||
return $this->load($key, function () use ($function, $key) {
|
||||
return call_user_func_array($function, array_slice($key, 1));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Caches results of function/method calls.
|
||||
* @param callable $function
|
||||
* @return \Closure
|
||||
*/
|
||||
public function wrap($function, array $dependencies = null)
|
||||
{
|
||||
return function () use ($function, $dependencies) {
|
||||
$key = [$function, func_get_args()];
|
||||
if (is_array($function) && is_object($function[0])) {
|
||||
$key[0][0] = get_class($function[0]);
|
||||
}
|
||||
$data = $this->load($key);
|
||||
if ($data === null) {
|
||||
$data = $this->save($key, call_user_func_array($function, $key[1]), $dependencies);
|
||||
}
|
||||
return $data;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the output cache.
|
||||
* @param mixed $key
|
||||
* @return OutputHelper|null
|
||||
*/
|
||||
public function start($key)
|
||||
{
|
||||
$data = $this->load($key);
|
||||
if ($data === null) {
|
||||
return new OutputHelper($this, $key);
|
||||
}
|
||||
echo $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates internal cache key.
|
||||
* @param mixed $key
|
||||
* @return string
|
||||
*/
|
||||
protected function generateKey($key)
|
||||
{
|
||||
return $this->namespace . md5(is_scalar($key) ? (string) $key : serialize($key));
|
||||
}
|
||||
|
||||
|
||||
/********************* dependency checkers ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Checks CALLBACKS dependencies.
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkCallbacks(array $callbacks)
|
||||
{
|
||||
foreach ($callbacks as $callback) {
|
||||
if (!call_user_func_array(array_shift($callback), $callback)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks CONSTS dependency.
|
||||
* @param string $const
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
private static function checkConst($const, $value)
|
||||
{
|
||||
return defined($const) && constant($const) === $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks FILES dependency.
|
||||
* @param string $file
|
||||
* @param int|null $time
|
||||
* @return bool
|
||||
*/
|
||||
private static function checkFile($file, $time)
|
||||
{
|
||||
return @filemtime($file) == $time; // @ - stat may fail
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching;
|
||||
|
||||
|
||||
/**
|
||||
* Cache storage with a bulk read support.
|
||||
*/
|
||||
interface IBulkReader
|
||||
{
|
||||
|
||||
/**
|
||||
* Reads from cache in bulk.
|
||||
* @return array key => value pairs, missing items are omitted
|
||||
*/
|
||||
function bulkRead(array $keys);
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching;
|
||||
|
||||
|
||||
/**
|
||||
* Cache storage.
|
||||
*/
|
||||
interface IStorage
|
||||
{
|
||||
|
||||
/**
|
||||
* Read from cache.
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
function read($key);
|
||||
|
||||
/**
|
||||
* Prevents item reading and writing. Lock is released by write() or remove().
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
function lock($key);
|
||||
|
||||
/**
|
||||
* Writes item into the cache.
|
||||
* @param string $key
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
function write($key, $data, array $dependencies);
|
||||
|
||||
/**
|
||||
* Removes item from the cache.
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
function remove($key);
|
||||
|
||||
/**
|
||||
* Removes items from the cache by conditions.
|
||||
* @return void
|
||||
*/
|
||||
function clean(array $conditions);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Output caching helper.
|
||||
*/
|
||||
class OutputHelper
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array */
|
||||
public $dependencies;
|
||||
|
||||
/** @var Cache|null */
|
||||
private $cache;
|
||||
|
||||
/** @var string */
|
||||
private $key;
|
||||
|
||||
|
||||
public function __construct(Cache $cache, $key)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->key = $key;
|
||||
ob_start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops and saves the cache.
|
||||
* @return void
|
||||
*/
|
||||
public function end(array $dependencies = null)
|
||||
{
|
||||
if ($this->cache === null) {
|
||||
throw new Nette\InvalidStateException('Output cache has already been saved.');
|
||||
}
|
||||
$this->cache->save($this->key, ob_get_flush(), (array) $dependencies + (array) $this->dependencies);
|
||||
$this->cache = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Cache dummy storage.
|
||||
*/
|
||||
class DevNullStorage implements Nette\Caching\IStorage
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
public function read($key)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function lock($key)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function write($key, $data, array $dependencies)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function clean(array $conditions)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
use Nette;
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
|
||||
/**
|
||||
* Cache file storage.
|
||||
*/
|
||||
class FileStorage implements Nette\Caching\IStorage
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/**
|
||||
* Atomic thread safe logic:
|
||||
*
|
||||
* 1) reading: open(r+b), lock(SH), read
|
||||
* - delete?: delete*, close
|
||||
* 2) deleting: delete*
|
||||
* 3) writing: open(r+b || wb), lock(EX), truncate*, write data, write meta, close
|
||||
*
|
||||
* delete* = try unlink, if fails (on NTFS) { lock(EX), truncate, close, unlink } else close (on ext3)
|
||||
*/
|
||||
|
||||
/** @internal cache file structure */
|
||||
const
|
||||
META_HEADER_LEN = 28, // 22b signature + 6b meta-struct size + serialized meta-struct + data
|
||||
META_TIME = 'time', // timestamp
|
||||
META_SERIALIZED = 'serialized', // is content serialized?
|
||||
META_EXPIRE = 'expire', // expiration timestamp
|
||||
META_DELTA = 'delta', // relative (sliding) expiration
|
||||
META_ITEMS = 'di', // array of dependent items (file => timestamp)
|
||||
META_CALLBACKS = 'callbacks'; // array of callbacks (function, args)
|
||||
|
||||
/** additional cache structure */
|
||||
const FILE = 'file',
|
||||
HANDLE = 'handle';
|
||||
|
||||
/** @var float probability that the clean() routine is started */
|
||||
public static $gcProbability = 0.001;
|
||||
|
||||
/** @var bool */
|
||||
public static $useDirectories = true;
|
||||
|
||||
/** @var string */
|
||||
private $dir;
|
||||
|
||||
/** @var bool */
|
||||
private $useDirs;
|
||||
|
||||
/** @var IJournal */
|
||||
private $journal;
|
||||
|
||||
/** @var array */
|
||||
private $locks;
|
||||
|
||||
|
||||
public function __construct($dir, IJournal $journal = null)
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
throw new Nette\DirectoryNotFoundException("Directory '$dir' not found.");
|
||||
}
|
||||
|
||||
$this->dir = $dir;
|
||||
$this->useDirs = (bool) static::$useDirectories;
|
||||
$this->journal = $journal;
|
||||
|
||||
if (mt_rand() / mt_getrandmax() < static::$gcProbability) {
|
||||
$this->clean([]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function read($key)
|
||||
{
|
||||
$meta = $this->readMetaAndLock($this->getCacheFile($key), LOCK_SH);
|
||||
if ($meta && $this->verify($meta)) {
|
||||
return $this->readData($meta); // calls fclose()
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies dependencies.
|
||||
* @return bool
|
||||
*/
|
||||
private function verify(array $meta)
|
||||
{
|
||||
do {
|
||||
if (!empty($meta[self::META_DELTA])) {
|
||||
// meta[file] was added by readMetaAndLock()
|
||||
if (filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < time()) {
|
||||
break;
|
||||
}
|
||||
touch($meta[self::FILE]);
|
||||
|
||||
} elseif (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < time()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($meta[self::META_ITEMS])) {
|
||||
foreach ($meta[self::META_ITEMS] as $depFile => $time) {
|
||||
$m = $this->readMetaAndLock($depFile, LOCK_SH);
|
||||
if ($m[self::META_TIME] !== $time || ($m && !$this->verify($m))) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
$this->delete($meta[self::FILE], $meta[self::HANDLE]); // meta[handle] & meta[file] was added by readMetaAndLock()
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function lock($key)
|
||||
{
|
||||
$cacheFile = $this->getCacheFile($key);
|
||||
if ($this->useDirs && !is_dir($dir = dirname($cacheFile))) {
|
||||
@mkdir($dir); // @ - directory may already exist
|
||||
}
|
||||
$handle = fopen($cacheFile, 'c+b');
|
||||
if ($handle) {
|
||||
$this->locks[$key] = $handle;
|
||||
flock($handle, LOCK_EX);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function write($key, $data, array $dp)
|
||||
{
|
||||
$meta = [
|
||||
self::META_TIME => microtime(),
|
||||
];
|
||||
|
||||
if (isset($dp[Cache::EXPIRATION])) {
|
||||
if (empty($dp[Cache::SLIDING])) {
|
||||
$meta[self::META_EXPIRE] = $dp[Cache::EXPIRATION] + time(); // absolute time
|
||||
} else {
|
||||
$meta[self::META_DELTA] = (int) $dp[Cache::EXPIRATION]; // sliding time
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($dp[Cache::ITEMS])) {
|
||||
foreach ((array) $dp[Cache::ITEMS] as $item) {
|
||||
$depFile = $this->getCacheFile($item);
|
||||
$m = $this->readMetaAndLock($depFile, LOCK_SH);
|
||||
$meta[self::META_ITEMS][$depFile] = $m[self::META_TIME]; // may be null
|
||||
unset($m);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($dp[Cache::CALLBACKS])) {
|
||||
$meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS];
|
||||
}
|
||||
|
||||
if (!isset($this->locks[$key])) {
|
||||
$this->lock($key);
|
||||
if (!isset($this->locks[$key])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$handle = $this->locks[$key];
|
||||
unset($this->locks[$key]);
|
||||
|
||||
$cacheFile = $this->getCacheFile($key);
|
||||
|
||||
if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) {
|
||||
if (!$this->journal) {
|
||||
throw new Nette\InvalidStateException('CacheJournal has not been provided.');
|
||||
}
|
||||
$this->journal->write($cacheFile, $dp);
|
||||
}
|
||||
|
||||
ftruncate($handle, 0);
|
||||
|
||||
if (!is_string($data)) {
|
||||
$data = serialize($data);
|
||||
$meta[self::META_SERIALIZED] = true;
|
||||
}
|
||||
|
||||
$head = serialize($meta) . '?>';
|
||||
$head = '<?php //netteCache[01]' . str_pad((string) strlen($head), 6, '0', STR_PAD_LEFT) . $head;
|
||||
$headLen = strlen($head);
|
||||
|
||||
do {
|
||||
if (fwrite($handle, str_repeat("\x00", $headLen)) !== $headLen) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (fwrite($handle, $data) !== strlen($data)) {
|
||||
break;
|
||||
}
|
||||
|
||||
fseek($handle, 0);
|
||||
if (fwrite($handle, $head) !== $headLen) {
|
||||
break;
|
||||
}
|
||||
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
return;
|
||||
} while (false);
|
||||
|
||||
$this->delete($cacheFile, $handle);
|
||||
}
|
||||
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
unset($this->locks[$key]);
|
||||
$this->delete($this->getCacheFile($key));
|
||||
}
|
||||
|
||||
|
||||
public function clean(array $conditions)
|
||||
{
|
||||
$all = !empty($conditions[Cache::ALL]);
|
||||
$collector = empty($conditions);
|
||||
$namespaces = isset($conditions[Cache::NAMESPACES]) ? $conditions[Cache::NAMESPACES] : null;
|
||||
|
||||
// cleaning using file iterator
|
||||
if ($all || $collector) {
|
||||
$now = time();
|
||||
foreach (Nette\Utils\Finder::find('_*')->from($this->dir)->childFirst() as $entry) {
|
||||
$path = (string) $entry;
|
||||
if ($entry->isDir()) { // collector: remove empty dirs
|
||||
@rmdir($path); // @ - removing dirs is not necessary
|
||||
continue;
|
||||
}
|
||||
if ($all) {
|
||||
$this->delete($path);
|
||||
|
||||
} else { // collector
|
||||
$meta = $this->readMetaAndLock($path, LOCK_SH);
|
||||
if (!$meta) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((!empty($meta[self::META_DELTA]) && filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < $now)
|
||||
|| (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < $now)
|
||||
) {
|
||||
$this->delete($path, $meta[self::HANDLE]);
|
||||
continue;
|
||||
}
|
||||
|
||||
flock($meta[self::HANDLE], LOCK_UN);
|
||||
fclose($meta[self::HANDLE]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->journal) {
|
||||
$this->journal->clean($conditions);
|
||||
}
|
||||
return;
|
||||
|
||||
} elseif ($namespaces) {
|
||||
foreach ($namespaces as $namespace) {
|
||||
$dir = $this->dir . '/_' . urlencode($namespace);
|
||||
if (is_dir($dir)) {
|
||||
foreach (Nette\Utils\Finder::findFiles('_*')->in($dir) as $entry) {
|
||||
$this->delete((string) $entry);
|
||||
}
|
||||
@rmdir($dir); // may already contain new files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleaning using journal
|
||||
if ($this->journal) {
|
||||
foreach ($this->journal->clean($conditions) as $file) {
|
||||
$this->delete($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads cache data from disk.
|
||||
* @param string $file
|
||||
* @param int $lock
|
||||
* @return array|null
|
||||
*/
|
||||
protected function readMetaAndLock($file, $lock)
|
||||
{
|
||||
$handle = @fopen($file, 'r+b'); // @ - file may not exist
|
||||
if (!$handle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
flock($handle, $lock);
|
||||
|
||||
$head = stream_get_contents($handle, self::META_HEADER_LEN);
|
||||
if ($head && strlen($head) === self::META_HEADER_LEN) {
|
||||
$size = (int) substr($head, -6);
|
||||
$meta = stream_get_contents($handle, $size, self::META_HEADER_LEN);
|
||||
$meta = unserialize($meta);
|
||||
$meta[self::FILE] = $file;
|
||||
$meta[self::HANDLE] = $handle;
|
||||
return $meta;
|
||||
}
|
||||
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads cache data from disk and closes cache file handle.
|
||||
* @param array $meta
|
||||
* @return mixed
|
||||
*/
|
||||
protected function readData($meta)
|
||||
{
|
||||
$data = stream_get_contents($meta[self::HANDLE]);
|
||||
flock($meta[self::HANDLE], LOCK_UN);
|
||||
fclose($meta[self::HANDLE]);
|
||||
|
||||
if (empty($meta[self::META_SERIALIZED])) {
|
||||
return $data;
|
||||
} else {
|
||||
return unserialize($data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns file name.
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheFile($key)
|
||||
{
|
||||
$file = urlencode($key);
|
||||
if ($this->useDirs && $a = strrpos($file, '%00')) { // %00 = urlencode(Nette\Caching\Cache::NAMESPACE_SEPARATOR)
|
||||
$file = substr_replace($file, '/_', $a, 3);
|
||||
}
|
||||
return $this->dir . '/_' . $file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes and closes file.
|
||||
* @param string $file
|
||||
* @param resource $handle
|
||||
* @return void
|
||||
*/
|
||||
private static function delete($file, $handle = null)
|
||||
{
|
||||
if (@unlink($file)) { // @ - file may not already exist
|
||||
if ($handle) {
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$handle) {
|
||||
$handle = @fopen($file, 'r+'); // @ - file may not exist
|
||||
}
|
||||
if ($handle) {
|
||||
flock($handle, LOCK_EX);
|
||||
ftruncate($handle, 0);
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
@unlink($file); // @ - file may not already exist
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
|
||||
/**
|
||||
* Cache journal provider.
|
||||
*/
|
||||
interface IJournal
|
||||
{
|
||||
|
||||
/**
|
||||
* Writes entry information into the journal.
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
function write($key, array $dependencies);
|
||||
|
||||
/**
|
||||
* Cleans entries from journal.
|
||||
* @return array|null of removed items or null when performing a full cleanup
|
||||
*/
|
||||
function clean(array $conditions);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
use Nette;
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
|
||||
/**
|
||||
* Memcached storage.
|
||||
*/
|
||||
class MemcachedStorage implements Nette\Caching\IStorage
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @internal cache structure */
|
||||
const
|
||||
META_CALLBACKS = 'callbacks',
|
||||
META_DATA = 'data',
|
||||
META_DELTA = 'delta';
|
||||
|
||||
/** @var \Memcache */
|
||||
private $memcache;
|
||||
|
||||
/** @var string */
|
||||
private $prefix;
|
||||
|
||||
/** @var IJournal */
|
||||
private $journal;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if Memcached extension is available.
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAvailable()
|
||||
{
|
||||
return extension_loaded('memcache');
|
||||
}
|
||||
|
||||
|
||||
public function __construct($host = 'localhost', $port = 11211, $prefix = '', IJournal $journal = null)
|
||||
{
|
||||
if (!static::isAvailable()) {
|
||||
throw new Nette\NotSupportedException("PHP extension 'memcache' is not loaded.");
|
||||
}
|
||||
|
||||
$this->prefix = $prefix;
|
||||
$this->journal = $journal;
|
||||
$this->memcache = new \Memcache;
|
||||
if ($host) {
|
||||
$this->addServer($host, $port);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function addServer($host = 'localhost', $port = 11211, $timeout = 1)
|
||||
{
|
||||
if ($this->memcache->addServer($host, $port, true, 1, $timeout) === false) {
|
||||
$error = error_get_last();
|
||||
throw new Nette\InvalidStateException("Memcache::addServer(): $error[message].");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return \Memcache
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->memcache;
|
||||
}
|
||||
|
||||
|
||||
public function read($key)
|
||||
{
|
||||
$key = urlencode($this->prefix . $key);
|
||||
$meta = $this->memcache->get($key);
|
||||
if (!$meta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// meta structure:
|
||||
// array(
|
||||
// data => stored data
|
||||
// delta => relative (sliding) expiration
|
||||
// callbacks => array of callbacks (function, args)
|
||||
// )
|
||||
|
||||
// verify dependencies
|
||||
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
|
||||
$this->memcache->delete($key, 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!empty($meta[self::META_DELTA])) {
|
||||
$this->memcache->replace($key, $meta, 0, $meta[self::META_DELTA] + time());
|
||||
}
|
||||
|
||||
return $meta[self::META_DATA];
|
||||
}
|
||||
|
||||
|
||||
public function lock($key)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function write($key, $data, array $dp)
|
||||
{
|
||||
if (isset($dp[Cache::ITEMS])) {
|
||||
throw new Nette\NotSupportedException('Dependent items are not supported by MemcachedStorage.');
|
||||
}
|
||||
|
||||
$key = urlencode($this->prefix . $key);
|
||||
$meta = [
|
||||
self::META_DATA => $data,
|
||||
];
|
||||
|
||||
$expire = 0;
|
||||
if (isset($dp[Cache::EXPIRATION])) {
|
||||
$expire = (int) $dp[Cache::EXPIRATION];
|
||||
if (!empty($dp[Cache::SLIDING])) {
|
||||
$meta[self::META_DELTA] = $expire; // sliding time
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($dp[Cache::CALLBACKS])) {
|
||||
$meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS];
|
||||
}
|
||||
|
||||
if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) {
|
||||
if (!$this->journal) {
|
||||
throw new Nette\InvalidStateException('CacheJournal has not been provided.');
|
||||
}
|
||||
$this->journal->write($key, $dp);
|
||||
}
|
||||
|
||||
$this->memcache->set($key, $meta, 0, $expire);
|
||||
}
|
||||
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
$this->memcache->delete(urlencode($this->prefix . $key), 0);
|
||||
}
|
||||
|
||||
|
||||
public function clean(array $conditions)
|
||||
{
|
||||
if (!empty($conditions[Cache::ALL])) {
|
||||
$this->memcache->flush();
|
||||
|
||||
} elseif ($this->journal) {
|
||||
foreach ($this->journal->clean($conditions) as $entry) {
|
||||
$this->memcache->delete($entry, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Memory cache storage.
|
||||
*/
|
||||
class MemoryStorage implements Nette\Caching\IStorage
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array */
|
||||
private $data = [];
|
||||
|
||||
|
||||
public function read($key)
|
||||
{
|
||||
return isset($this->data[$key]) ? $this->data[$key] : null;
|
||||
}
|
||||
|
||||
|
||||
public function lock($key)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function write($key, $data, array $dependencies)
|
||||
{
|
||||
$this->data[$key] = $data;
|
||||
}
|
||||
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
||||
|
||||
public function clean(array $conditions)
|
||||
{
|
||||
if (!empty($conditions[Nette\Caching\Cache::ALL])) {
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
use Nette;
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
|
||||
/**
|
||||
* Memcached storage using memcached extension.
|
||||
*/
|
||||
class NewMemcachedStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @internal cache structure */
|
||||
const
|
||||
META_CALLBACKS = 'callbacks',
|
||||
META_DATA = 'data',
|
||||
META_DELTA = 'delta';
|
||||
|
||||
/** @var \Memcached */
|
||||
private $memcached;
|
||||
|
||||
/** @var string */
|
||||
private $prefix;
|
||||
|
||||
/** @var IJournal */
|
||||
private $journal;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if Memcached extension is available.
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAvailable()
|
||||
{
|
||||
return extension_loaded('memcached');
|
||||
}
|
||||
|
||||
|
||||
public function __construct($host = 'localhost', $port = 11211, $prefix = '', IJournal $journal = null)
|
||||
{
|
||||
if (!static::isAvailable()) {
|
||||
throw new Nette\NotSupportedException("PHP extension 'memcached' is not loaded.");
|
||||
}
|
||||
|
||||
$this->prefix = $prefix;
|
||||
$this->journal = $journal;
|
||||
$this->memcached = new \Memcached;
|
||||
if ($host) {
|
||||
$this->addServer($host, $port);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function addServer($host = 'localhost', $port = 11211)
|
||||
{
|
||||
if ($this->memcached->addServer($host, $port, 1) === false) {
|
||||
$error = error_get_last();
|
||||
throw new Nette\InvalidStateException("Memcached::addServer(): $error[message].");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return \Memcached
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->memcached;
|
||||
}
|
||||
|
||||
|
||||
public function read($key)
|
||||
{
|
||||
$key = urlencode($this->prefix . $key);
|
||||
$meta = $this->memcached->get($key);
|
||||
if (!$meta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// meta structure:
|
||||
// array(
|
||||
// data => stored data
|
||||
// delta => relative (sliding) expiration
|
||||
// callbacks => array of callbacks (function, args)
|
||||
// )
|
||||
|
||||
// verify dependencies
|
||||
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
|
||||
$this->memcached->delete($key, 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!empty($meta[self::META_DELTA])) {
|
||||
$this->memcached->replace($key, $meta, $meta[self::META_DELTA] + time());
|
||||
}
|
||||
|
||||
return $meta[self::META_DATA];
|
||||
}
|
||||
|
||||
|
||||
public function bulkRead(array $keys)
|
||||
{
|
||||
$prefixedKeys = array_map(function ($key) {
|
||||
return urlencode($this->prefix . $key);
|
||||
}, $keys);
|
||||
$keys = array_combine($prefixedKeys, $keys);
|
||||
$metas = $this->memcached->getMulti($prefixedKeys);
|
||||
$result = [];
|
||||
$deleteKeys = [];
|
||||
foreach ($metas as $prefixedKey => $meta) {
|
||||
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
|
||||
$deleteKeys[] = $prefixedKey;
|
||||
} else {
|
||||
$result[$keys[$prefixedKey]] = $meta[self::META_DATA];
|
||||
}
|
||||
|
||||
if (!empty($meta[self::META_DELTA])) {
|
||||
$this->memcached->replace($prefixedKey, $meta, $meta[self::META_DELTA] + time());
|
||||
}
|
||||
}
|
||||
if (!empty($deleteKeys)) {
|
||||
$this->memcached->deleteMulti($deleteKeys, 0);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function lock($key)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function write($key, $data, array $dp)
|
||||
{
|
||||
if (isset($dp[Cache::ITEMS])) {
|
||||
throw new Nette\NotSupportedException('Dependent items are not supported by MemcachedStorage.');
|
||||
}
|
||||
|
||||
$key = urlencode($this->prefix . $key);
|
||||
$meta = [
|
||||
self::META_DATA => $data,
|
||||
];
|
||||
|
||||
$expire = 0;
|
||||
if (isset($dp[Cache::EXPIRATION])) {
|
||||
$expire = (int) $dp[Cache::EXPIRATION];
|
||||
if (!empty($dp[Cache::SLIDING])) {
|
||||
$meta[self::META_DELTA] = $expire; // sliding time
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($dp[Cache::CALLBACKS])) {
|
||||
$meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS];
|
||||
}
|
||||
|
||||
if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) {
|
||||
if (!$this->journal) {
|
||||
throw new Nette\InvalidStateException('CacheJournal has not been provided.');
|
||||
}
|
||||
$this->journal->write($key, $dp);
|
||||
}
|
||||
|
||||
$this->memcached->set($key, $meta, $expire);
|
||||
}
|
||||
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
$this->memcached->delete(urlencode($this->prefix . $key), 0);
|
||||
}
|
||||
|
||||
|
||||
public function clean(array $conditions)
|
||||
{
|
||||
if (!empty($conditions[Cache::ALL])) {
|
||||
$this->memcached->flush();
|
||||
|
||||
} elseif ($this->journal) {
|
||||
foreach ($this->journal->clean($conditions) as $entry) {
|
||||
$this->memcached->delete($entry, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
use Nette;
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
|
||||
/**
|
||||
* SQLite based journal.
|
||||
*/
|
||||
class SQLiteJournal implements IJournal
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @string */
|
||||
private $path;
|
||||
|
||||
/** @var \PDO */
|
||||
private $pdo;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
if (!extension_loaded('pdo_sqlite')) {
|
||||
throw new Nette\NotSupportedException('SQLiteJournal requires PHP extension pdo_sqlite which is not loaded.');
|
||||
}
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
|
||||
private function open()
|
||||
{
|
||||
if ($this->path !== ':memory:' && !is_file($this->path)) {
|
||||
touch($this->path); // ensures ordinary file permissions
|
||||
}
|
||||
|
||||
$this->pdo = new \PDO('sqlite:' . $this->path);
|
||||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
$this->pdo->exec('
|
||||
PRAGMA foreign_keys = OFF;
|
||||
PRAGMA journal_mode = WAL;
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
key BLOB NOT NULL,
|
||||
tag BLOB NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS priorities (
|
||||
key BLOB NOT NULL,
|
||||
priority INT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_key_tag ON tags(key, tag);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_priorities_key ON priorities(key);
|
||||
CREATE INDEX IF NOT EXISTS idx_priorities_priority ON priorities(priority);
|
||||
');
|
||||
}
|
||||
|
||||
|
||||
public function write($key, array $dependencies)
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
$this->open();
|
||||
}
|
||||
$this->pdo->exec('BEGIN');
|
||||
|
||||
if (!empty($dependencies[Cache::TAGS])) {
|
||||
$this->pdo->prepare('DELETE FROM tags WHERE key = ?')->execute([$key]);
|
||||
|
||||
foreach ((array) $dependencies[Cache::TAGS] as $tag) {
|
||||
$arr[] = $key;
|
||||
$arr[] = $tag;
|
||||
}
|
||||
$this->pdo->prepare('INSERT INTO tags (key, tag) SELECT ?, ?' . str_repeat('UNION SELECT ?, ?', count($arr) / 2 - 1))
|
||||
->execute($arr);
|
||||
}
|
||||
|
||||
if (!empty($dependencies[Cache::PRIORITY])) {
|
||||
$this->pdo->prepare('REPLACE INTO priorities (key, priority) VALUES (?, ?)')
|
||||
->execute([$key, (int) $dependencies[Cache::PRIORITY]]);
|
||||
}
|
||||
|
||||
$this->pdo->exec('COMMIT');
|
||||
}
|
||||
|
||||
|
||||
public function clean(array $conditions)
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
$this->open();
|
||||
}
|
||||
if (!empty($conditions[Cache::ALL])) {
|
||||
$this->pdo->exec('
|
||||
BEGIN;
|
||||
DELETE FROM tags;
|
||||
DELETE FROM priorities;
|
||||
COMMIT;
|
||||
');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$unions = $args = [];
|
||||
if (!empty($conditions[Cache::TAGS])) {
|
||||
$tags = (array) $conditions[Cache::TAGS];
|
||||
$unions[] = 'SELECT DISTINCT key FROM tags WHERE tag IN (?' . str_repeat(', ?', count($tags) - 1) . ')';
|
||||
$args = $tags;
|
||||
}
|
||||
|
||||
if (!empty($conditions[Cache::PRIORITY])) {
|
||||
$unions[] = 'SELECT DISTINCT key FROM priorities WHERE priority <= ?';
|
||||
$args[] = (int) $conditions[Cache::PRIORITY];
|
||||
}
|
||||
|
||||
if (empty($unions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$unionSql = implode(' UNION ', $unions);
|
||||
|
||||
$this->pdo->exec('BEGIN IMMEDIATE');
|
||||
|
||||
$stmt = $this->pdo->prepare($unionSql);
|
||||
$stmt->execute($args);
|
||||
$keys = $stmt->fetchAll(\PDO::FETCH_COLUMN, 0);
|
||||
|
||||
if (empty($keys)) {
|
||||
$this->pdo->exec('COMMIT');
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->pdo->prepare("DELETE FROM tags WHERE key IN ($unionSql)")->execute($args);
|
||||
$this->pdo->prepare("DELETE FROM priorities WHERE key IN ($unionSql)")->execute($args);
|
||||
$this->pdo->exec('COMMIT');
|
||||
|
||||
return $keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
use Nette;
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
|
||||
/**
|
||||
* SQLite storage.
|
||||
*/
|
||||
class SQLiteStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var \PDO */
|
||||
private $pdo;
|
||||
|
||||
|
||||
public function __construct($path)
|
||||
{
|
||||
if ($path !== ':memory:' && !is_file($path)) {
|
||||
touch($path); // ensures ordinary file permissions
|
||||
}
|
||||
|
||||
$this->pdo = new \PDO('sqlite:' . $path);
|
||||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
$this->pdo->exec('
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS cache (
|
||||
key BLOB NOT NULL PRIMARY KEY,
|
||||
data BLOB NOT NULL,
|
||||
expire INTEGER,
|
||||
slide INTEGER
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
key BLOB NOT NULL REFERENCES cache ON DELETE CASCADE,
|
||||
tag BLOB NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS cache_expire ON cache(expire);
|
||||
CREATE INDEX IF NOT EXISTS tags_key ON tags(key);
|
||||
CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag);
|
||||
PRAGMA synchronous = OFF;
|
||||
');
|
||||
}
|
||||
|
||||
|
||||
public function read($key)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT data, slide FROM cache WHERE key=? AND (expire IS NULL OR expire >= ?)');
|
||||
$stmt->execute([$key, time()]);
|
||||
if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
if ($row['slide'] !== null) {
|
||||
$this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key=?')->execute([time(), $key]);
|
||||
}
|
||||
return unserialize($row['data']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function bulkRead(array $keys)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT key, data, slide FROM cache WHERE key IN (?' . str_repeat(',?', count($keys) - 1) . ') AND (expire IS NULL OR expire >= ?)');
|
||||
$stmt->execute(array_merge($keys, [time()]));
|
||||
$result = [];
|
||||
$updateSlide = [];
|
||||
foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
|
||||
if ($row['slide'] !== null) {
|
||||
$updateSlide[] = $row['key'];
|
||||
}
|
||||
$result[$row['key']] = unserialize($row['data']);
|
||||
}
|
||||
if (!empty($updateSlide)) {
|
||||
$stmt = $this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key IN(?' . str_repeat(',?', count($updateSlide) - 1) . ')');
|
||||
$stmt->execute(array_merge([time()], $updateSlide));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function lock($key)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function write($key, $data, array $dependencies)
|
||||
{
|
||||
$expire = isset($dependencies[Cache::EXPIRATION]) ? $dependencies[Cache::EXPIRATION] + time() : null;
|
||||
$slide = isset($dependencies[Cache::SLIDING]) ? $dependencies[Cache::EXPIRATION] : null;
|
||||
|
||||
$this->pdo->exec('BEGIN TRANSACTION');
|
||||
$this->pdo->prepare('REPLACE INTO cache (key, data, expire, slide) VALUES (?, ?, ?, ?)')
|
||||
->execute([$key, serialize($data), $expire, $slide]);
|
||||
|
||||
if (!empty($dependencies[Cache::TAGS])) {
|
||||
foreach ((array) $dependencies[Cache::TAGS] as $tag) {
|
||||
$arr[] = $key;
|
||||
$arr[] = $tag;
|
||||
}
|
||||
$this->pdo->prepare('INSERT INTO tags (key, tag) SELECT ?, ?' . str_repeat('UNION SELECT ?, ?', count($arr) / 2 - 1))
|
||||
->execute($arr);
|
||||
}
|
||||
$this->pdo->exec('COMMIT');
|
||||
}
|
||||
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
$this->pdo->prepare('DELETE FROM cache WHERE key=?')
|
||||
->execute([$key]);
|
||||
}
|
||||
|
||||
|
||||
public function clean(array $conditions)
|
||||
{
|
||||
if (!empty($conditions[Cache::ALL])) {
|
||||
$this->pdo->prepare('DELETE FROM cache')->execute();
|
||||
|
||||
} else {
|
||||
$sql = 'DELETE FROM cache WHERE expire < ?';
|
||||
$args = [time()];
|
||||
|
||||
if (!empty($conditions[Cache::TAGS])) {
|
||||
$tags = (array) $conditions[Cache::TAGS];
|
||||
$sql .= ' OR key IN (SELECT key FROM tags WHERE tag IN (?' . str_repeat(',?', count($tags) - 1) . '))';
|
||||
$args = array_merge($args, $tags);
|
||||
}
|
||||
|
||||
$this->pdo->prepare($sql)->execute($args);
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "nette/component-model",
|
||||
"description": "⚛ Nette Component Model",
|
||||
"keywords": ["nette", "components"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"nette/utils": "^2.5 || ~3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"tracy/tracy": "^2.3"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/nette": "<2.2",
|
||||
"nette/application": "<2.4"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
How to contribute & use the issue tracker
|
||||
=========================================
|
||||
|
||||
Nette welcomes your contributions. There are several ways to help out:
|
||||
|
||||
* Create an issue on GitHub, if you have found a bug
|
||||
* Write test cases for open bug issues
|
||||
* Write fixes for open bug/feature issues, preferably with test cases included
|
||||
* Contribute to the [documentation](https://nette.org/en/writing)
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Please **do not use the issue tracker to ask questions**. We will be happy to help you
|
||||
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
**Feature requests** are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
|
||||
|
||||
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
|
||||
|
||||
Please do not fix whitespace, format code, or make a purely cosmetic patch.
|
||||
|
||||
Thanks! :heart:
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
Nette Component Model
|
||||
=====================
|
||||
|
||||
[](https://packagist.org/packages/nette/component-model)
|
||||
[](https://travis-ci.org/nette/component-model)
|
||||
[](https://coveralls.io/github/nette/component-model?branch=master)
|
||||
[](https://github.com/nette/component-model/releases)
|
||||
[](https://github.com/nette/component-model/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Components are the foundation of reusable code. They make your work easier and allow you to profit from community work. Components are wonderful.
|
||||
Nette Framework introduces several classes and interfaces for all these types of components.
|
||||
|
||||
Documentation can be found on the [website](https://doc.nette.org/components).
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The recommended way to install is via Composer:
|
||||
|
||||
```
|
||||
composer require nette/component-model
|
||||
```
|
||||
|
||||
It requires PHP version 5.6 and supports PHP up to 7.2. The dev-master version requires PHP 7.1.
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\ComponentModel;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of \ArrayAccess for IContainer.
|
||||
*/
|
||||
trait ArrayAccess
|
||||
{
|
||||
/**
|
||||
* Adds the component to the container.
|
||||
* @param string|int $name
|
||||
* @param IComponent $component
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($name, $component)
|
||||
{
|
||||
$this->addComponent($component, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns component specified by name. Throws exception if component doesn't exist.
|
||||
* @param string|int $name
|
||||
* @return IComponent
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public function offsetGet($name)
|
||||
{
|
||||
return $this->getComponent($name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does component specified by name exists?
|
||||
* @param string|int $name
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($name)
|
||||
{
|
||||
return $this->getComponent($name, false) !== null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes component from the container.
|
||||
* @param string|int $name
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($name)
|
||||
{
|
||||
if ($component = $this->getComponent($name, false)) {
|
||||
$this->removeComponent($component);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\ComponentModel;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Component is the base class for all components.
|
||||
*
|
||||
* Components are objects implementing IComponent. They has parent component and own name.
|
||||
*
|
||||
* @property-read string $name
|
||||
* @property-read IContainer|null $parent
|
||||
*/
|
||||
abstract class Component implements IComponent
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var IContainer|null */
|
||||
private $parent;
|
||||
|
||||
/** @var string|null */
|
||||
private $name;
|
||||
|
||||
/** @var array of [type => [obj, depth, path, array of [attached, detached]]] */
|
||||
private $monitors = [];
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
list($parent, $name) = func_get_args() + [null, null];
|
||||
if ($parent !== null) {
|
||||
trigger_error(__METHOD__ . '() argument $parent is deprecated, use $parent->addComponent() instead.', E_USER_DEPRECATED);
|
||||
$parent->addComponent($this, $name);
|
||||
|
||||
} elseif ($name !== null) {
|
||||
trigger_error(__METHOD__ . '() argument $name is deprecated, use $parent->setParent(null, $name) instead.', E_USER_DEPRECATED);
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the closest ancestor specified by class or interface name.
|
||||
* @param string|null $type
|
||||
* @param bool $throw
|
||||
* @return IComponent|null
|
||||
*/
|
||||
public function lookup($type, $throw = true)
|
||||
{
|
||||
if (!isset($this->monitors[$type])) { // not monitored or not processed yet
|
||||
$obj = $this->parent;
|
||||
$path = self::NAME_SEPARATOR . $this->name;
|
||||
$depth = 1;
|
||||
while ($obj !== null) {
|
||||
$parent = $obj->getParent();
|
||||
if ($type ? $obj instanceof $type : $parent === null) {
|
||||
break;
|
||||
}
|
||||
$path = self::NAME_SEPARATOR . $obj->getName() . $path;
|
||||
$depth++;
|
||||
$obj = $parent; // IComponent::getParent()
|
||||
if ($obj === $this) {
|
||||
$obj = null; // prevent cycling
|
||||
}
|
||||
}
|
||||
|
||||
if ($obj) {
|
||||
$this->monitors[$type] = [$obj, $depth, substr($path, 1), []];
|
||||
|
||||
} else {
|
||||
$this->monitors[$type] = [null, null, null, []]; // not found
|
||||
}
|
||||
}
|
||||
|
||||
if ($throw && $this->monitors[$type][0] === null) {
|
||||
throw new Nette\InvalidStateException("Component '$this->name' is not attached to '$type'.");
|
||||
}
|
||||
|
||||
return $this->monitors[$type][0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the closest ancestor specified by class or interface name and returns backtrace path.
|
||||
* A path is the concatenation of component names separated by self::NAME_SEPARATOR.
|
||||
* @param string|null $type
|
||||
* @param bool $throw
|
||||
* @return string|null
|
||||
*/
|
||||
public function lookupPath($type = null, $throw = true)
|
||||
{
|
||||
$this->lookup($type, $throw);
|
||||
return $this->monitors[$type][2];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts monitoring of ancestors.
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
final public function monitor($type, callable $attached = null, callable $detached = null)
|
||||
{
|
||||
if (func_num_args() === 1) {
|
||||
$attached = [$this, 'attached'];
|
||||
$detached = [$this, 'detached'];
|
||||
}
|
||||
if (($obj = $this->lookup($type, false)) && $attached && !in_array([$attached, $detached], $this->monitors[$type][3], true)) {
|
||||
$attached($obj);
|
||||
}
|
||||
$this->monitors[$type][3][] = [$attached, $detached]; // mark as monitored
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops monitoring of ancestors.
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function unmonitor($type)
|
||||
{
|
||||
unset($this->monitors[$type]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will be called when the component (or component's parent)
|
||||
* becomes attached to a monitored object. Do not call this method yourself.
|
||||
* @param IComponent $obj
|
||||
* @return void
|
||||
* @deprecated use monitor($type, $attached)
|
||||
*/
|
||||
protected function attached($obj)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will be called before the component (or component's parent)
|
||||
* becomes detached from a monitored object. Do not call this method yourself.
|
||||
* @param IComponent $obj
|
||||
* @return void
|
||||
* @deprecated use monitor($type, null, $detached)
|
||||
*/
|
||||
protected function detached($obj)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/********************* interface IComponent ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the parent container if any.
|
||||
* @return IContainer|null
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets or removes the parent of this component. This method is managed by containers and should
|
||||
* not be called by applications
|
||||
* @param string $name
|
||||
* @return static
|
||||
* @throws Nette\InvalidStateException
|
||||
* @internal
|
||||
*/
|
||||
public function setParent(IContainer $parent = null, $name = null)
|
||||
{
|
||||
if ($parent === null && $this->parent === null && $name !== null) {
|
||||
$this->name = $name; // just rename
|
||||
return $this;
|
||||
|
||||
} elseif ($parent === $this->parent && $name === null) {
|
||||
return $this; // nothing to do
|
||||
}
|
||||
|
||||
// A component cannot be given a parent if it already has a parent.
|
||||
if ($this->parent !== null && $parent !== null) {
|
||||
throw new Nette\InvalidStateException("Component '$this->name' already has a parent.");
|
||||
}
|
||||
|
||||
// remove from parent?
|
||||
if ($parent === null) {
|
||||
$this->refreshMonitors(0);
|
||||
$this->parent = null;
|
||||
|
||||
} else { // add to parent
|
||||
$this->validateParent($parent);
|
||||
$this->parent = $parent;
|
||||
if ($name !== null) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
$tmp = [];
|
||||
$this->refreshMonitors(0, $tmp);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is called by a component when it is about to be set new parent. Descendant can
|
||||
* override this method to disallow a parent change by throwing an Nette\InvalidStateException
|
||||
* @return void
|
||||
* @throws Nette\InvalidStateException
|
||||
*/
|
||||
protected function validateParent(IContainer $parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refreshes monitors.
|
||||
* @param int $depth
|
||||
* @param array|null $missing (array = attaching, null = detaching)
|
||||
* @param array $listeners
|
||||
* @return void
|
||||
*/
|
||||
private function refreshMonitors($depth, &$missing = null, &$listeners = [])
|
||||
{
|
||||
if ($this instanceof IContainer) {
|
||||
foreach ($this->getComponents() as $component) {
|
||||
if ($component instanceof self) {
|
||||
$component->refreshMonitors($depth + 1, $missing, $listeners);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing === null) { // detaching
|
||||
foreach ($this->monitors as $type => $rec) {
|
||||
if (isset($rec[1]) && $rec[1] > $depth) {
|
||||
if ($rec[3]) { // monitored
|
||||
$this->monitors[$type] = [null, null, null, $rec[3]];
|
||||
foreach ($rec[3] as $pair) {
|
||||
$listeners[] = [$pair[1], $rec[0]];
|
||||
}
|
||||
} else { // not monitored, just randomly cached
|
||||
unset($this->monitors[$type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else { // attaching
|
||||
foreach ($this->monitors as $type => $rec) {
|
||||
if (isset($rec[0])) { // is in cache yet
|
||||
continue;
|
||||
|
||||
} elseif (!$rec[3]) { // not monitored, just randomly cached
|
||||
unset($this->monitors[$type]);
|
||||
|
||||
} elseif (isset($missing[$type])) { // known from previous lookup
|
||||
$this->monitors[$type] = [null, null, null, $rec[3]];
|
||||
|
||||
} else {
|
||||
$this->monitors[$type] = null; // forces re-lookup
|
||||
if ($obj = $this->lookup($type, false)) {
|
||||
foreach ($rec[3] as $pair) {
|
||||
$listeners[] = [$pair[0], $obj];
|
||||
}
|
||||
} else {
|
||||
$missing[$type] = true;
|
||||
}
|
||||
$this->monitors[$type][3] = $rec[3]; // mark as monitored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($depth === 0) { // call listeners
|
||||
$prev = [];
|
||||
foreach ($listeners as $item) {
|
||||
if ($item[0] && !in_array($item, $prev, true)) {
|
||||
call_user_func($item[0], $item[1]);
|
||||
$prev[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* cloneable, serializable ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Object cloning.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->parent === null) {
|
||||
return;
|
||||
|
||||
} elseif ($this->parent instanceof Container) {
|
||||
$this->parent = $this->parent->_isCloning();
|
||||
if ($this->parent === null) { // not cloning
|
||||
$this->refreshMonitors(0);
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->parent = null;
|
||||
$this->refreshMonitors(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevents serialization.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
throw new Nette\NotImplementedException('Object serialization is not supported by class ' . get_class($this));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevents unserialization.
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new Nette\NotImplementedException('Object unserialization is not supported by class ' . get_class($this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\ComponentModel;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* ComponentContainer is default implementation of IContainer.
|
||||
*
|
||||
* @property-read \Iterator $components
|
||||
*/
|
||||
class Container extends Component implements IContainer
|
||||
{
|
||||
/** @var IComponent[] */
|
||||
private $components = [];
|
||||
|
||||
/** @var Container|null */
|
||||
private $cloning;
|
||||
|
||||
|
||||
/********************* interface IContainer ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Adds the component to the container.
|
||||
* @param string|int|null $name
|
||||
* @param string|int $insertBefore
|
||||
* @return static
|
||||
* @throws Nette\InvalidStateException
|
||||
*/
|
||||
public function addComponent(IComponent $component, $name, $insertBefore = null)
|
||||
{
|
||||
if ($name === null) {
|
||||
$name = $component->getName();
|
||||
}
|
||||
|
||||
if (is_int($name)) {
|
||||
$name = (string) $name;
|
||||
|
||||
} elseif (!is_string($name)) {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Component name must be integer or string, %s given.', gettype($name)));
|
||||
|
||||
} elseif (!preg_match('#^[a-zA-Z0-9_]+\z#', $name)) {
|
||||
throw new Nette\InvalidArgumentException("Component name must be non-empty alphanumeric string, '$name' given.");
|
||||
}
|
||||
|
||||
if (isset($this->components[$name])) {
|
||||
throw new Nette\InvalidStateException("Component with name '$name' already exists.");
|
||||
}
|
||||
|
||||
// check circular reference
|
||||
$obj = $this;
|
||||
do {
|
||||
if ($obj === $component) {
|
||||
throw new Nette\InvalidStateException("Circular reference detected while adding component '$name'.");
|
||||
}
|
||||
$obj = $obj->getParent();
|
||||
} while ($obj !== null);
|
||||
|
||||
// user checking
|
||||
$this->validateChildComponent($component);
|
||||
|
||||
if (isset($this->components[$insertBefore])) {
|
||||
$tmp = [];
|
||||
foreach ($this->components as $k => $v) {
|
||||
if ($k === $insertBefore) {
|
||||
$tmp[$name] = $component;
|
||||
}
|
||||
$tmp[$k] = $v;
|
||||
}
|
||||
$this->components = $tmp;
|
||||
} else {
|
||||
$this->components[$name] = $component;
|
||||
}
|
||||
|
||||
try {
|
||||
$component->setParent($this, $name);
|
||||
} catch (\Exception $e) {
|
||||
unset($this->components[$name]); // undo
|
||||
throw $e;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the component from the container.
|
||||
* @return void
|
||||
*/
|
||||
public function removeComponent(IComponent $component)
|
||||
{
|
||||
$name = $component->getName();
|
||||
if (!isset($this->components[$name]) || $this->components[$name] !== $component) {
|
||||
throw new Nette\InvalidArgumentException("Component named '$name' is not located in this container.");
|
||||
}
|
||||
|
||||
unset($this->components[$name]);
|
||||
$component->setParent(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns component specified by name or path.
|
||||
* @param string|int $name
|
||||
* @param bool $throw throw exception if component doesn't exist?
|
||||
* @return IComponent|null
|
||||
*/
|
||||
public function getComponent($name, $throw = true)
|
||||
{
|
||||
if (!is_int($name) && !is_string($name)) {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Component name must be integer or string, %s given.', gettype($name)));
|
||||
}
|
||||
|
||||
list($name) = $parts = explode(self::NAME_SEPARATOR, (string) $name, 2);
|
||||
|
||||
if (!isset($this->components[$name])) {
|
||||
if (!preg_match('#^[a-zA-Z0-9_]+\z#', $name)) {
|
||||
if ($throw) {
|
||||
throw new Nette\InvalidArgumentException("Component name must be non-empty alphanumeric string, '$name' given.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$component = $this->createComponent($name);
|
||||
if ($component) {
|
||||
if (!$component instanceof IComponent) {
|
||||
throw new Nette\UnexpectedValueException('Method createComponent() did not return Nette\ComponentModel\IComponent.');
|
||||
|
||||
} elseif (!isset($this->components[$name])) {
|
||||
$this->addComponent($component, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$component = isset($this->components[$name]) ? $this->components[$name] : null;
|
||||
if ($component !== null) {
|
||||
if (!isset($parts[1])) {
|
||||
return $component;
|
||||
|
||||
} elseif ($component instanceof IContainer) {
|
||||
return $component->getComponent($parts[1], $throw);
|
||||
|
||||
} elseif ($throw) {
|
||||
throw new Nette\InvalidArgumentException("Component with name '$name' is not container and cannot have '$parts[1]' component.");
|
||||
}
|
||||
|
||||
} elseif ($throw) {
|
||||
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_merge(
|
||||
array_keys($this->components),
|
||||
array_map('lcfirst', preg_filter('#^createComponent([A-Z0-9].*)#', '$1', get_class_methods($this)))
|
||||
), $name);
|
||||
throw new Nette\InvalidArgumentException("Component with name '$name' does not exist" . ($hint ? ", did you mean '$hint'?" : '.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Component factory. Delegates the creation of components to a createComponent<Name> method.
|
||||
* @param string $name
|
||||
* @return IComponent|null
|
||||
*/
|
||||
protected function createComponent($name)
|
||||
{
|
||||
$ucname = ucfirst($name);
|
||||
$method = 'createComponent' . $ucname;
|
||||
if ($ucname !== $name && method_exists($this, $method) && (new \ReflectionMethod($this, $method))->getName() === $method) {
|
||||
$component = $this->$method($name);
|
||||
if (!$component instanceof IComponent && !isset($this->components[$name])) {
|
||||
$class = get_class($this);
|
||||
throw new Nette\UnexpectedValueException("Method $class::$method() did not return or create the desired component.");
|
||||
}
|
||||
return $component;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates over descendants components.
|
||||
* @param bool $deep return all descendant?
|
||||
* @param string $filterType class type to return
|
||||
* @return \Iterator
|
||||
*/
|
||||
public function getComponents($deep = false, $filterType = null)
|
||||
{
|
||||
$iterator = new RecursiveComponentIterator($this->components);
|
||||
if ($deep) {
|
||||
$deep = $deep > 0 ? \RecursiveIteratorIterator::SELF_FIRST : \RecursiveIteratorIterator::CHILD_FIRST;
|
||||
$iterator = new \RecursiveIteratorIterator($iterator, $deep);
|
||||
}
|
||||
if ($filterType) {
|
||||
$iterator = new \CallbackFilterIterator($iterator, function ($item) use ($filterType) {
|
||||
return $item instanceof $filterType;
|
||||
});
|
||||
}
|
||||
return $iterator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Descendant can override this method to disallow insert a child by throwing an Nette\InvalidStateException.
|
||||
* @return void
|
||||
* @throws Nette\InvalidStateException
|
||||
*/
|
||||
protected function validateChildComponent(IComponent $child)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/********************* cloneable, serializable ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Object cloning.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->components) {
|
||||
$oldMyself = reset($this->components)->getParent();
|
||||
$oldMyself->cloning = $this;
|
||||
foreach ($this->components as $name => $component) {
|
||||
$this->components[$name] = clone $component;
|
||||
}
|
||||
$oldMyself->cloning = null;
|
||||
}
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is container cloning now?
|
||||
* @return Container|null
|
||||
* @internal
|
||||
*/
|
||||
public function _isCloning()
|
||||
{
|
||||
return $this->cloning;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\ComponentModel;
|
||||
|
||||
|
||||
/**
|
||||
* Provides functionality required by all components.
|
||||
*/
|
||||
interface IComponent
|
||||
{
|
||||
/** Separator for component names in path concatenation. */
|
||||
const NAME_SEPARATOR = '-';
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
function getName();
|
||||
|
||||
/**
|
||||
* Returns the parent container if any.
|
||||
* @return IContainer|null
|
||||
*/
|
||||
function getParent();
|
||||
|
||||
/**
|
||||
* Sets the parent of this component.
|
||||
* @param string $name
|
||||
* @return static
|
||||
*/
|
||||
function setParent(IContainer $parent = null, $name = null);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\ComponentModel;
|
||||
|
||||
|
||||
/**
|
||||
* Containers are objects that logically contain zero or more IComponent components.
|
||||
*/
|
||||
interface IContainer extends IComponent
|
||||
{
|
||||
/**
|
||||
* Adds the component to the container.
|
||||
* @param string|int|null $name
|
||||
* @return static
|
||||
*/
|
||||
function addComponent(IComponent $component, $name);
|
||||
|
||||
/**
|
||||
* Removes the component from the container.
|
||||
* @return void
|
||||
*/
|
||||
function removeComponent(IComponent $component);
|
||||
|
||||
/**
|
||||
* Returns component specified by name or path.
|
||||
* @param string|int $name
|
||||
* @return IComponent|null
|
||||
*/
|
||||
function getComponent($name);
|
||||
|
||||
/**
|
||||
* Iterates over descendants components.
|
||||
* @return \Iterator
|
||||
*/
|
||||
function getComponents();
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\ComponentModel;
|
||||
|
||||
|
||||
/**
|
||||
* Recursive component iterator. See Container::getComponents().
|
||||
* @internal
|
||||
*/
|
||||
class RecursiveComponentIterator extends \RecursiveArrayIterator implements \Countable
|
||||
{
|
||||
/**
|
||||
* Has the current element has children?
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChildren()
|
||||
{
|
||||
return $this->current() instanceof IContainer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The sub-iterator for the current element.
|
||||
* @return \RecursiveIterator
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return $this->current()->getComponents();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the count of elements.
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return iterator_count($this);
|
||||
}
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
build: off
|
||||
cache:
|
||||
- c:\php -> appveyor.yml
|
||||
- '%LOCALAPPDATA%\Composer\files -> appveyor.yml'
|
||||
|
||||
clone_folder: c:\projects\database
|
||||
|
||||
services:
|
||||
- mssql2008r2sp2
|
||||
# - mssql2012sp1
|
||||
# - mssql2014
|
||||
- mysql
|
||||
|
||||
init:
|
||||
- SET PATH=c:\php;%PATH%
|
||||
- SET PHP=1
|
||||
- SET ANSICON=121x90 (121x90)
|
||||
|
||||
install:
|
||||
# Install PHP
|
||||
- IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php)
|
||||
- IF %PHP%==1 cd c:\php
|
||||
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-5.6.14-Win32-VC11-x86.zip --output php.zip
|
||||
- IF %PHP%==1 7z x php.zip >nul
|
||||
- IF %PHP%==1 echo extension_dir=ext >> php.ini
|
||||
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
|
||||
- IF %PHP%==1 appveyor DownloadFile https://files.nette.org/misc/php-sqlsrv.zip
|
||||
- IF %PHP%==1 7z x php-sqlsrv.zip >nul
|
||||
- IF %PHP%==1 copy SQLSRV\php_pdo_sqlsrv_56_ts.dll ext\php_pdo_sqlsrv_ts.dll
|
||||
- IF %PHP%==1 del /Q *.zip
|
||||
- cd c:\projects\database
|
||||
|
||||
# Install Nette Tester
|
||||
- appveyor DownloadFile https://getcomposer.org/composer.phar
|
||||
- php composer.phar install --prefer-dist --no-interaction --no-progress
|
||||
|
||||
# Create databases.ini
|
||||
- copy tests\databases.appveyor.ini tests\Database\databases.ini
|
||||
|
||||
test_script:
|
||||
- vendor\bin\tester tests -s -c tests\php-win.ini
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "nette/database",
|
||||
"description": "💾 Nette Database: layer with a familiar PDO-like API but much more powerful. Building queries, advanced joins, drivers for MySQL, PostgreSQL, SQLite, MS SQL Server and Oracle.",
|
||||
"keywords": ["nette", "database", "notorm", "queries", "pdo", "mysql", "postgresql", "sqlite", "mssql", "oracle"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"ext-pdo": "*",
|
||||
"nette/caching": "^2.2",
|
||||
"nette/utils": "^2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"nette/di": "^2.4 || ~3.0.0",
|
||||
"mockery/mockery": "^1.0.0",
|
||||
"tracy/tracy": "^2.4"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/nette": "<2.2"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
How to contribute & use the issue tracker
|
||||
=========================================
|
||||
|
||||
Nette welcomes your contributions. There are several ways to help out:
|
||||
|
||||
* Create an issue on GitHub, if you have found a bug
|
||||
* Write test cases for open bug issues
|
||||
* Write fixes for open bug/feature issues, preferably with test cases included
|
||||
* Contribute to the [documentation](https://nette.org/en/writing)
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Please **do not use the issue tracker to ask questions**. We will be happy to help you
|
||||
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
**Feature requests** are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
|
||||
|
||||
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
|
||||
|
||||
Please do not fix whitespace, format code, or make a purely cosmetic patch.
|
||||
|
||||
Thanks! :heart:
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
Nette Database
|
||||
==============
|
||||
|
||||
[](https://packagist.org/packages/nette/database)
|
||||
[](https://travis-ci.org/nette/database)
|
||||
[](https://ci.appveyor.com/project/dg/database/branch/v2.4)
|
||||
|
||||
Nette provides a powerful layer for accessing your database easily.
|
||||
|
||||
- composes SQL queries with ease
|
||||
- easily fetches data
|
||||
- uses efficient queries and does not transmit unnecessary data
|
||||
|
||||
The [Nette Database Core](https://doc.nette.org/database-core) is a wrapper around the PDO and provides core functionality.
|
||||
|
||||
The [Nette Database Explorer](https://doc.nette.org/database-explorer) layer helps you to fetch database data more easily and in a more optimized way.
|
||||
|
||||
[Please see documentation on our website](https://doc.nette.org/database).
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\DatabaseDI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Nette Framework Database services.
|
||||
*/
|
||||
class DatabaseExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public $databaseDefaults = [
|
||||
'dsn' => null,
|
||||
'user' => null,
|
||||
'password' => null,
|
||||
'options' => null,
|
||||
'debugger' => true,
|
||||
'explain' => true,
|
||||
'reflection' => null, // BC
|
||||
'conventions' => 'discovered', // Nette\Database\Conventions\DiscoveredConventions
|
||||
'autowired' => null,
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
private $debugMode;
|
||||
|
||||
|
||||
public function __construct($debugMode = false)
|
||||
{
|
||||
$this->debugMode = $debugMode;
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
$configs = $this->getConfig();
|
||||
foreach ($configs as $k => $v) {
|
||||
if (is_scalar($v)) {
|
||||
$configs = ['default' => $configs];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$defaults = $this->databaseDefaults;
|
||||
$defaults['autowired'] = true;
|
||||
foreach ((array) $configs as $name => $config) {
|
||||
if (!is_array($config)) {
|
||||
continue;
|
||||
}
|
||||
$config = $this->validateConfig($defaults, $config, $this->prefix($name));
|
||||
$defaults['autowired'] = false;
|
||||
$this->setupDatabase($config, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function setupDatabase($config, $name)
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
|
||||
foreach ((array) $config['options'] as $key => $value) {
|
||||
if (is_string($value) && preg_match('#^PDO::\w+\z#', $value)) {
|
||||
$config['options'][$key] = $value = constant($value);
|
||||
}
|
||||
if (preg_match('#^PDO::\w+\z#', $key)) {
|
||||
unset($config['options'][$key]);
|
||||
$config['options'][constant($key)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$connection = $builder->addDefinition($this->prefix("$name.connection"))
|
||||
->setFactory(Nette\Database\Connection::class, [$config['dsn'], $config['user'], $config['password'], $config['options']])
|
||||
->setAutowired($config['autowired']);
|
||||
|
||||
$structure = $builder->addDefinition($this->prefix("$name.structure"))
|
||||
->setFactory(Nette\Database\Structure::class)
|
||||
->setArguments([$connection])
|
||||
->setAutowired($config['autowired']);
|
||||
|
||||
if (!empty($config['reflection'])) {
|
||||
$conventionsServiceName = 'reflection';
|
||||
$config['conventions'] = $config['reflection'];
|
||||
if (is_string($config['conventions']) && strtolower($config['conventions']) === 'conventional') {
|
||||
$config['conventions'] = 'Static';
|
||||
}
|
||||
} else {
|
||||
$conventionsServiceName = 'conventions';
|
||||
}
|
||||
|
||||
if (!$config['conventions']) {
|
||||
$conventions = null;
|
||||
|
||||
} elseif (is_string($config['conventions'])) {
|
||||
$conventions = $builder->addDefinition($this->prefix("$name.$conventionsServiceName"))
|
||||
->setFactory(preg_match('#^[a-z]+\z#i', $config['conventions'])
|
||||
? 'Nette\Database\Conventions\\' . ucfirst($config['conventions']) . 'Conventions'
|
||||
: $config['conventions'])
|
||||
->setArguments(strtolower($config['conventions']) === 'discovered' ? [$structure] : [])
|
||||
->setAutowired($config['autowired']);
|
||||
|
||||
} else {
|
||||
$class = method_exists(Nette\DI\Helpers::class, 'filterArguments') ? Nette\DI\Helpers::class : Nette\DI\Compiler::class;
|
||||
$conventions = $class::filterArguments([$config['conventions']])[0];
|
||||
}
|
||||
|
||||
$builder->addDefinition($this->prefix("$name.context"))
|
||||
->setFactory(Nette\Database\Context::class, [$connection, $structure, $conventions])
|
||||
->setAutowired($config['autowired']);
|
||||
|
||||
if ($config['debugger']) {
|
||||
$connection->addSetup('@Tracy\BlueScreen::addPanel', [
|
||||
'Nette\Bridges\DatabaseTracy\ConnectionPanel::renderException',
|
||||
]);
|
||||
if ($this->debugMode) {
|
||||
$connection->addSetup('Nette\Database\Helpers::createDebugPanel', [$connection, !empty($config['explain']), $name]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->name === 'database') {
|
||||
$builder->addAlias($this->prefix($name), $this->prefix("$name.connection"));
|
||||
$builder->addAlias("nette.database.$name", $this->prefix($name));
|
||||
$builder->addAlias("nette.database.$name.context", $this->prefix("$name.context"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\DatabaseTracy;
|
||||
|
||||
use Nette;
|
||||
use Nette\Database\Helpers;
|
||||
use Tracy;
|
||||
|
||||
|
||||
/**
|
||||
* Debug panel for Nette\Database.
|
||||
*/
|
||||
class ConnectionPanel implements Tracy\IBarPanel
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
public $maxQueries = 100;
|
||||
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
/** @var bool|string explain queries? */
|
||||
public $explain = true;
|
||||
|
||||
/** @var bool */
|
||||
public $disabled = false;
|
||||
|
||||
/** @var int logged time */
|
||||
private $totalTime = 0;
|
||||
|
||||
/** @var int */
|
||||
private $count = 0;
|
||||
|
||||
/** @var array */
|
||||
private $queries = [];
|
||||
|
||||
|
||||
public function __construct(Nette\Database\Connection $connection)
|
||||
{
|
||||
$connection->onQuery[] = [$this, 'logQuery'];
|
||||
}
|
||||
|
||||
|
||||
public function logQuery(Nette\Database\Connection $connection, $result)
|
||||
{
|
||||
if ($this->disabled) {
|
||||
return;
|
||||
}
|
||||
$this->count++;
|
||||
|
||||
$source = null;
|
||||
$trace = $result instanceof \PDOException ? $result->getTrace() : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
foreach ($trace as $row) {
|
||||
if (isset($row['file']) && is_file($row['file']) && !Tracy\Debugger::getBluescreen()->isCollapsed($row['file'])) {
|
||||
if ((isset($row['function']) && strpos($row['function'], 'call_user_func') === 0)
|
||||
|| (isset($row['class']) && is_subclass_of($row['class'], '\\Nette\\Database\\Connection'))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$source = [$row['file'], (int) $row['line']];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($result instanceof Nette\Database\ResultSet) {
|
||||
$this->totalTime += $result->getTime();
|
||||
if ($this->count < $this->maxQueries) {
|
||||
$this->queries[] = [$connection, $result->getQueryString(), $result->getParameters(), $source, $result->getTime(), $result->getRowCount(), null];
|
||||
}
|
||||
|
||||
} elseif ($result instanceof \PDOException && $this->count < $this->maxQueries) {
|
||||
$this->queries[] = [$connection, $result->queryString, null, $source, null, null, $result->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function renderException($e)
|
||||
{
|
||||
if (!$e instanceof \PDOException) {
|
||||
return;
|
||||
}
|
||||
if (isset($e->queryString)) {
|
||||
$sql = $e->queryString;
|
||||
|
||||
} elseif ($item = Tracy\Helpers::findTrace($e->getTrace(), 'PDO::prepare')) {
|
||||
$sql = $item['args'][0];
|
||||
}
|
||||
return isset($sql) ? [
|
||||
'tab' => 'SQL',
|
||||
'panel' => Helpers::dumpSql($sql),
|
||||
] : null;
|
||||
}
|
||||
|
||||
|
||||
public function getTab()
|
||||
{
|
||||
$name = $this->name;
|
||||
$count = $this->count;
|
||||
$totalTime = $this->totalTime;
|
||||
ob_start(function () {});
|
||||
require __DIR__ . '/templates/ConnectionPanel.tab.phtml';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
public function getPanel()
|
||||
{
|
||||
$this->disabled = true;
|
||||
if (!$this->count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->name;
|
||||
$count = $this->count;
|
||||
$totalTime = $this->totalTime;
|
||||
$queries = [];
|
||||
foreach ($this->queries as $query) {
|
||||
list($connection, $sql, $params, $source, $time, $rows, $error) = $query;
|
||||
$explain = null;
|
||||
if (!$error && $this->explain && preg_match('#\s*\(?\s*SELECT\s#iA', $sql)) {
|
||||
try {
|
||||
$cmd = is_string($this->explain) ? $this->explain : 'EXPLAIN';
|
||||
$explain = $connection->queryArgs("$cmd $sql", $params)->fetchAll();
|
||||
} catch (\PDOException $e) {
|
||||
}
|
||||
}
|
||||
$query[] = $explain;
|
||||
$queries[] = $query;
|
||||
}
|
||||
|
||||
ob_start(function () {});
|
||||
require __DIR__ . '/templates/ConnectionPanel.panel.phtml';
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Nette\Bridges\DatabaseTracy;
|
||||
|
||||
use Nette\Database\Helpers;
|
||||
use Tracy;
|
||||
|
||||
?>
|
||||
<style class="tracy-debug">
|
||||
#tracy-debug td.nette-DbConnectionPanel-sql { background: white !important }
|
||||
#tracy-debug .nette-DbConnectionPanel-source { color: #BBB !important }
|
||||
#tracy-debug .nette-DbConnectionPanel-explain td { white-space: pre }
|
||||
</style>
|
||||
|
||||
<h1 title="<?= htmlspecialchars($connection->getDsn(), ENT_QUOTES, 'UTF-8') ?>">Queries: <?php
|
||||
echo $count, ($totalTime ? sprintf(', time: %0.3f ms', $totalTime * 1000) : ''), ', ', htmlspecialchars($name, ENT_NOQUOTES, 'UTF-8') ?></h1>
|
||||
|
||||
<div class="tracy-inner">
|
||||
<div class="tracy-inner-container">
|
||||
<table>
|
||||
<tr><th>Time ms</th><th>SQL Query</th><th>Rows</th></tr>
|
||||
<?php
|
||||
foreach ($queries as $query):
|
||||
list($connection, $sql, $params, $source, $time, $rows, $error, $explain) = $query;
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if ($error): ?>
|
||||
<span title="<?= htmlspecialchars($error, ENT_IGNORE | ENT_QUOTES, 'UTF-8') ?>">ERROR</span>
|
||||
<?php elseif ($time !== null): echo sprintf('%0.3f', $time * 1000); endif ?>
|
||||
<?php if ($explain): ?>
|
||||
<br /><a class="tracy-toggle tracy-collapsed" data-tracy-ref="^tr .nette-DbConnectionPanel-explain">explain</a>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="nette-DbConnectionPanel-sql"><?= Helpers::dumpSql($sql, $params, $connection) ?>
|
||||
<?php if ($explain): ?>
|
||||
<table class="tracy-collapsed nette-DbConnectionPanel-explain">
|
||||
<tr>
|
||||
<?php foreach ($explain[0] as $col => $foo): ?>
|
||||
<th><?= htmlspecialchars($col, ENT_NOQUOTES, 'UTF-8') ?></th>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
<?php foreach ($explain as $row): ?>
|
||||
<tr>
|
||||
<?php foreach ($row as $col): ?>
|
||||
<td><?= htmlspecialchars($col, ENT_NOQUOTES, 'UTF-8') ?></td>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
<?php if ($source) echo substr_replace(Tracy\Helpers::editorLink($source[0], $source[1]), ' class="nette-DbConnectionPanel-source"', 2, 0); ?>
|
||||
</td>
|
||||
<td><?= $rows ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php if (count($queries) < $count): ?><p>...and more</p><?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user