Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Raymond's avatar

What when we receive/get (remote) JSON data?

I am working on a website, and we learned a lot here @laracasts but what i am missing is, how to receive and alter data in Laravel, many things I learned on laracasts about presenters, commanders, solid design principles and transformers. And sure, I've worked my way around it.

The nicest feature i've picked up was a lesson in de commanders series, how we could map data with the Reflection class. I'm using this when i receive data from guzzlehttp/client. Also found out that i could use presenters everywhere.

So my request would be @jeffreyway, is it useful to make a screencast about receive/get (remote) JSON data?

Below an example i am working on, maybe not the best code you've seen, but just give me your comments :-)

<?php namespace Acme\Hotels;

class HotelList extends HotelGlobal {

    // ..... //

    /**
     * Get the list
     * @return string
     */
    public function doRequest()
    {
        $url = 'http://domain.tld/list';
        $this->generateCommonRequestParameters();

        $query = $this->query;

        $response = $this->buildForCache($url, $query);

        $this->setHotelListResponseAttributes($response['HotelListResponse']);

        if(array_key_exists('EanWsError', $response['HotelListResponse']))
        {
            $error = $response['HotelListResponse']['EanWsError'];
                $this->setErrors($error['presentationMessage']);
                Log::error($error['verboseMessage']);
                Log::error($error['ServerInfo']);
            return false;
        }

        $this->mapHotels(Hotel::class, $response['HotelListResponse']['HotelList']['HotelSummary']);
    }

    // ..... //

    /**
     * Mapping hotels (actually from a trait)
     * @param $className
     * @param $hotels
     */
    protected function mapHotels($className, $hotels)
    {
        if( array_key_exists( 'hotelId', $hotels ) )
            $hotels = array($hotels);
        foreach($hotels as $hotel)
        {
            $this->hotels[] = $this->mapDataToClass($className, $this->unknownSignReplacer($hotel));
        }
    }

    // ...Cache Trait ... //

    protected function buildForCache($url, $query)
    {
        if ($this->cacheEnabled) {
            $that = $this;
            $cacheName = $this->generateCacheName($this->query);
            $response = Cache::remember($cacheName, $this->cacheMinutes, function () use ($that, $url, $query) {
                $client = $that->guzzleSend($url, $query);
                return $client->json();
            });
            return $response;
        } else {
            $response = $this->guzzleSend($url, $query)->json();
            return $response;
        }
    }

    // ..... //

    /**
     * @param       $className
     * @param array $input
     *
     * @return object
     * @throws \InvalidArgumentException
     */
    protected function mapDataToClass($className, array $input)
    {
        $dependencies = [];

        $class = new ReflectionClass($className);

        foreach ($class->getConstructor()->getParameters() as $parameter)
        {
            $name = $parameter->getName();

            if (array_key_exists($name, $input))
            {
                $dependencies[] = $input[$name];
            }
            elseif ($parameter->isDefaultValueAvailable())
            {
                $dependencies[] = $parameter->getDefaultValue();
            }
            else
            {
                throw new InvalidArgumentException("Unable to map input to class: {$name} on: {$className}");
            }
        }

        return $class->newInstanceArgs($dependencies);
    }
    // ..... //
}

class Hotel {
    use PresentableTrait;

    protected $presenter = HotelPresenter::class;

    public $hotelId;
    public $name;
    public $address1;
    // ..... //
}

And in my controller i am doing this:

$hotel->doRequest();
        if($hotel->hasErrors())
            return $hotel->getErrors();
        $hotels = $hotel->get();

edit: typo

0 likes
6 replies
jimmck's avatar

Here is how I get an index of records to fill a list box.

JS: function index() { var list = $("#list_box"); alert("index"); list.empty(); //clear list box. postData = "";

sqlBusy = true;
$.myPost('idx', postData, function(data) {
    sqlBusy = false;
    alert(data);
    //var myRec = eval('(' + data + ')'); // I know totally evil, so they say!!!
    var myRec = JSON.parse(data); // as opposed to jQuery.ParseJSON...??? Works
    //var myRec = jQuery.parseJSON('(' + data + ')'); // no work. doc says cannot use escape chars?????
    //var myRec = jQuery.parseJSON(data); // And so which way to go, eval bad. Backend formats the hell out of string!!!
    //alert(myRec.IdNum);

    switch (myRec.type)
    {
        case PAYLOAD_DATA:
            for (i = 0; i < myRec.CoverageCategories.Category.length; i++)  {
                //list.append(new Option(myRec.Category[i].Desc, myRec.Category[i].Id));
                //opt = new Option(myRec.Category[i].Dessc, myRec.Category[i].Id);
                //alert(myRec.Category[i].Desc);
                viewModel.addItem(myRec.CoverageCategories.Category[i].Desc, myRec.CoverageCategories.Category[i].Id);
            }
            break;

        case PAYLOAD_MSG:
            break;

        case PAYLOAD_ERROR:
            break;

        default:
            alert("UnKnown Message from Server.");
            break;
    }

}, sqlError, TIMEOUT);
viewModel.execClear();

}

Laravel Controller: public function index() { $payload = null; try { $rootName = ""; $root = null; $type = -1;

            $cmd = new SelectAllCvgCatTypes();
            $ret = $cmd->get();

            if (Utils::isType($ret, "DataMap"))
            {
                $rootName = $ret->getName();
                $root = $ret;
                $type = 0;
                $debbie = 1;
            } else if (Utils::isType($ret, "Exception"))
            {
                $rootName = "Error";
                $root = $ret;
                $type = Payload::ERROR;
            } else if (Utils::isType($ret, "Message"))
            {
                $rootName = "Msg";
                $root = $ret;
                $type = Payload::MSG;
            }

            // Create Payload object.
            $payload = Payload::create($type, 0, "Index", $rootName, $root);
        } catch (Exception $e)
        {
            return $e; // Should never fire ?
        } finally
        {
            $ret = $payload->toJSON();
            return $ret;
        }
    }

Laravel Model to Get Data: public function index() { $ret = null; $select = (new Select()) ->addColumn(CvgCatDefs::listCols()) ->addFrom(CvgCatDefs::table()); $result = $this->sql->doQuery($select->toString());

        if (Utils::isType($result, 'ResultSet'))
        {
            $rs = new ResultRows();
            $rs
                ->addColumn(new ResultColumn("Melissa"))
                ->addColumn(new ResultColumn("Denise", ColType::INT, 999));
            $row = $rs->newRow();
            $row->set("Melissa", "Test");
            $rs->addRow($row);

            $row = $rs->newRow();
            $row->set("Melissa", "Test1");
            $rs->addRow($row);

            $row = $rs->newRow();
            $row->set("Melissa", "Test2");
            $rs->addRow($row);

            $rSet = new ResultSet();
            $rSet->addResult($rs);

            // We are going do DataMaps in the actual command handler.
            // So here we are going to return a ResultStArray Object..
            $ret = (new ResultSetArray())
                ->addResultSet($result)
                ->addResultSet($rSet);
        }
        return $ret;
    }

// uses my PHP MySQL library. // The controller takes the return and renders 1 of 3 objects, Data Return which is a JSON object. Message Object which will rendered by controller into a Message JSON Payload, or an Exception Payload.

Raymond's avatar

@jimmck what you are referring to is JS/jQuery/Other. That's my end step but in order to receive data, and for example google need to index, you gonna have troubles with indexing, for what i know is that the google bot is skipping javascript.

But let's things make clear (in my case):

  1. get/post json data from/to a secure url
  2. convert in laravel json object to a php object/class
  3. normalize data to make sure i have control.
  4. export data for caching and for json
  5. output for html (blade) and jQuery/AngularJS (pagination).
thepsion5's avatar

One of the data sources in my current major project at work is retrieved from an external JSON site. This is how I handle it:

  1. I have a JsonFileLoader that just opens a JSON file, tries to decode it, and throws an exception if it fails.
  2. I have a ElectionCommissionJsonTransformer, which takes the decoded data from the JSON source and turns it into an instance of an ElectionCommission entity
  3. I have the JsonElectionCommissionRepo, which has both injected into it and hides this functionality from the rest of my app.

Here's the repository:

class JsonElectionCommissionRepo implements ElectionCommissionRepo
{
    protected $commissions = array();

    protected $loaded = false;

    protected $jsonSource = '';

    protected $transformer;

    protected $loader;

    public function __construct($jsonSourceFile, JsonFileLoader $loader, ElectionCommissionJsonTransformer $transformer)
    {
        $this->jsonSource = $jsonSourceFile;
        $this->transformer = $transformer;
        $this->loader = $loader;
    }

    /**
     * @param TnCounty $county
     * @throws \Gvt\Domain\Core\Exceptions\ResourceNotFound
     * @return ElectionCommissionEntity
     */
    public function findByCounty(TnCounty $county)
    {
        $name = (string) $county;
        $commission = $this->getCommissions($name);
        if(!$commission) {
            throw new ResourceNotFound("Unable to find an Election Commission for [$county] County.");
        }
        return $this->transformer->transform($commission);
    }

    /**
     * @return \Illuminate\Support\Collection
     */
    public function all()
    {
        $commissions = $this->getCommissions();
        return $this->transformer->transformCollection($commissions);
    }

    protected function getCommissions($county = null)
    {
        $this->load();
        if($county) {
            return (isset($this->commissions[$county])) ? $this->commissions[$county] : null;
        } else {
            return $this->commissions;
        }
    }

    protected function load()
    {
        if($this->loaded == false) {
            $commissions = $this->loader->load($this->jsonSource);
            foreach($commissions as $commission) {
                $this->commissions[$commission->county] = $commission;
            }
            $this->loaded = true;
        }
    }
}

And here's the transformer I use:

class ElectionCommissionJsonTransformer extends AbstractTransformer
{

    public function transform($item)
    {
        $attributes = array(
            'id' => $item->id,
            'county' => new TnCounty($item->county),
            'admin_name' => $item->name,
            'address' => $item->address,
            'city' => $item->city,
            'state' => 'TN',
            'zip' => $item->zip,
            'mailing_address' => (string) $item->mailing_po_box,
            'mailing_zip' => $item->mailing_zip,
            'phone' => $this->transformPhone($item->phone),
            'fax' => $this->transformPhone($item->fax),
            'email' => $item->email,
            'website' => $this->transformUrl($item->website),
            'office_hours' => $item->hours
        );
        return new ElectionCommission($attributes);
    }

    protected function transformUrl($field)
    {
        if(!$field) {
            $field = null;
        } elseif(substr($field, 0, 7) != 'http://') {
            $field = 'http://' . $field;
        }
        return $field;
    }

    protected function transformPhone($phone)
    {
        $phone = array(
            '(',
            substr($phone, 0, 3),
            ') ',
            substr($phone, 3, 3),
            '-',
            substr($phone, 6)
        );
        return implode('', $phone);
    }
}
1 like
jimmck's avatar

get/post from secure url? Part of infrastructure. Idea here is a one page web app. I dont want pages flying all over the place. I want data. Interface is a seperate entity. In a Windows app, you do not redefine the entire window everytime you get new data. My MySQL libs move data to and from JSON and PHP. Data is normalized through SQL, and Businese Models Export data is supported. You need to define the format and transport. Output is Webpage is JSON and rendered by jQuery. But again, you have to divide the models between client and server. Client side it could be a Plain Old JavaScript object. Or a Backbone view etc. Server side same deal, Plain old PHP object, XML object, JSON etc...

Here is an example of translating a MySQL/PHP Data reference to JSON

class Payload { const DATA = 0; const MSG = 1; const ERROR = 2; const STATUS = 3;

static public function create($type, $status, $cmd, $rootName, $root)
{
    // Define Payload JSON Object.
    $payload = (new DataMap("Payload"))
        ->setUseName(false)
        ->addNode(new Element("type", MapDataType::NUMBER))
        ->addNode(new Element("status", MapDataType::NUMBER))
        ->addNode(new Element("cmd", MapDataType::STRING))
        ->addNode(new Element("rootName", MapDataType::STRING));

    switch ($type)
    {
        // Payload::DATA comes into function as is.
        case Payload::MSG:
            $root = self::msgPayload($root);
            break;

        case Payload::ERROR:
            $root = self::errorPayload($root);
            break;
    }

    // Create Payload object.
    $payload
        ->addMapping("type", Mapping::addValue($type))
        ->addMapping("status", Mapping::addValue($status))
        ->addMapping("cmd", Mapping::addValue($cmd))
        ->addMapping("rootName", Mapping::addValue($rootName))
        ->addDataMap($root, true);
    return $payload;
}

static public function msgPayload(Message $m)
{
    $dataMap = (new DataMap("Msg"))
        ->addNode(new Element("Status", MapDataType::NUMBER))
        ->addNode(new Element("Code", MapDataType::NUMBER))
        ->addNode(new Element("Message", MapDataType::STRING))
        ->addMapping("Status", Mapping::addValue($m->getStatus()))
        ->addMapping("Code", Mapping::addValue($m->getCode()))
        ->addMapping("Message", Mapping::addValue($m->getMsg()));
    return $dataMap;
}

static public function errorPayload(MsgException $m)
{
    $dataMap = (new DataMap("Error"))
        ->addNode(new Element("Class", MapDataType::STRING))
        ->addNode(new Element("Code", MapDataType::NUMBER))
        ->addNode(new Element("Line", MapDataType::NUMBER))
        ->addNode(new Element("Trace", MapDataType::STRING))
        ->addNode(new Element("Message", MapDataType::STRING))
        ->addMapping("Class", Mapping::addValue($m->getName()))
        ->addMapping("Code", Mapping::addValue($m->getCode()))
        ->addMapping("Line", Mapping::addValue($m->getLine()))
        ->addMapping("Trace", Mapping::addValue($m->getTrace()))
        ->addMapping("Message", Mapping::addValue($m->getMessage()));
    return $dataMap;
}

} ?>

Raymond's avatar

@thepsion5 i get your point, but how do you handle missing items? for example your first request will return:

{
    "glossary": {
        "name": "example",
        "field1": "fooBar"
    }
}

and your second request will return:

{
    "glossary": {
        "name": "example",
        "field3": "fooBar",
        "price": {
            "taxRate": 18,
            "rate": 20
        }
    }
}

In my opinion you should receive an ErrorException, if you use it like:

public function transform($data)
{
    $array = [
        'name' => $data->name,
        'field1' => $data->field1,
        'field3' => $data->field3,
        'price' => $data->price->rate,
}

@jimmck also i get your point, but the thing is, in my project prices are variable, within a timespan of 10 minutes, I reduce the request by using a 10 minute caching on php, also i ain't flying over the pages, only the first 10/20/50 items are directly from laravel/php (40ms to load), the last part (pagination) it is receiving by plain javascript/jQuery through laravel/php, formatted in the way that i want.

thepsion5's avatar

@Raymond In this particular case, the JSON data should always have the required fields, so I don't need to gracefully handle cases where it won't. The generic error handler will eventually take care of that.

There are some optional fields, but the JSON is structured so those fields will still be present, they'd just be false or empty strings.

Please or to participate in this conversation.