Jak simulovat HttpRequest?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Tomik
Nette Evangelist | 485
+
0
-

Zdravím!
Rád bych do svého systému implementoval tuto vlastnost – při přidání, resp. upravení textu se veškerá URI, která jdou na některý z Presenterů aplikace nahradí za nějakou pseudo-syntax (klidně podobnou CurlyBrackets), např. URI http://www.web.cz/ahoj/edit se nahradí za {link Pages:edit 'uri' => 'ahoj'}, či něco podobného, poté při zobrazování tuto pseudo-syntax pouze převedu na aktuální URI. Jakou to má výhodu? Může se změnit schéma URI webu (myšleno definice rout) a není třeba v žádném textu upravovat jediný odkaz.

A teď k dotazu:

Už jsem se pustil do implementace, regulárem najdu všechna URI v odkazech (resp. v src|href|action|on[a-z]+), a potom bych na tyto jednotlivá URI chtěl aplikovat podobný princip zpracování jako probíhá u RoutingDebuggeru. A pokud dojde k schodě, tj. nalezení Presenteru a action k danému URI, pak jej nahradit za pseudo-syntax odkazu, pokud ke shodě nedojde, pak nechat normální URI – to které tam bylo (tj. URI pravděpodobně vede mimo web, resp. vede např. na obrázek, či styl) – proto jej nebudu nahrazovat.

Ale! Princip zjišťování v RoutingDebuggeru zda URI odpovídá nějaké routě (resp. jejímu presenteru a action) je založen na HttpRequest, a tu nelze vytvořit jinou než s URI právě položeného požadavku. Chci se tedy zeptat, zda je možné nějak HttpRequest objekt nějak simulovat pro konkrétní URI (tj. jinou adresu, než která je pro právě položený request – tedy pro jinou adresu, než která je zobrazena v adres-baru), nebo je nutné kompletně pro tento požadavek přepsat metodu match v Routě (tak, aby pokud to potřebuji nebrala informace z HttpRequest, ale třeba z objektu Uri, který jí nějak předám).

Díky za rady. :)

Jan Tvrdík
Nette guru | 2595
+
0
-

Nešlo by podědit a upravit HttpRequest nebo vyrobit vlastní třídu implementující IHttpRequest?

PS: Nechápu, co myslíš tím simulováním RoutingDebuggeru. Mělo by stačit zavolat Environment::getApplication()->router->match($httpRequest).

EDIT: Vypadá to, že stačí, když si třídu HttpRequest podědíš a napíšeš si metodu __construct, která naplní atributy hodnotami, takže k volání detectUri a initialize vůbec nedojde.

Editoval Jan Tvrdík (8. 5. 2009 18:03)

kravčo
Člen | 721
+
0
-

Vzhľadom na to, ako je napísaný HttpRequest, cezeň to asi nepôjde. Vďaka otvorenosti však Router vyžaduje interface IHttpRequest, čiže ti nič nebráni spraviť veľmi jednoduchý MockHttpRequest, ktorému v konštruktore alebo pomocou setterov nastavíš všetko čo treba…

class MockHttpRequest extends Object implements IHttpRequest
{
	public function __construct($uri)
	{
		// ...
	}

	public function setUri($uri)
	{
		// ...
	}

	// ...
}
Tomik
Nette Evangelist | 485
+
0
-

kravco napsal(a):

Vzhľadom na to, ako je napísaný HttpRequest, cezeň to asi nepôjde. Vďaka otvorenosti však Router vyžaduje interface IHttpRequest, čiže ti nič nebráni spraviť veľmi jednoduchý MockHttpRequest, ktorému v konštruktore alebo pomocou setterov nastavíš všetko čo treba…

O tomhle jsem taky uvažoval. Asi to bude nejjednodušší. Díky.

Tomik
Nette Evangelist | 485
+
0
-

Jan Tvrdík napsal(a):

PS: Nechápu, co myslíš tím simulováním RoutingDebuggeru. Mělo by stačit zavolat Environment::getApplication()->router->match($httpRequest).

Máš pravdu, tohle mě fakt nenapadlo. :) Já zbytečně procházel každou subroutu Multirouteru. Nějak mě nedoclaklo, že zjistit Routu, která odpovídá nějakému requestu jde mnohem snáze. ;)

Díky vám oboum za spolupráci. ;)

xificurk
Člen | 121
+
0
-

Doufám, že jsem vykopíroval ze svých Helperů vše podstatné…

Použití:

  • Před uložením textu do databáze zavolat Helpers::url2PresenterLink($text)
  • Vytažený text z databáze v šabloně můžeme prohnat helperem, příp. Texy pomocí {!$text|PL2U|texy}
<?php

/**
 * Helpers class
 *
 * Copyright (c) 2008, 2009 Petr Moravek [Xificurk]
 *
 * This source file is subject to the General Public License (GPL)
 * http://www.gnu.org/copyleft/gpl.txt
 *
 * This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @copyright  Copyright (c) 2008, 2009 Petr Moravek [Xificurk]
 * @license    General Public License (GPL), http://www.gnu.org/copyleft/gpl.txt
 * @version    0.2
 */

/**
 * Helpers class.
 *
 * @author     Petr Morávek [Xificurk]
 * @copyright  Copyright (c) 2008 Petr Morávek [Xificurk]
 */
class Helpers extends Object
{

    /**
     * Static class - cannot be instantiated.
     */
    final public function __construct()
    {
        throw new LogicException("Cannot instantiate static class " . get_class($this));
    }

    /**
     * @param string name
     * @return callback
     */
    public static function helperLoader($name)
    {
        switch ($name) {
        case 'pl2u':
            return array('Helpers', 'presenterLink2Url');
        }
    }


    /********************* links *********************/

    /** @const */
    /* PCRE for URL taken from David Grudl's Texy! class TexyLinkModule (deleted ftp scheme) // GPL license */
    const PAT_URL = '#(?<=^|[\s([<:\x17])(?:https?://|www\.)[0-9.A-Za-z\x{C0}-\x{2FF}\x{370}-\x{1EFF}-][/\dA-Za-z\x{C0}-\x{2FF}\x{370}-\x{1EFF}+\.~%&?@=_:;\#,\x{ad}-]+[/\dA-Za-z\x{C0}-\x{2FF}\x{370}-\x{1EFF}+~%?@=_\#]#u';

    /**
     * Convert URLs to Presenter Links where possible.
     * @param  string text
     * @return string
     */
    public static function url2PresenterLink($text)
    {
        return preg_replace_callback(self::PAT_URL, array(__CLASS__, 'cb_url2PresenterLink'), $text);
    }

    /**
     * Callback for url2PresenterLink
     * @param  array
     * @return string
     */
    private static function cb_url2PresenterLink($url)
    {
        $req = preg_replace('#^www\.#i', 'http://', $url[0]);
        $req = new UriHttpRequest($req);
        $app = Environment::getApplication()->getRouter()->match($req);

        if (!$app) {
            return $url[0];
        }

        $params = $app->params;
        $presenterName = ':'. $app->presenterName . ':' . $params['action'];
        unset($params['action']);
        $arguments = '';
        if (!empty($params)) {
            $arguments = ', ' . preg_replace('#^array\s*\((.*)\)$#i', '\1', str_replace("\n", '', var_export($params, true)));
        }
        $plink = '{plink //' .
            $presenterName .
            (!empty($req->uri->fragment) ? '#' . $req->uri->fragment : '') .
            $arguments .
            '}';

        return $plink;
    }

    /**
     * Converts {plink :Module:Presenter:view array('param1' => 'val')} to URL where possible
     * @param  string text
     * @return string
     */
    public static function presenterLink2Url($string)
    {

        return preg_replace_callback(
            '~\\{plink( [^}]*?)\\}~xsi',
            array(__CLASS__, 'cb_presenterLink2Url'),
            $string
        );
    }

    /**
     * Callback for presenterLink2Url
     * @param  array
     * @return string
     */
    private static function cb_presenterLink2Url($link)
    {
        $var = trim($link[1]);

        $presenterName = NULL;
        $presenterParams = array();
        if (preg_match('#^([^\s,]+),?\s*(.*)$#', $var, $m)) {
            $presenterName = preg_replace('#[\'"]?(.*)[\'"]#', '\\1', $m[1]);
            if ($m[2]) {
                eval('$presenterParams = array(' . $m[2] . ');');
            }
        }

        $oldMode = Presenter::$invalidLinkMode;
        Presenter::$invalidLinkMode = Presenter::INVALID_LINK_EXCEPTION;
        try {
            $ret = TemplateHelpers::escapeHtml(Environment::getApplication()->presenter->link($presenterName, $presenterParams));
        } catch (Exception $e) {
            $ret = $link[0];
        }

        Presenter::$invalidLinkMode = $oldMode;
        return $ret;
    }

}
?>
<?php

/**
 * UriHttpRequest class
 *
 * Copyright (c) 2008, 2009 Petr Moravek [Xificurk]
 *
 * This source file is subject to the General Public License (GPL)
 * http://www.gnu.org/copyleft/gpl.txt
 *
 * This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @copyright  Copyright (c) 2008, 2009 Petr Moravek [Xificurk]
 * @license    General Public License (GPL), http://www.gnu.org/copyleft/gpl.txt
 * @version    0.3
 */

/**
 * UriHttpRequest class.
 *
 * @author     Petr Morávek [Xificurk]
 * @copyright  Copyright (c) 2008 Petr Morávek [Xificurk]
 */
class UriHttpRequest extends Object implements IHttpRequest
{

    /** @var array */
    protected $query;

    /** @var UriScript {@link HttpRequest::getUri()} */
    protected $uri;

    /**
     * @param string URI
     */
    public function __construct($uri)
    {
        $origUri = parse_url($uri);

        $uri = $this->uri = new UriScript;

        $uri->scheme   = isset($origUri['scheme']) ? $origUri['scheme'] : 'http';
        $uri->path     = isset($origUri['path']) ? $origUri['path'] : '/';
        $uri->user     = isset($origUri['user']) ? $origUri['user'] : '';
        $uri->pass     = isset($origUri['pass']) ? $origUri['pass'] : '';
        $uri->host     = isset($origUri['host']) ? $origUri['host'] : '';
        $uri->port     = isset($origUri['port']) ? $origUri['port'] : 80;
        $uri->fragment = isset($origUri['fragment']) ? $origUri['fragment'] : '';

        if (!empty($origUri['query'])) {
            parse_str($origUri['query'], $this->query);
        } else {
            $this->query = array();
        }

// Taken from David Grudl's Nette class Nette\HttpRequest->detectUri() (rev 283) // Nette license
        // normalized uri
        $uri->canonicalize();

        // detect base URI-path - inspired by Zend Framework (c) Zend Technologies USA Inc. (http://www.zend.com), new BSD license
        $filename = basename($_SERVER['SCRIPT_FILENAME']);

        if (basename($_SERVER['SCRIPT_NAME']) === $filename) {
            $scriptPath = rtrim($_SERVER['SCRIPT_NAME'], '/');

        } elseif (basename($_SERVER['PHP_SELF']) === $filename) {
            $scriptPath = $_SERVER['PHP_SELF'];

        } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) {
            $scriptPath = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility

        } else {
            // Backtrack up the script_filename to find the portion matching php_self
            $path = $_SERVER['PHP_SELF'];
            $segs = explode('/', trim($_SERVER['SCRIPT_FILENAME'], '/'));
            $segs = array_reverse($segs);
            $index = 0;
            $last = count($segs);
            $scriptPath = '';
            do {
                $seg = $segs[$index];
                $scriptPath = '/' . $seg . $scriptPath;
                $index++;
            } while (($last > $index) && (FALSE !== ($pos = strpos($path, $scriptPath))) && (0 != $pos));
        }

        // Does the scriptPath have anything in common with the request_uri?
        if (strncmp($uri->path, $scriptPath, strlen($scriptPath)) === 0) {
            // whole $scriptPath in URL
            $uri->scriptPath = $scriptPath;

        } elseif (strncmp($uri->path, $scriptPath, strrpos($scriptPath, '/') + 1) === 0) {
            // directory portion of $scriptPath in URL
            $uri->scriptPath = substr($scriptPath, 0, strrpos($scriptPath, '/') + 1);

        } elseif (strpos($uri->path, basename($scriptPath)) === FALSE) {
            // no match whatsoever; set it blank
            $uri->scriptPath = '/';

        } elseif ((strlen($uri->path) >= strlen($scriptPath))
            && ((FALSE !== ($pos = strpos($uri->path, $scriptPath))) && ($pos !== 0))) {
            // If using mod_rewrite or ISAPI_Rewrite strip the script filename
            // out of scriptPath. $pos !== 0 makes sure it is not matching a value
            // from PATH_INFO or QUERY_STRING
            $uri->scriptPath = substr($uri->path, 0, $pos + strlen($scriptPath));

        } else {
            $uri->scriptPath = $scriptPath;
        }
    }

    /**
     * Returns URL object.
     * @return UriScript
     */
    function getUri()
    {
        return $this->uri;
    }

    /**
     * Returns variable provided to the script via URL query ($_GET).
     * If no key is passed, returns the entire array.
     * @param  string key
     * @param  mixed  default value
     * @return mixed
     */
    function getQuery($key = NULL, $default = NULL)
    {
        if (func_num_args() === 0) {
            return $this->query;

        } elseif (isset($this->query[$key])) {
            return $this->query[$key];

        } else {
            return $default;
        }
    }

    /**
     * Returns variable provided to the script via POST method ($_POST).
     * If no key is passed, returns the entire array.
     * @param  string key
     * @param  mixed  default value
     * @return mixed
     */
    function getPost($key = NULL, $default = NULL)
    {
        if (func_num_args() === 0) {
            // We don't have post, return empty array
            return array();

        } else {
            return $default;
        }
    }

    /**
     * Returns HTTP POST data in raw format (only for "application/x-www-form-urlencoded").
     * @return string
     */
    function getPostRaw()
    {
        // We don't have post
        return "";
    }

    /**
     * Returns uploaded file.
     * @param  string key (or more keys)
     * @return HttpUploadedFile
     */
    function getFile($key)
    {
        if (func_num_args() === 0) {
            // We don't have files, return empty array
            return array();

        } else {
            return NULL;
        }
    }

    /**
     * Returns uploaded files.
     * @return array
     */
    function getFiles()
    {
        // We don't have files, return empty array
        return array();
    }

    /**
     * Returns variable provided to the script via HTTP cookies.
     * @param  string key
     * @param  mixed  default value
     * @return mixed
     */
    function getCookie($key, $default = NULL)
    {
        if (func_num_args() === 0) {
            // We don't have cookies, return empty array
            return array();
        } else {
            return $default;
        }
    }

    /**
     * Returns variables provided to the script via HTTP cookies.
     * @return array
     */
    function getCookies()
    {
        // We don't have cookies, return empty array
        return array();
    }

    /**
     * Returns HTTP request method (GET, POST, HEAD, PUT, ...). The method is case-sensitive.
     * @return string
     */
    function getMethod()
    {
        // always assume GET
        return 'GET';
    }

    /**
     * Checks HTTP request method.
     * @param  string
     * @return bool
     */
    function isMethod($method)
    {
        // always assume GET
        return strcasecmp('GET', $method) === 0;
    }

    /**
     * Return the value of the HTTP header. Pass the header name as the
     * plain, HTTP-specified header name (e.g. 'Accept-Encoding').
     * @param  string
     * @param  mixed
     * @return mixed
     */
    function getHeader($header, $default = NULL)
    {
        // no headers
        return $default;
    }

    /**
     * Returns all HTTP headers.
     * @return array
     */
    function getHeaders()
    {
        // no headers
        return array();
    }

    /**
     * Is the request is sent via secure channel (https).
     * @return bool
     */
    function isSecured() {
        // assume unsecured connection
        return FALSE;
    }

    /**
     * Is AJAX request?
     * @return bool
     */
    function isAjax()
    {
        // assume normal request
        return FALSE;
    }

    /**
     * Returns the IP address of the remote client.
     * @return string
     */
    function getRemoteAddress()
    {
        // no remote_addr
        return NULL;
    }

    /**
     * Returns the host of the remote client.
     * @return string
     */
    function getRemoteHost()
    {
        // no remote_host
        return NULL;
    }

}
?>

Editoval xificurk (15. 5. 2009 13:04)

Tomik
Nette Evangelist | 485
+
0
-

xificurk: Supr. Díky! :)

xificurk
Člen | 121
+
0
-

Ještě jsem si vzpomněl na jednu poznámku: Zatím jsem nevymyslel, jak to vyřešit lépe, ale…

Je nutné mít routy včetně domény, protože jinak to suveréně naparsuje http://example.com/ na {plink //DefaultPresenter:}. Jiným řešením by bylo upravit regulár v helperu pro použití na konkrétních doménách, ale ty routy mi přišly jako menší zlo.

David Grudl
Nette Core | 8228
+
0
-

Ten regulár z Texy budiž pod BSD, MIT i LGPL ;)