0

I am likely doing something silly and thought this would be pretty straightforward. I am pulling data from an external API and storing it right into the database via Eloquent model's I am creating/saving.

The data saved into the database field looks like this: I Can't Believe It's A - The field is using utf8mb4_unicode_ci collation.

My webpage has the following meta data:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

And I am displaying it via my Laravel blade template like so:

<td>{{ $company->type->name }}</td>

I am a bit confused what I am doing wrong here? From the documentation and other stackoverflow questions I appear to be doing it correctly. My config/database.php has the following:

        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => 'InnoDB',
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

I believe this is all correct. Is there something I am missing here? Switching to use the unescaped {!! !!} seems to result in the content still being displayed the same. I am using Laravel v7.13.0

Also Google Chrome reports it is UTF-8 rendered:

> document.characterSet
"UTF-8"

Edit:

Here is an example of getting the response and what the returned JSON name looks like:

>>> $response = Http::withOptions(
            [
                'verify' => false,
                'base_uri' => config('custom.torn_api_base'),
                'timeout' => 5.0
            ]
        )->get(
            "company/79831",
            [
                'selections' => 'profile',
                'key' => config('custom.torn_api_key')
            ]
        );
=> Illuminate\Http\Client\Response {#3393
     +"cookies": GuzzleHttp\Cookie\CookieJar {#3379},
     +"transferStats": GuzzleHttp\TransferStats {#3418},
   }

>>> $response->json()['company']['name'];
=> " Button Mashers™"

^ You can see above their API is giving me the same string with the UTF-8 encoding that would be used on their website.

Here is me creating and saving the model to the database:

            $company = Company::updateOrCreate(
                [
                    'id' => $tornPlayerData['job']['company_id']
                ],
                [
                    'name' => $tornPlayerData['job']['company_name'],
                    'player_id' => $this->player->id,
                    'isOwner' => true
                ]
            );

I also am logging the response and here is what one of the lines with an Emoji looks like:

Log::info("Updating company '{$company->name}'' now", ['company' => $company]);

laravel.log│[2020-06-22 00:43:52] staging.INFO: Updating company '&#128126; Button Mashers&#8482;'' now {"company":{"App\\Company":{"id":79831,"name":"&#128126; Button Mashers&#8482;","player_id":2141091,"updated_at":"2020-06-22T04:43:52.000000Z","created_at":"2020-06-22T04:43:52.000000Z"}}}

Edit 2: I just manually copied and pasted the Tinker output of Button Mashers™ to name in the associated database row. Now the website displays that manually adjusted one properly. So it seems Laravel is doing something weird to the data from the API when it is storing it and I am unsure why that would be.

Edit 3:

Using the HEX query as the user asked in the answer.

SELECT id,name,HEX(name) FROM `companies` WHERE id=60335

(60335, 'Pokey&#039;s Play House!', '506F6B657926233033393B7320506C617920486F75736521');

(60335, '&#039;', '26233033393B');

The second result is with me modifying the Pokey&#039;s Play House! to just &#039; so you can see the result of just the apostrophe.

Edit 4:

The blade template:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Company Directory</title>

        <!-- Fonts -->
{{--        <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">--}}

        <!-- Styles -->
          ** Snipped some minor CSS away from here **
        </style>
        <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.css">

        <!-- Scripts -->

        <script type="text/javascript" charset="utf8" src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
        <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.js"></script>
        <script type="text/javascript" charset="utf8">
            **removed some datatables code from here for this paste**
        </script>

    </head>
    <body>
        <table id='directory-table' class='display'>
            <thead>
                <tr>
                    <th>Player Name</th>
                    <th>Company Name</th>
                    <th>Type</th>
                    <th>Rank</th>
                    <th>Positions</th>
                </tr>
            </thead>
            <tbody>
                @forelse ($companies as $company)
                    <tr>
                        <td>
                            <a href='https://www.example.
net/profiles.php?XID={{ $company->player->id }}'>
                                {{ $company->player->name }}
                            </a>
                        </td>
                        <td>
                            <a href='https://www.example.net/joblist.php#/p=corpinfo&userID={{ $company->id }}'>
                                {{ $company->name }}
                            </a>
                        </td>
                        <td>{{ $company->type->name }}</td> //THIS IS THE PROBLEM STRING HERE BEING OUTPUTTED
                        <td> {{ $company->rank }}</td>
                        <td> {{ $company->hired_employees }}/{{ $company->max_employees }}</td>
                    </tr>
                @empty
                    <tr>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                    </tr>
                @endforelse
            </tbody>
        </table>
    </body>
</html>
ComputerLocus
  • 3,448
  • 10
  • 47
  • 96
  • 1
    1. How does the data look when you display it directly from the API? (Meaning, before you insert it to your database). 2. How do you save it to the database, can you show that code, in full? – Qirel Jun 22 '20 at 05:40
  • @Qirel I have added extra information that hopefully helps. The data is saved just like you would when creating/updating a normal Eloquent ORM record. I take it straight from the JSON response and don't manipulate it or anything. It is put right into the ORM and saved. – ComputerLocus Jun 22 '20 at 16:27
  • @Qirel Just to note I previously was using no framework and generating a static HTML page using this external API data and saving it directly into an HTML page it displays fine. So it seems like the problem lies with the database/Laravel side as those are the things that have changed in the equation. – ComputerLocus Jun 22 '20 at 17:19
  • It’s rather unclear what the actual problem is! Are you saying it appears as “Can't” on your website. I.e. exactly as stored in the database? – deceze Jun 24 '20 at 06:20
  • @deceze Yes that is the issue exactly. Using Laravel to save and display the data (via blade template) I assumed there is something weird I am missing on the framework end causing this issue. – ComputerLocus Jun 25 '20 at 16:02
  • Not really, it works exactly as it should if it’s outputting the text exactly as it is in the database. If you have text with HTML entities, you need to decode those at some point. – deceze Jun 25 '20 at 17:44
  • Yeah so that is my issue I am having though. Why is the data that is going from the API being stored like that? If I directly call the API via Guzzle and save that data straight to an HTML page without involving a database it properly displays the emoji. Why is it being changed when saved to the database and how do I resolve that so it saves the emoji/apostrophes correctly and then displays then correctly. @deceze – ComputerLocus Jun 26 '20 at 23:10
  • When you echo an HTML entity into HTML and then look at it in a browser, you see the character the entity represents. The text changes. When you output something via Laravel, it takes care to encode text so it is output exactly as is. Why your API is returning HTML entities, who knows, it’s certainly not good practice. Maybe read http://kunststube.net/escapism. – deceze Jun 27 '20 at 05:49
  • @deceze Ah okay so you are saying I should decode the data from the API prior to storing it into the DB then. I will give that a shot. – ComputerLocus Jun 27 '20 at 18:02
  • @deceze Doing either `htmlentities()` or `html_entity_decode()` on the external API data before saving does not result in the data being displayed/stored correctly still. – ComputerLocus Jun 28 '20 at 01:45
  • Could you share the way you're printing the data in blade please? Also, could you print the data first (before saving)? Maybe a dd() after you get response from the API? – Qumber Jun 29 '20 at 05:32
  • @Qumber Check the first edit I made earlier. That shows me making the model and getting the API response. It also shows outputting the API's values. Specifically look at the part where I output `$response->json()['company']['name'];`. This is outputting exactly what the API is getting for the "name". The name is where these UTF-8 characters are coming from. Part of how I output is already stated in the OP but I will add a more full blade file to a new edit :) – ComputerLocus Jun 29 '20 at 22:12
  • 1
    Thanks. :) In your blade, could you try printing using the following methods - `{!! html_entity_decode($company->name) !!}` & `name; ?>`. Do these make any difference? – Qumber Jun 30 '20 at 05:08
  • @Qumber Oddly enough both of those work and display correctly! I assumed a normal `{{ }}` would do the similar thing to html_entity_decode. Weird stuff. Is this the solution then or is there something else I can do to handle this problem? – ComputerLocus Jun 30 '20 at 16:11
  • This is your solution. Blade `{{ }}` statements are automatically sent through PHP's `htmlspecialchars` function to prevent XSS attacks. I don't think they have anything to do with `html_entity_decode`. The data was being escaped thanks to the statement. Official docs on this: https://laravel.com/docs/7.x/blade#displaying-data – Qumber Jun 30 '20 at 16:32
  • 1
    @Qumber interesting, feel free to make this an answer – ComputerLocus Jun 30 '20 at 16:34
  • Sure, happily. :) – Qumber Jun 30 '20 at 16:47

3 Answers3

2

In your blade, try printing using the following statements:

{!! html_entity_decode($company->name) !!}

OR

<?php echo $company->name; ?>

Official docs on printing data in blade.

Qumber
  • 13,130
  • 4
  • 18
  • 33
0

"HTML entities" (such as &#039;) are useful for web pages, but should not be how you store things into a database.

It may be that you converted to entities twice, once before storing the data, once while displaying on a web page. To further diagnose the issue, get SELECT HEX(..) ... of the string.

Rick James
  • 135,179
  • 13
  • 127
  • 222
  • I am simply taking the data from the external API and saving it to the database using the Laravel Eloquent ORM. I am not doing anything extra to it so if it's being converted twice it would be the framework doing that which is the issue I am trying to resolve. My problem is essentially why is the framework not properly storing and displaying this data, what did I do wrong to cause it to do this? I have included the further diagnoses in the OP. – ComputerLocus Jun 25 '20 at 16:09
  • 1
    @ComputerLocus - What code does it go through after being fetched from the database? The HEX implies that it is going through a _second_ call to `htmlentities()` after the `SELECT`. Get rid of either one; I recommend getting rid of the one _before_ storing into the database. – Rick James Jun 25 '20 at 19:51
  • There really isn't much to it, I just take it right from the API, convert it from the `->json` and then `updateOrCreate()` it right into the database. I don't do anything to it. Maybe Laravel is doing something to it? Here is the code for it: https://i.imgur.com/wy2W530.png – ComputerLocus Jun 26 '20 at 23:14
0

It seems like an encoding problem to me.

You need to open AppServiceProvider and add below code in boot method:

Blade::setEchoFormat('e(utf8_encode(%s))');

Hope this works for you!!

Ankit Jindal
  • 3,672
  • 3
  • 25
  • 37
  • Hmm this solution works as well as the other one but the benefit is that it is global. Is there any downside to this solution? – ComputerLocus Jun 30 '20 at 17:05