PHP file_get_contents en UTF-8

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik denk dat het inmiddels enkele jaren geleden is dat ik hier voor het laatst een topic geopend heb, maar ben nu al uren bezig met een probleem dat volgens mij heel simpel moet zijn.

Ik heb een script dat data laad van een externe bron:

PHP:
1
2
3
4
5
6
7
8
9
<?php

$content = file_get_contents("http://intra.local/json/?view=1");

print_r(json_decode($content,true));

echo "\nTösti";

?>


Vervolgens komt het probleem, de data die word opgehaald met file_get_contents laad bijv. Pelgröm zien ipv Pelgröm.
Maar de Tösti is wel zichtbaar als Tösti.

Het probleem zit dus niet in mijn test-weergave (UTF-8 headers) maar in de file_get_contents. Wie weet mij hiermee op weg te helpen?

Acties:
  • 0 Henk 'm!

  • Firesphere
  • Registratie: September 2010
  • Laatst online: 11-09 05:38

Firesphere

Yoshis before Hoshis

Gebruik cURL.

I'm not a complete idiot. Some parts are missing.
.Gertjan.: Ik ben een zelfstandige alcoholist, dus ik bepaal zelf wel wanneer ik aan het bier ga!


Acties:
  • 0 Henk 'm!

  • Osiris
  • Registratie: Januari 2000
  • Niet online
file_get_contents() laadt simpelweg alle bytes in een string en doet niets qua characters of charsets e.d.

Ik zou gokken dat de bron al foute bytes aanlevert. Kwestie van eventjes downloaden met 'wget' en in een hexeditor checken wat precies de bytes zijn: geldige UTF-8-chars of niet.

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Daar ga ik ook vanuit, de bron heb ik helaas geen controle over, maar hoe is dit dan om te zetten? :)

Acties:
  • 0 Henk 'm!

Verwijderd

Om te zetten? Wat wil je precies?

Je zou het aan de PHP kant kunnen converteren. Dan moet je eerst weten wat de source charset is. Evt. kan je functies gebruiken die dit voor je bepalen (gokken). Door middel van een google-actie moet je hier wel uitkomen lijkt me.

Acties:
  • 0 Henk 'm!

  • Cor453
  • Registratie: Mei 2011
  • Laatst online: 17-08 22:55
Al heb je geen controle over de bron, maar ben je wel zeker van de encoding, dan kun je natuurlijk converteren met iconv().

En anders is er mb_detect_encoding() (niet direct aan te raden) om de charset te raden.

De PHP documentatie heeft hier mooie artikeltjes over.

Acties:
  • 0 Henk 'm!

  • ValHallASW
  • Registratie: Februari 2003
  • Niet online
Cor453 schreef op maandag 24 november 2014 @ 23:16:
Al heb je geen controle over de bron, maar ben je wel zeker van de encoding, dan kun je natuurlijk converteren met iconv().
Dit is het enige juiste antwoord. Je bron levert bytes aan: tekst in een bepaalde encoding. Op basis van je verhaal is dat UTF-8 -- dat is trouwens ook de enige encoding waar json_decode correct op werkt!

Jouw bestand bevat ook bytes. Wederom: tekst in een bepaalde encoding. In jouw geval waarschijnlijk windows-1252 (ook wel 'ansi' genoemd als je een west-europese windows gebruikt)

Vervolgens plak je die bytes bij elkaar, en stuur je ze naar een browser. Die interpreteert ze weer. Als wat? Getuige je output als windows-1252.

Je probleem is nu alleen dat je teksten met twee verschillende encodings bij elkaar gooit. Dat werkt niet! Je combineert namelijk

ö in utf-8 = 0xc3 0xb6
met
ö in latin-1 = 0xf6

en vervolgens laat je de browser het tonen alsof het allemaal latin-1 is. Die maakt vervolgens van

0xc3 0xb6 0xf6 = öö in latin-1 (looks familiar?)


Wat moet je nu doen? Zorgen dat alles in één encoding staat. Dat kán latin-1 zijn, maar utf-8 is een logischere keuze. Daarvoor moet je drie dingen doen:

1) zorgen dat je je php-bestand óók als utf-8 opslaat. Dan wordt de ö in je 'tösti' namelijk niet als 0xf6 maar als 0xc3 0xb6 opgeslagen, en
2) als je eventuele andere bronnen hebt die géén utf-8 gebruiken maar een andere encoding: die converteren met behulp van iconv(), en
3) tegen je browser vertellen dat je karakter-encoding utf-8 is. Dat doe je door een content-type header mee te sturen, met charset=utf-8.

Acties:
  • 0 Henk 'm!

  • Cor453
  • Registratie: Mei 2011
  • Laatst online: 17-08 22:55
Laten we dan dit topic ook gelijk misbruiken om te zeggen dat "verkeerde bytes" niet bestaan. Alles staat of valt met de manier waarop jij met een rijtje bytes omgaat. Wees ook consequent met je character encoding, want er gaat meer stuk dan je lief is als je dat niet doet.

iconv() is the way forward, en wees voor jezelf ook helder over wat je nu precies gebruikt in je code, en hoe je het gebruikt.

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Bovenstaand voorbeeld was een uitgeklede versie van mijn 'probleem'.
Ik heb het inmiddels opgelost.

Het probleem zat in de opslag in de database, de data die uit json_decode kwam werd uiteindelijk opgeslagein in een mysql database. Database staat op utf8_general_ci, en de velden ook.
Oplossing zat in de connectie met de database, in de PDO connectie toegevoegd:
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")

Acties:
  • 0 Henk 'm!

  • Osiris
  • Registratie: Januari 2000
  • Niet online
Handig, een topic openen met essentiele gegevens die missen :D Chapeau.

Acties:
  • 0 Henk 'm!

Verwijderd

Deze snap ik even niet? :P

Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
file_get_contents is in de basis vrij simpel en doet vrij weinig aan http-headers interpreteren etc waardoor je eigenlijk simpel in je string een default (php) charset representatie krijgt van de binnengekomen bytes. Waardoor je dus eigenlijk geen idee hebt wat er in je string staat enkel dat het er niet uitziet zonder iconv.

Curl is geavanceerder en die interpreteert wel http-headers etc waardoor die in theorie wel kan ontdekken dat het utf-8 is en bijv automatisch een vertaalslag kan maken naar je default (php) charset waardoor je weet dat je string in de goede charset staat en niet meer geconverteerd hoeft te worden, of je weet in welke charset je string staat zodat je het gericht kan converteren.

Als ik nu 3 bronnen heb (met correcte http-headers etc) :
Bron A : ISO-8859-1
Bron B : UTF-8
Bron C : UTF-16
dan levert file_get_contents me 3 strings op van verschillende lengtes waarvan ik niet weet wat waarin staat, dit zal ik echt per bron bij moeten houden en converteren.
Curl zou het moeten kunnen bijhouden / automatisch converteren.

Of anders gezegd : Met file_get_contents krijg je gewoon een setje bytes binnen en moet je zelf maar weten welke charset het is.
Met Curl kan je uit de header-info de charset halen waarin het encoded was.

Bijv bij een website die ik ken hebben ze 30 webservices, x jaar terug zijn ze overgestapt van iso-8859-15 naar utf-8. Wat daar dus het resultaat is is dat er zeg maar 5 legacy webservices zijn die (ook al is het intern utf-8) output geven in iso-8859-15. Dat die dat doen valt alleen op te maken uit documentatie (maar wie leest dat nou) en http-headers.
Ga ik dan naar de support-vragen kijken daar dan zie je echt een heel duidelijke scheiding, een aantal programmeurs gebruikt instructies als file_get_contents en krijgt rare meldingen bij die 5 webservices en een aantal programmeurs die gebruikt instructies als curl en merkt nog niet eens dat die 5 webservices iets anders retourneren omdat het gewoon afgehandeld wordt.

Acties:
  • 0 Henk 'm!

  • NMe
  • Registratie: Februari 2004
  • Laatst online: 09-09 13:58

NMe

Quia Ego Sic Dico.

Verwijderd schreef op dinsdag 25 november 2014 @ 00:15:
Oplossing zat in de connectie met de database, in de PDO connectie toegevoegd:
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
Ik weet toevallig dat met MySQLi het gebruik van SET NAMES niet handig is omdat je daarmee de variabelen die je in je prepared statements stopt niet mee-encodet. Ik weet niet of dat voor PDO ook relevant is en of het op een of andere manier wordt afgevangen, maar misschien wil je daar even in duiken. ;)

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


Acties:
  • 0 Henk 'm!

  • Firesphere
  • Registratie: September 2010
  • Laatst online: 11-09 05:38

Firesphere

Yoshis before Hoshis

wat Gomez zegt dus ;)
cURL kan meer, waardoor de fout afgevangen kan worden.

Tenzij de fout aan de vragende kant ligt, ik begrijp dat't in de db-storage zat. Dan heeft cURL geen toegevoegde waarde inderdaad ;)

I'm not a complete idiot. Some parts are missing.
.Gertjan.: Ik ben een zelfstandige alcoholist, dus ik bepaal zelf wel wanneer ik aan het bier ga!


Acties:
  • 0 Henk 'm!

  • Robbiedobbie
  • Registratie: Augustus 2009
  • Laatst online: 07:32
NMe schreef op woensdag 26 november 2014 @ 00:18:
[...]

Ik weet toevallig dat met MySQLi het gebruik van SET NAMES niet handig is omdat je daarmee de variabelen die je in je prepared statements stopt niet mee-encodet. Ik weet niet of dat voor PDO ook relevant is en of het op een of andere manier wordt afgevangen, maar misschien wil je daar even in duiken. ;)
Ik vermoed dat het bij PDO gewoon goed zal gaan zolang je niet expliciet de prepared statement emulatie uitzet.

(Default worden deze statements geëmuleerd, door alle params zelf te escapen, in de query te plaatsen, en dan naar de server door te zenden. Door een bepaalde flag om te knallen, gaat pdo daadwerkelijk bij prepared statements alle data native naar de server door knallen)

Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Firesphere schreef op woensdag 26 november 2014 @ 00:22:
[...]
wat Gomez zegt dus ;)
cURL kan meer, waardoor de fout afgevangen kan worden.

Tenzij de fout aan de vragende kant ligt, ik begrijp dat't in de db-storage zat. Dan heeft cURL geen toegevoegde waarde inderdaad ;)
Zoals ik het begrijp zit het in een stukje db_storage die simpelweg nergens voorkomt in de vraag.

Zoals ik het begrijp doet de functie printrijjsondecode iets van opslaan naar db, dan de resultaten terughalen uit db en dan die resultaten printen. Geen idee waarom er een complete roundtrip naar de db gebeurt (dat kan handiger als je de json al hebt, dan hoef je het enkel op te slaan en niet terug te halen want je hebt het al). Maar anders zie ik niet waar het fout kan gaan.

En in principe heeft met deze werkwijze cURL nog steeds een meerwaarde, want in je richting van je database moet je opgeven in welke charset je data gaat versturen, dan moet je wel eerst weten in welke charset je de data hebt om te weten of je er een gerichte iconv overheen moet gooien of niet. En cURL kan je die info vertellen terwijl file_get_contents dit niet doet.

In wezen zou je er een generiek iets (save webresult to db) van willen maken dan zou je iets als het volgende moeten hebben :
- Stel vast waarin je db het opslaat (eenmalig vastleggen over je hele project)
- Haal de webresult op en ga na wat de encoding van het webresult is (hier heb je dus cURL voor nodig)
- Als de webresult encoding gelijk is aan db encoding dan rechtstreeks doorblazen
- Als de webresult encoding anders is aan db encoding dan iconv en varianten gebruiken om gericht om te zetten en dan dat resultaat doorblazen

Zelf zou ik hem nog 1 stapje hoger zetten : Alle input die php ingaat moet gechecked worden en indien nodig met iconv omgezet worden naar utf-8 (of welke interne php-encoding je ook hanteert). En voor web kan je checken met cURL, voor files kan je checken op bijv een BOM (als die er is).
Dan weet je op elk moment in je script welke encoding je hebt en kan alles volgens hetzelfde protocol naar de database gaan. Alleen betekent het dat je een stukje framework moet schrijven wat file_get_contents overbodig maakt en file_get_contents nergens meer gebruiken.

Als je namelijk in de huidige opzet voor bijv een testje / een fix even de url op regel 3 vervangt door een lokale json file die toevallig iso-8859-2 opgeslagen is dan kan dat weer wat rariteiten opleveren bij enkele characters want er wordt nu blind vanuit gegaan dat het utf-8 is, of als de developer ipv utf-8 utf-16 op de json gaat zetten (twijfelachtig of het volgens de specs mag, maar technisch wel mogelijk) dan ga je ook weer rariteiten krijgen.

Valideer simpelweg altijd, maar dan ook echt altijd je input. Dat soort dingen heb ik door de jaren heen op de harde manier geleerd (denk aan een meerjaren rapportage maken en in verschillende jaren op verschillende plekken "rare" data zien (group by's die niet goed gingen, totalen die daardoor niet goed gingen etc etc etc) en dan gewoon eerst je nieuwe rapportage code gaan debuggen (die is toch nieuw en de data is oud) dan de database gaan bekijken en na x uur erachter komen dat de encodings niet overal kloppen, maar in 99,99% van de gevallen wel. In 1e instantie vluchtig de code bekijken en die gaat gewoon goed.
Dan maar eens de release notes afgaan of er bijv updates / rariteiten rond die periodes zijn geweest en er misschien daar iets fout bij is gegaan, nope.
Dan maar eens in je geheugen gaan graven of er iets speciaal was in die tijd (want ergens zit er een structurele fout), niet kunnen verzinnen dus maar even aan de invoerders van de data vragen of die weten of er iets speciaals was in die tijd, lege blikken terugkrijgen.
Dan nog wat werk erin steken maar uiteindelijk maar de data voor nu fixen en de case open laten staan.
Dan een half jaar van de verkoopleider te horen krijgen dat het weer spontaan optreed, dan na wederom lang uitzoeken / navragen te horen krijgen dat de boekhouder periodiek correcties kan uitvoeren via een niet 100% reguliere weg (management script / correctie script / etc) en dat hij dat ongeveer 1x per jaar doet.
Dat script erbij zoeken en kijken, ah daar mist de validatie in omdat sysbeheer het verstand heeft om te weten hoe ze data moeten opslaan voor import (en omdat het wel eens makkelijk kan zijn voor correcties etc)
Dan maar eens de procedure erbij pakken die de boekhouder gehad heeft, daar staat het keurig in dat het utf-8 moet zijn. Navragen bij boekhouder, te horen krijgen dat hij het van zijn voorganger heeft gekregen en dat hij eigenlijk nooit wist wat utf-8 betekende, maar voor zover hij kon zien ging het altijd goed dus vroeg hij het maar niet meer) En dan praat je dus over 1 niet 100% nagevolgde procedure en iets van 100 uur werk verspreid over een heleboel tijd om dat ding te vinden.
En dat is daarna (want we hadden interne urenverekening) weer een hele bitch-fight geworden over wie nou die 100 uur werk die toch geleverd was op zijn rekening zou nemen :)

Daar zijn een aantal lessen uit voortgekomen, maar 1 daarvan is : Altijd, altijd je input checken.
Pagina: 1