Načítání souborů

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

Viděl jsem už spoustu způsobů načítání souborů.

<?php
// špatný způsob
$fp = fopen($filename, "r");
$file = fread($fp, filesize($filename));
fclose($fp);

// neefektivní způsob
$file = implode("", file($filename));

// správný způsob
$file = file_get_contents($filename);
?>

Funkce file_get_contents byla přidána sice až v PHP 4.3.0 (prosinec 2002), ale kdo by už kvůli bezpečnostním dírám používal starší verze, že. První způsob je špatně proto, že druhý parametr funkce fread znamená počet bytů, které se mohou maximálně načíst (a ne počet bytů, které se bezpodmínečně musí načíst), a navíc funkci filesize nelze použít u vzdálených souborů (dostupných např. protokolem HTTP).

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

Diskuse

Dax:

Nechtelo by to vice rozepsat a uvest lepsi priklad?

ikona dgx:

no Jakube, s tím počtem bytů, které se mohou maximálně načíst jsi trošku přestřelil, ne? :-)

Samozřejmě jde o počet bytů, které se načtou, pokud náhodou soubor není menší.

ikona Jakub Vrána OpenID:

Je to opravdu ještě rozšířenější omyl než jsem čekal, když si to myslíš i ty. V dokumentaci je přitom jasně napsáno "reads up to $length bytes".

U lokálních souborů se obvykle načte celá délka, ale funkce to nezaručuje, což se projeví např. u vzdálených souborů:
<?php
$filename
= "http://www.vrana.cz/";
$fp = fopen($filename, "rb");
echo
strlen(fread($fp, 10000)) . "\n"; // 1460
echo strlen(file_get_contents($filename)) . "\n"; // 2818
?>

ikona dgx:

mrkni ba dokumentaci: fread() reads up to length bytes from the file ... Reading stops when length bytes have been read, EOF (end of file) is reached, or (for network streams) when a packet becomes available, whichever comes first.

Tedy u lokálních souborů se přečtě vše, až do EOF. Výjimka jsou network streams, ale to je trošku mimo téma "Načítání souborů"

ikona Jakub Vrána OpenID:

Nikde netvrdím, že pro lokální soubory to nemůže fungovat. Ale obecně se na to spolehnout nedá. Když si na to člověk zvykne, tak se pak může divit, proč mu nefunguje takovýto kód:

<?php
$fp
= fsockopen("www.vrana.cz", 80);
// poslání a přečtení hlaviček, mimo jiné i Content-Length
$data = fread($fp, $length);
?>

Několikrát jsem se s tím setkal, jednou jsem se do této pasti i sám lapil. Proto je lepší vědět, že druhý parametr funkce fread nezaručuje přečtení daného počtu bajtů, na což jsem chtěl mimo jiné upozornit.

ikona Jakub Vrána OpenID:

Netýká se to jen network streams. Viz http://bugs.php.net/bug.php?id=30936 a http://bugs.php.net/bug.php?id=35859.

Mordae:

Taky načítání touhle metodou je na dvě věci, moje oblíbené je:
<?php
    $fp
= fopen($filename, "r");
    $file = '';
    while(!feof($fp)){
        $file .= fread($fp, 1024);
    }
    fclose($fp);
?>
A na soubory pod 10kB se vyplatí i implode('', file($filename)). O ty milisekundy strach nemám.

A co se týká file_get_contents(), což je opravdu správný způsob, php > 4.3.0 se nemusí vyskytovat všude. Natož doma v testovací instalaci. Nevím jak kdy, ale já updatuju méně často než na mě YaST huláká že už to potřebuji...

A protože by toto mělo být konstruktivní, doporučuji toto:
<?php
   
if(!function_exists('file_get_contents')){
        function file_get_contents($filename, $use_include_path = 0){
        $fp = fopen($filename, "r", $use_include_path);
        $file = '';
        while(!feof($fp)){
            $file .= fread($fp, 1024);
        }
        fclose($fp);
        return $file;
    }
}
?>
Na maxlength atd. se můžete vykašlat - stejně jsou jen v php 5.1. Také by bylo možné vylepšit přeposílání chyb.

Také doporučuji místo url wraperů používat fsockopen a oříznout ručně hlavičky (první výskyt \n\n nebo \r\n\r\n). Funguje to i když někdo fopen _wrappers vypne.

---
Za případné chyby se předem omlouvám, netestoval jsem konkrétně tyto případy - píšu ze školy.
mordae@mordae.net

ikona Jakub Vrána OpenID:

Pouhé oříznutí hlaviček obecně vzato bohužel nestačí kvůli různým Transfer-Encoding, hlavně chunked, které posílá např. samo PHP. Viz http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41.

Mordae:

Pravda. Co ale poslat do requestu HTTP/1.0? To mi zajistí poměrně slušnou čitelnost. Zbytek se dá dochytat.

ikona Jakub Vrána OpenID:

To je možné pokud vím pouze u serverů, které nepoužívají Virtual Hosty (protože jinak se musí poslat hlavička Host, která tuším patří až do HTTP/1.1).

ikona spaze:

Taky mam pocit, ze url-wrappers jsou doporucovany (abstraktni) zpusob. Kdyz nekdo vypne url-wrappers, nejspis vypne i fsockopen, curl apod.

A k tomu if (!function_exists..), asi bych spis pouzil http://pear.php.net/PHP_Compat, implementace je sice stejna, ale uz je to hotovy ;)

ikona Jakub Vrána OpenID:

URL wrappers může webmaster vypnout jako ochranu před ignoranty, kteří bez jakéhokoliv ověření používají kód <?php include $_GET["file"]; ?>. Když totiž někdo do parametru file předá plné URL, tak se kód na tomto URL provede v kontextu prováděného skriptu. Už na to téma mám něco napsané.

Zmiňuji to proto, že vypnutí URL wrappers nemusí nutně znamenat také vypnutí fsockopen a podobných.

ikona spaze:

Dneska (od 5.2.0) na to mame samostatnou direktivu:

; Whether to allow include/require to open URLs (like http:// or ftp://) as files.
allow_url_include = Off

Mordae:

Pravda. Byl to ale jen nástin, stejně tuhle funkci napsalo určite už spoustu lidí...

ikona llook:

A nebo rovnou použít PHP Compat knihovnu - http://aidan.dotgeek.org/wiki/index.php?area=…=Installation
Ono těch funkcí v PHP < 4.3 chybí víc.

Mike:

Neni to prilis k tematu, ale mam otazku.

Mam soubor napriklad pokus.htm, ma hlavicku, obsahuje tagy <body>, <html> atd.

Potrebuji skript, ktery otevre zadany html soubor, nahradi vsechen text, ktery se nenachazi v tazich, urcitym retezcem. Tzn, ze skript napriklad nenahradi "<meta http-equiv>" za "<nahrazovaci_retezec>", ale treba toto uz nahradi:

<body>
Nejaky text v souboru pokus.htm
</body>

Nahradi za:

<body>
Nahrazovaci_retez
</body>

V regulernich vyrazech nejsem vubec zbehly, takze se na to ptam.

ikona Jakub Vrána OpenID:

Polož dotaz třeba na http://diskuse.jakpsatweb.cz/index.php?act…&forum=9, tohle na to není nejvhodnější místo.

humanoidvani:

funguje krasne diky .... :-)

Samuraj:

thx :)

Michal:

Hmmm....tak som vyskusal nacitat textovy subor funkciou file_get_contents(); ale je to omnoho pomalejsie ako ked "klasicky" citam riadok po riadku fnc FgetS(); a kontrolujem koniec suboru metodou napr. while (!Feof($fp));
Nechapem to.....

Tom@$:

potrebuju postupne nacist soubor (vypisuju nekolika MB) ale moc sem nepochopil co mam dat za "resource context"

vychazim z (uvedene na php.net)
string file_get_contents ( string filename [, bool use_include_path [, resource context [, int offset [, int maxlen]]]] )

takze abych mohl pouzit "offset" a "maxlen" k postupnemu nacitani souboru, musim uvezt i "resource context". co tam dat???

diky moc

ikona Jakub Vrána OpenID:

Vždyť je to napsáno jen o kousíček dál: "context parameter can be skipped by NULL."

Tom@$:

heh, tak sem prave zistil, ze i kdyz je CZ verze psana EN, neni tam to sami jako v EN verzy :)
takze dik ;)

Jakub Kříž:

Používám tohle pro plnou kontrolu. Nektere servery me s file_get_contents nepustily.

<?php
function file_get_sock($url,$headers=false) {
    $url = parse_url($url);

    if (!isset($url['port'])) {
      if ($url['scheme'] == 'http') { $url['port']=80; }
      elseif ($url['scheme'] == 'https') { $url['port']=443; }
    }
    $url['query']=isset($url['query'])?$url['query']:'';

    $url['protocol']=$url['scheme'].'://';
    $eol="\r\n";

    $headers =  "GET ".$url['protocol'].$url['host'].$url['path']." HTTP/1.0".$eol.
                "Host: ".$url['host'].$eol.
                "Referer: ".$url['protocol'].$url['host'].$url['path'].$eol.
                "Content-Type: application/x-www-form-urlencoded".$eol.
                "Content-Length: ".strlen($url['query']).$eol.
                $eol.$url['query'];
    $fp = fsockopen($url['host'], $url['port'], $errno, $errstr, 30);
    if($fp) {
      fputs($fp, $headers);
      $result = '';
      while(!feof($fp)) { $result .= fgets($fp, 128); }
      fclose($fp);
      if (!$headers) {

        $pattern="/^.*\r\n\r\n/s";
        $result=preg_replace($pattern,'',$result);
      }
      return $result;
    }
}
?>

ikona Jakub Vrána OpenID:

Proč se query string posílá po hlavičkách a ne přímo v GET požadavku?

ikona Jakub Vrána OpenID:

Hlavička Host je k dispozici až v HTTP/1.1, správně bys tedy měl komunikovat tímto protokolem. Pak se ovšem musíš vypořádat také např. s hlavičkou Transfer-Encoding: chunked.

petr:

Jak je to s legálností?Využití cizího serveru pro vlastní potřeby?Žádný obsah nenačtu do svého webu jen tím vykonám formulář na jiném serveru, je to v pořádku?

JusepeG:

Dobrý den,

snažím se pomocí kódu:

header("Cache-Control:no-cache");
$url= "http://www.bezvakup.cz/rss/seznam.xml";
$xml= file_get_contents($url);
if(!empty($xml))echo($xml);

z jednoho serveru načíst soubor z jiného serveru.
Před měsícem mi to fungovalo, bohužel nyní mi to píše:

Warning: file_get_contents(http://www.bezvakup.cz/rss/seznam.xml) [function.file-get-contents]: failed to open stream: Při systémovém volání, při kterém nikdy nemělo dojít k chybě, došlo k chybě. in C:\wamp\www\reklama\reklama.php on line 4

Nevím si rady, kde může být problém?

Děkuji
       JusepeG

piiiiiiiiiip:

Ahoj nan soubor neco.php?vypis=clanky a chci aby se mi obsach zobrazil v souboru neco.php.Jak na to?

Dominik David:

Zdravím, mám problém částečně navazující na příspěvek.
Potřebuji pomocí PHP načíst a zpracovat tento TXT soubor s daty:
http://www.ceps.cz/_layouts/Ceps/_Pages/GraphData.…&ver=RT&

Bohužel při použití file_get_contents se dostanu na HTML stránku s errorem:

Error

Object reference not set to an instance of an object.   at Ceps.Moss.Web.Controls.Moduls.PreventOfficeDiscoveryModule.context_PostAuthenticateRequest(Object sender, EventArgs e)
   at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Díky za pomoc.

Jonez1:

Zdravim,
problem je ten, ze stranka ocekava nejake hlavicky pozadavku, ktere ji URL wrapper nejspise neposkytuje - odchazi jen velice omezeny pocet.

Lze to obejit tim, ze funkci file_get_contents hlavicky vnutis nasledujicim zpusobem
(
viz PHP - manual k funkci
http://www.php.net/manual/en/function.file-…#example-2303
)
<?php
$opts
= array(
  'http'=>array(
    'method'=>"GET",
    'header'=>"Accept-language: cs\r\n" .
              "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; cs; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14\r\n"
  )
);
$context = stream_context_create($opts);
$url= "http://www.ceps.cz/_layouts/Ceps/_Pages/GraphData.…&ver=RT&";
// otevri soubor s pouzitim hlavicek
$fc= file_get_contents($url, false, $context);

// kontrola hlavicek odezvy + vystupu
echo "<pre>". print_r($http_response_header, 1). "</pre>";
echo
"<pre>". print_r($fc, 1). "</pre>";

?>

Michal:

Zajímalo by mě, zda je možné po načtení souboru pomocí file_get_contents výsledný řetězec dále zpracovat. Potřeboval bych jednotlivé řádky do pole. Bohužel, explode mi problém nevyřeší (si myslím).

Michal:

Vyřešeno.

Pavel Beran:

taky si mohl napsat jak, to miluju takovýdle debily

Wautip:

A ono je to přitom někdy tak jednoduchý

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.