Vzájemné propojení souborů

Školení, která pořádám

Když mají mít jednotlivé stránky prezentace stejný vzhled, lze to zařídit v zásadě dvěma způsoby. Prvním způsobem je odkazovat vždy na stejný soubor a jako parametr mu předat název souboru, který má být použit uvnitř – něco jako index.php?action=kontakt. Tento způsob ale má několik problémů:

Druhým způsobem je odkazovat se přímo na cílové soubory (např. kontakt.php) a vložení společného kódu zařídit z nich. Já používám zhruba toto:

<?php
include "include/lib.inc.php"; // vložení všech potřebných knihoven, připojení k databázi, případné ověření uživatele
// kód, který se provede před odesláním jakéhokoliv výstupu
page_header("Nadpis");

// hlavní kód stránky

page_footer();
?>

Tento způsob má ještě jednu výhodu – kód dává smysl sám o sobě, přímo obsahuje svůj kontext. Když se někdo neobeznámený s logikou vkládání podívá u prvního způsobu do jakéhokoliv souboru, tak nemůže pochopit, kde se vzaly např. proměnné, které soubor používá. U druhého způsobu je naopak na první pohled jasné, že musely být vytvořeny v souboru include/lib.inc.php.

Přijďte si o tomto tématu popovídat na školení Bezpečnost PHP aplikací.

Jakub Vrána, Dobře míněné rady, 1.6.2005, diskuse: 34 (nové: 0)

Diskuse

MaReK Penguin_007 Olšavský:

Výborně, skvěle, dokumentuje to asi cestu, kterou prošel kdekdo, moje osobní stránky jsou řešeny přesně stylem index.php?subj=xxx&lang=yy, dneska tvořím každou stránku samostatně a i při použití smarty mám jednu šablonu, která definuje layout a mnoho dalších (includovaných), které definují zobrazení konkrétního obsahu...

Llaik:

Naznacene reseni ma jednu nevyhodu - co kdyz na nekterych strankach nepotrebujes pripojeni k dabatazi? Co kdyz jedna stranka ma jinou paticku (napriklad se neodpojuje od databaze :))?

Jinak druhy bod pripominky (neosetrene vlozeni souboru) preci neni problem:
$povolene_stranky = array('index', 'napoveda', 'kniha_navstev');
- pak staci overit, zda v action je neco z pole a mate overeni hotove, ne? Pole se da rozsirit o napriklad minimalni level autorizace a je to jako lusknuti prstem.

Osetreni proti primemu spusteni je lehke - bylo liche ho davat do problemu (je to vazne vetsi problem, nez napriklad nepripojeni k db, pokud to neni treba?)

Oboje ma sva pro a proti, proc to vyznelo tak jednostrane?

ikona Jakub Vrána OpenID:

Jednostranně to vyznělo proto, že mnou zatracované řešení jednoduše žádné výhody nemá. Konec konců i ty jenom říkáš, že ty nevýhody vlastně nejsou tak velké - nejsou velké, ale zůstávají. Všechny tři popsané nevýhody se dají ošetřit, ale s vynaložením nemalého úsilí se dostanu jenom k tomu, co mám u druhého způsobu zadarmo. V závěru článku popisovaná čtvrtá nevýhoda se navíc neodstraní vůbec.

Individualizace lze řešit velice snadno. Buď pomocí:
<?php
include "include/connect.inc.php"; // tam kde potřebuji
include "include/lib.inc.php"; // všude
?>
Nebo předáváním parametrů funkcím (v mém příkladě např. různý design). Nebo i ošetřením přímo ve vkládaném souboru:
<?php
if (!$nepripojovat) {
    mysql_connect();
}

// případně
if (!in_array(basename($_SERVER["PHP_SELF"]), $bez_pripojeni)) {
  mysql_connect();
}
?>

Kladení dalšího odporu je zbytečné :-).

Llaik:

<?php
if ($nepripojovat) // ...?
?>

tak pozor, abys to dobre osetril :) prijde ti to jako elegantni reseni? Co kdyz 60% skriptu potrebuje nacist informaci o uzivateli, a zbytek ne? Dalsi podminka?

<?php
if ($pripojovat) ...
if (
$nacist_informace)...
?>
a pak v index.php neco jako:
<?php
$pripojovat
= 1;
$nacist_informace = 0;
include ...?
?>

pak ale nezapomen, ze musis osetrit druhou nevyhodu druheho reseni take - tj. pokazde kdyz v include souboru udelas dalsi vyjimku, vsude jinde je musis zavest! Pripadne to resit pomoci konstant a "if defined", budiz, to zni jako ciste reseni :)

A samozrejme - musis ohlidat i treti nevyhodu druheho reseni, co kdyz se nekdo proboucha primo na tu knihovnu?

Ke 4. nevyhode - ty predpokladas, ze je to jediny vkladany soubor, tj. promenna urcite bude v nem? To je preci hloupost - take muze byt v nastaveni, nebo v nejakych podminene vkladanych skriptech, ne? Je naivni predpokladat, ze vetsi projekt bude vkladat jen jeden soubor. Bude jich vkladat treba 10 a pak stejne nevis, kde se promenna vzala (tedy do okamziku, kdy se podivas do dokumentace).

Jiste, druhe reseni ma sve mouchy, ale je kratkozrake je nevidet ani u toho prvniho. Dovedu si predstavit nekolik ruznych aplikaci, kdy to druhe zjednodussi praci (jako ze poslednich nekolik mesice delam jiny projekt, kde se jako lepsi v dobe designu ukazalo to, ktere ty tu tak vychvalujes)

Odpor marny. Programovani neni chuze kolem pravitka.

ikona Jakub Vrána OpenID:

Vše podstatné už padlo, jenom tedy dovysvětlím tu čtvrtou nevýhodu - samozřejmě může být vložených souborů více a není tedy ihned poznat, kde se proměnná vzala. Problém je v tom, že když si otevřu soubor, který na začátku nemá žádné include a přesto používá nějaké proměnné, tak nepoznám ani to, kde je mám hledat - problém spočívá v tom, že soubor je vytržen z kontextu.

Co se týče podmíněného provedení některých částí, nabízí mnou vyzdvihovaný způsob víc možných řešení. Jedno z nich je ekvivalentní prvnímu způsobu:
<?php
// index.php?action=...
if ($_GET["action"] != "kontakt") {
    mysql_connect();
}

// kontakt.php - relevantní kód v lib.inc.php
if (basename($_SERVER["PHP_SELF"]) != "kontakt.php") {
    mysql_connect();
}
?>

Llaik:

Soubor neni vytrzen z kontextu, protoze ty vis, ze je includovany, protoze to ma uvedene ve sve hlavicce + je to samozrejme v programatorske docce (tj. precetl sis to minimalne na dvou mistech, nez jsi ho otevrel)

tve reseni tedy vypada asi takto:
<?php
if ($_GET['action'] != 'kontakt' && $_GET['action'] != 'faq' && $_GET['action'] != 'stranka_blabolu' .... ... ..) {
  mysql_connect();
}
?>
?

No, toto je ale spina a balast (kterou muze spatny programator ale spachat v obou resenich).

Takze ty problemy "ne tveho reseni" shrnme:
1] oh
2] "Soubor index.php musí být ošetřen proti neoprávněnému vložení cizích souborů." - to se tyka obou reseni
3] "Vkládané soubory musí být zajištěny proti spuštění bez kontextu" - to se tyka obou reseni
4] "kód dává smysl sám o sobě, přímo obsahuje svůj kontext" - neni pravda ani u jednoho z pripadu (krome toho to resi docka, tj. irelevantni tu o tom psat)

tj. ta reseni maji stejne problemy, vyhodou toho tveho muze byt hezci url (misto /action.php je to /?p=action).

Jinak bych volil vzdy takove reseni, ktere co nejjednoduseji vyresi dany problem. Coz jak rikam - nekdy muze byt jedno, jindy druhe.

Protoze u vetsiho projektu, kde to tebou prosazovane zacina sklizet vice uspechu, se to muze dostat diky vsemoznym IFum do stavu, kdy zjistis, ze pro tvou potrebu jsou spatna OBE tato reseni a vytvoris zkratka jine a lepsi :) (pro inspiraci si zkus udelat treba debiani balicek a pak se zamysli, zda by nektere veci nesli pouzit jako inspirace? Nevim, jen me to ted napadlo...)

ikona spaze:

ad 1] da se vyresit jednoduchym prepisem URL -- u obou pripadu.

Llaik:

jiste, ale to bychom uz byli uplne mimo tema :)

srigi:

Llaik >> hore pises, ze osetrit priame spustenie je lahke. PLS popis ako.

Shippy:

Ještě existuje jeden důvod, proč se ono zatracované řešení může hodit. Co když se jména souborů mění? Co když za chvíli budu chtít hodit include_once('inc.lib.php5')? Co potom? Budu měnit desítky či stovky souborů jen proto, že jsou takto udělané?
Ne. Změním index.php a bude vymalováno.

ikona Jakub Vrána OpenID:

Pokud by bylo potřeba názvy souborů měnit, tak byly špatně navržené. Lepší je navrhnout je rovnou správně.

ikona Marty:

To ano, například na jednom serveru jsem vytvářel stránky a byl jsem nucen přejmenovat příponu z .php na .php5, protože doma jsem měl PHP5 a na serveru, ač mi bylo slíbeno, že tam bude také PHP5, PHP5 nebylo.

Jarek:

No, pripojeni k databazi se da samozrejme resit 'ondemand', cili az ve chvili, kdy si skript rekne o  spojeni s db zavolanim nejake funkce. Takhle se da resit vetsina casove narocnych veci (u me napriklad nacteni xml s definici metadat). A jeste vyuzivam __autoload pro includy, abych to nemusel psat porad dokola.

A mam jeste vymakanejsi :) ve vsech souborech includuju layout.php, ktery pri vlozeni vykresli hlavicku, ale zaregistruje se pres register_shutdown_function a po ukonceni volaciho skriptu vypise paticku...pozor, nefunguje s ob_gzhandler!

Jarek:

cili stranka pak vypada:

<?
 
require_once('layout.php');

  //dalsi kod
?>

Andrew:

Ahoj Jakube,

já jsem prošel cestou opačnou. Mno, abych byl přesný, na různých webech to mám různě. A nyní přesněji.
Mám jeden web, kde je všude stejné to okolí a mění se opravdu jen to tělo. Navíc proměnné, které se používají v tom okolí, vůbec nepoužiju v těle. Tím způsobem, který zatracuješ, dokážu hezky oddělit kód, který má na starosti zobrazení stránky (menu, hlavičky atd.) od funkčního kódu jednotlivých stránek. Někde si vystačím se samotným HTML, jinde je více funkcí (třeba diskusní fórum), tak tam je kód k těmto věcem, ale nemýchá se s ničím jiným. Navíc přidání stránky či aktualizace je věc velmi přehledná a elegantní.
Co se týče nevýhod, tak když si přečtu, co všechno se musí ošetřit v té které verzi, tak to vyjde na stejno. co se týče logiky, tak z vlastní zkušenosti vím, že je jednodušší pro cizí osobu udělat jednoduchý vkládaný soubor, než zkoumat, proč tam jsou různé includy atd. (Sám mám totiž web, kam si jeho majitel chce něco doplňovat sám - moc pomalu totiž reaguju na jeho aktualizační požadavky :-) - a tak je pro něj jednodušší udělat libovolný HTML soubor, který se prostě vloží, než abych mu vysvětloval, že tam musí zkopírovat pár řádků v PHP, kterým vůbec nerozumí, ale nesmí je tam dát blbě).
Jedinný zásadní důvod (a kvůli němu to také mám na jiném webu uděláno Tvým způsobem) je ošklivé URL, které si lidi nebudou pamatovat.
Abych to uzavřel, netroufl bych si jednoznačně upředňostnit ani jeden ze způsobů, jelikož je to opravdu značně subjektivní a záleží na každém, co mu vyhovuje lépe. Rozhodně bych netvrdil, že některý způsob je správnější (ale to ty netvrdíš, pouze že je lepší).
PS: Při přečtení náhledu z RSS feedu jsem se těšil na nějaké převratné superelegantní řešení, tak jsem trochu zklamán, ale co se dádělat :-)

pif:

osobne vzdy testuji file_exists, pouzivam skoro vsude reseni index.php?soubor=nejaky.

Osobne mi to prijde lepsi (v kombinaci se sablonami), nez volat fce na volani nejakyho predobsahu.

Nicmene, todle uz je docela malo podstatny, jelikoz obe dve metody vedou ke stejnemu cili.

ikona Jakub Vrána OpenID:

file_exists() sám o sobě nestačí, protože nezabrání vložení souboru, který by vložen být nikdy neměl. Co se asi stane, když někdo zavolá index.php?soubor=index ?

Je na tom hezky vidět, že u tohoto způsobu je potřeba myslet na spoustu věcí a ošetřit je a člověk kromě hnusných URL nezíská nic, co by u mnou vyzdvihovaného způsobu nezískal zadarmo.

GrizzlyNetch:

Nebo je také jednodušší varianta. Soubory, které se inkludují stačí ověřovat pomocí file_exists, odstranit jim ".." a mít je umístěné třeba ve složce "pages/". Výsledek? Není potřeba vytvářet jiná pole, stránky jsou definované pouze existencí souboru ve složce pages...tudíž není problém přidávat další a další stránky(extrémně výhodné u Smarty)

NeNe:

Dá se to udělat taky objektově.

1) Abstraktní bázová třída Page obsahuje metody writeHeader(), writeFooter() a abstraktní metodu render()

2) Každá další stránka je třídou, která dědí od Page a implementuje metodu render(). V té zavolá writeHeader(), udělá něco vlastního a zavolá writeFooter()

3) Soubor stranka.php potom obsahuje už jen
<?php
include("SomePageClass.php")
$page = new SomePage();
$page->render();
?>

Vzhledem k tomu, že je to objektové, lze jednoduše překrýt hlavičku a patičku v případě, že je to potřeba. Nebo je lze v metodě render() zcela vynechat. Toto řešení jsem si zvolil, protože jsem se byl líný učit nějaké šablony a docela mi vyhovuje.

ikona dgx:

to je přesně ono, tento způsob má budoucnost!

Andrew:

To už vypadá zajímavějc. Zde už začínám vidět něco, co bych nazýval "elegantní řešení". Odprostí to totiž člověka od řešení 150 vyjímek, když se náhodou něco liší.
Přiznám se, že objektový přístup v PHP moc nepoužívám (ještě zvyk z dřívějších verzí) a přestal jsem vidět využití. Tohle je ale důvod k zamyšlení.

SNOP:

A jeste misto WriteHeader a Write Footer mit zabudovany nejaky template engine (idealne vlastni) a je to.

Osobne to delam tak, ze mam jeden ridici soubor, v nem jeden ridici objekt (konkretne objekt Menu), ktery rozhoduje, co kde vysvitit, kde jsem a ktery objekt bude zpracovavat vlastni stranku. Jelikoz jsou vsechny objekty vlastne dedici toho, co je tady nazvano lib.inc.php, maji automaticky zpristupnene potrebne veci.
Celou vec extremne zjednodusuje uziti PHP 5.0, metod __get,__call,__construct a fce __autoload

DaMage:

Pre mňa je prijateľnejšie prvé riešenie, kde celú logiku mam v includoch čiže kod index.php vyzerá asi takto:
<?php
//Toto načíta jadro systému
require_once "/blablabla/system.core.php";
//Toto nastaví hlavné nastavenia systému
require_once "/blablabla/system.init.php";
$system->Proceed();
echo
$system->Output; //Vypíše vystup systému
?>
a ak potrebujem urobiť lepšie URL napríklad spominane kontakty... stačí mi vytvoriť súbor kontakty.php:
<?php
//Toto načíta jadro systému
require_once "/blablabla/system.core.php";
//Toto nastaví hlavné nastavenia systému
require_once "/blablabla/system.init.php";
$system->Action="kontakty";
$system->Proceed();
echo
$system->Output; //Vypíše vystup systému
?>
A výstup bude rovnaký (aj kód a logika skriptu), ak by som použil "index.php?a=kontakty" alebo "lepšie" vyzerajúcu adresu "kontakty.php"

Andrew:

Tak se po půl roce vracím k tématu. :-) Možná, že mi někdo odpoví. Přece jen se mi trochu víc zalíbil Jakubův způsob (v objektové variaci od NeNe), ale narazil jsem na věc, kterou moc nedokážu elegantně vyřešit. Na webu bych měl desítky a stovky stránek, třeba článků jako zde, a každý by měl svůj soubor (a tedy hezkou adresu). Nicméně data bych chtěl mít v databázi, kvůli lepší manipulaci, vyhledávání atd. Možné řešení mě napadlo - všechny soubory by byly téměř totožné (includované hlavičky, patičky ...) a jen by se měnil parametr nějaké zobrazovací funkce označující id článku v databázi. Adresa hezká, všechno dobré, jen mi přijde trochu hloupé, mít stovky souborů s téměř shodným obsahem. Způsob s jedním souborem a parametrem v adrese to vyřeší mnohem snáz. Taky bych to nemusel dávat do databáze a mělo by to větší smysl, ale připravil bych se o výhody, které mi databáze poskytuje. Jaktedy z toho co nejelegantněji ven?

ikona Jakub Vrána OpenID:

Vytvářet stovky téměř shodných souborů je nesmysl. Já vytvářím jeden soubor pro každý typ stránky (úvodní stránka, detail článku, ...). Předat si nějaký parametr v URL (např. ID zobrazeného článku) není problém. Pokud chceš mít soubory na hezkých URL, tak si ke každému objektu ulož do databáze URL, na kterém má být k dispozici, a přesměrování zařiď např. pomocí mod_rewrite nebo ErrorDocument. Přes mod_rewrite např. takhle:

RewriteRule ^clanky/([-a-z0-9_]+)\.html$ clanek.php?url=$1 [L,QSA]

Andrew:

S mod_rewrite mám smůlu, nemohu ho použít a tudíž jsem hledal něco jiného, ale ErrorDocument je dobrý trik. To jsem nějak opomněl, jelikož ho používám opravdu jen pro ošetření chybové stránky. Díky moc za nakopnutí.

Quip:

Pokud je na serveru Apache a lze pouzit PathInfo, pak neni mod_rewrite v podstate potreba. Cely web (nebo jeho potrebna cast) muze byt obsluhovan jednim scriptem a parametr se bude psat za lomitko. URL nebude tak "krasna" jako s mod_rewrite, ale bude se hodne blizit.
Napriklad: page.php/nazev-clanku
V PHP je pak mozne dostat "nazev-clanku" z pole $_SERVER a to v indexu $_SERVER['PATH_INFO']. Zbytek scriptu pak bude fungovat uplne stejne, jako kdyby se nazev clanku dostaval pres mod_rewrite v prikladu od Jakuba.

Funky Květák:

Řeším systém menšího webu (cca 10-20 stránek .html)
zatím jsem si osvojil systém pomocí
include, nebo require:

<?php
$launch
= $_GET["launch"];

if (!isSet(
$launch) || empty($launch)) {
$launch = "defaultni";
}
require (
$launch.".php");
?>

...a volám změny:  <a href="?launch=strana">něco</a>

Rád bych ale pochopil zde "OBJEVENÝ" objektový  model. S PHP  začínám a s objektovým scriptováním jsem se jen lehce setkal v javascriptech. Můžete mi to prosím polopaticky vysvětlit, tak abych to mohl použít... díky moc.

ikona Jakub Vrána OpenID:

To je špatný přístup. Volej rovnou strana.php a pokud potřebuješ ve všech souborech vykonat nějaký společný kód, tak ho vlož na začátek těchto souborů <?php include "hlavicka.php"; ?>.

krteczek:

Používám zmiňovaný škaredý způsob.
Vypadá to následovně: index.php + includy nastavení a základních funkcí. Všechny obslužné stránky jsou samostatné funkce includované až v okamžiku kdy je potřebuji a návratovou hodnotou je obsah stránky v poli...
vypadá to nějak takhle:

<?php
function ukaz_clanek()
{
if(!empty(
$_GET['akce']))
  {
   switch($_GET['akce'])
    {
     case 'prihlaseni'
      require('prihlaseni.php');
      $s=prihlaseni();
     break;
     //takhle projdu vse
     default;
      $s=stranka_neni();
     break;
    }
   return $s;
  }
}
?>

a v index.php mám v podstatě html stránku, kde zavolám fci ukaz_clanek() a ta mi vrátí pole, jehož obsah postupně vypíšu...
<?php
$stranka
=ukaz_clanek();
//nějaké html
if(!empty($stranka['nazev']))
{
  echo '<title>'.$stranka['nazev'].'</title>';
}
//pokračování výpisu stránky,
if(!empty($stranka['clanek']))
{
  echo $stranka['clanek'];
}
?>
takhle nějak to vypadá.
Co je na tom šppatného? kromě adresy??  díky

viki:

Podobný problém jsem řešil a vlastně ještě řeším.
Inspiroval jsem se na weblogu http://zapisky.info/.
Josef Petrák tam nastínil řešení v objektech: http://zapisky.info/?category=php.
Já jsem to celkem s úspěchem uplatnil. Potřeboval jsem framework pro generování webu z XML zdrojů. A na freehosting. Tzn. došlo na PHP.
Vzal jsem princip Cocoonu (z Javy) a toto řešení a něco mi vyšlo. Zatím to není dokonalý.
Pokud se tam přidá to XML, rozhodně se oddělí logika, zdroje dat a vzhled.
Samozřejmě bez XML to jde taky...
Ideální by bylo propojit ještě s URL rewriterem. Ale k tomu jsem zatím nedošel.
Prostě objekty jsou objekty.

petko3000:

tststs... to je nazorov... a co toto?

$page=(isset($_GET['page'])) ? $_GET['page'] : '../index.inc';

switch ($page) {
  case 'clanky' : include_once(../clanky.inc);
   break;
  default : include_once(../index.inc);
   break;
}

index.inc uz moze obsahovat co chcete.
vsetky includovane paginy su mimo document root takze si nikto nemoze precitat source...

nadherne URL si vysieste opachovanim apacha. existuje nejaky patch alebo plugin (ask google-nepamatam sa presne-nepouzivam), ktory umoznuje reprezentovat $_GET premennu ako fyzicky adresar cize URL potom vyzera asi takto:

http://www.mediate.sk/clanky/toto_skusim.html

ikona Jakub Vrána OpenID:

Princip whitelistu je samozřejmě možný, otázka je, co z toho? Pro přidání tohoto souboru ho nestačí vytvořit, je potřeba ho doplnit ještě do whitelistu.

Honza Odvárko:

Skvělou věcí jsou direktivy auto_prepend_file a auto_append_file. Tyto, nastaveny v .htaccess, v kombinaci s chytáním výstupu přes ob_* funkce, umožňují dělat geniálně jednoduché prezentační mini-weby. Tady je na takový web kuchařka:

- ve skriptu připojeném skrz auto_prepend_file zavoláme pouze ob_start()
- ve skriptu připojeném skrz auto_append_file chytíme veškerý výstup: $body = ob_get_clean() (je tam tedy výstup aktuální stránky, třeba o_nas.php) a dále v $body regulárním výrazem najdeme první nadpis <h1>***</h1>. Ten uložíme třeba do $title.
Nakonec v onom auto_appendovaném skriptu vypíšeme celý HTML dokument, s dosazeným $body (a $title z něho získaným).

Nevýhodou jest, že direktivy auto_prepend_* jsou nastaveny i pro podadresáře, takže kde nechceme automaticky připojit kostru dokumentu, je třeba direktivy extra zrušit.

Pointa: takhle není nutné na začátku a na konci provádět include

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.