0

I am currently getting data from an external application, by using Laravel in my app.

I have created a simple webhook in my controller file, like:

public function webhook(Request $request)
{
        $document = new Document();

        $document->fill($request->all());

        //Insert (or update existing) data in our database.
        $document::updateOrCreate(
            ['booking_reference' => $document->booking_reference],
            $document->getAttributes()
        );

        return response()->json('OK');
}

Now my problem is, I have to insert this data in a table. In order to do this, the received json objects should match the name of my table columns. I am able to set the json names in the external application - however, sometimes the external application add child elements to the json names.

For example, imagine this is my database:

id    |   booking_reference   | shipment_reference

And my JSON have these:

"booking_reference"  : "32000"
"shipment_reference" : "TREXOOO"

Then the data will be inserted correctly in my database. However as mentioned, the JSON objects can sometimes look like this:

"booking_reference_0"  : "32000"
"shipment_reference_5" : "TREX000"

In this example, my table will not be updated, since the JSON names does not match my table names.

How can I do, so I dynamically can insert the data in my table? The data will always have the prefix of the table name like booking_reference or booking_reference_{child}

Update:

Using Erubiel's answer, I can get the columns to dynamically match the name. However, I have a basic belongsTo relation setup, but solution by Erubiel strips the $document object, and only adds the mapped columns.

$document->creator()->associate(auth()->user());

and in my model:

public function creator()
{
    return $this->belongsTo(User::class, 'user_id', 'id');
}

But my JSON response does not contain any user_id or creator info:

{"booking_reference":"CDK10000","pickup_location":"Afhentingsadresse 100 2650","drop_off_location":"Leveringsadresse 200 1000","comments":"HaHA","shipment_referenec":"SBRY100000"}

If I just get the $document, before the foreach loop:

{"user_id":1,"creator":{"id":1,"name":"Oliver","email":"[email protected]","created_at":"2018-08-27 10:58:10","updated_at":"2018-08-27 10:58:10"}}

Final edit, with solution

Using Erubiel's answer, I just modified it a bit to store the new values in my existing JSON object:

foreach ($request->all() as $key => $value) {
     $newKey = preg_replace("/(_?\d+)+$/", '', $key); //this generates the name of column that you need
     $document->$newKey = $value;
 }

Which works like a charm - output:

{"user_id":1,"booking_reference":"CDK10000","pickup_location":"Afhentingsadresse 100 2650","drop_off_location":"Leveringsadresse 200 1000","comments":"HaHA","shipment_referenec":"SBRY100000","creator":{"id":1,"name":"Oliver","email":"[email protected]","created_at":"2018-08-27 10:58:10","updated_at":"2018-08-27 10:58:10"}}
8
  • however, sometimes the external application add child elements to the json names. Is it possible for you to take action on this external application ? It seems like a better idea than opening your application to inconsistency Commented Aug 27, 2018 at 12:06
  • Unfortunately not - the only consistency from the external application is that the names will always come first and then it will dynamically add to that name if needed, like shipment_reference_XXX Commented Aug 27, 2018 at 12:08
  • I see, then you'll need some pre-processing, did you try using strpos to check if they key exists and matches what the external application returns ? It would check if substring is present in string Commented Aug 27, 2018 at 12:11
  • can you add a screen shot of how the dd($document) looks like after fill ? Commented Aug 27, 2018 at 12:11
  • something like strpos($bookingReferenceKey, $validBookingReferenceKey), where bookingReferenceKey would be the key returned by the json, and validbookingReferenceKey would be booking_reference Commented Aug 27, 2018 at 12:12

3 Answers 3

1

This should work, as suggested on the comments, preprocess.

public function webhook(Request $request)
{

    $arrAux = array();
    foreach($request->all() as $key => $value){
        $newKey = preg_replace("/(_?\d+)+$/","",$key); //this generates the name of column that you need
        $arrAux[$newKey] = $value;
    }

    //Insert (or update existing) data in our database.
    Document::updateOrCreate(
        ['booking_reference' => $arrAux['booking_reference']],
        $arrAux
    );

    return response()->json('OK');
}
Sign up to request clarification or add additional context in comments.

8 Comments

This approach requires me to "map" all the values from the JSON - which can be a lot. I need a way to do it dynamically
Should i access it using $doc or $document after the loop?
Btw. this gives me "Invalid argument supplied for foreach" - on this ($doc as $key => $value)
It leaves the JSON objects NULL
I modified your answer a bit, and kept the JSON object instead of array in the foreach loop. It works like a charm now. Thanks!
|
0

Your approach to resolve problem seems like architecture/design anti-pattern (insert's based on raw json data). That's not answer to your question but only my opinion.

The quickest solution to Your problem seems be a class which will accept $jsonKey and translate it/return $dbKey.

Your current code:

$document->fill($request->all());

New approach:

$input = $this->translator->fromInput($request->all());
$document->fill($input);

Example sanitize/translate class:

class Translator {
    /**
     * @param array $input
     * @return array
     */
    public function fromInput(array $input): array
    {
        $translated = [];

        foreach ($input as $key => $value) {
            switch ($key){
                case 'a':
                case 'a_1':
                    $translated['a'] = $value;
                    break;
                case mb_strpos($key, 'b'):
                    $translated['b'] = $value;
                    break;
                default: $translated[$key] = $value;
            }
        }

        return $translated;
    }
}

2 Comments

What is translator and fromInput in this example?
@oliverbj translator is a class with method fromInput , please see updated post
0

Add protected $fillable property with list of available fields to mass assign to your model:

protected $fillable = ['booking_reference','shipment_reference'];

Then catch mismatch field error in your controller

try {
    $document->update($request->input());
} catch (MassAssignmentException $e) {
    $data = $request->input();
    foreach ($data as &$field) {
       preg_match('~([a-zA-Z]+(_[a-zA-Z]+)+)~', $field, $match);
       $field = $match[1];
    }
    $document->update($data);
}

2 Comments

When receiving the API from the external application, above code always stop at the try{$document->update($request->input()); - however, it will just remove the objects that contain child elements (or elements that it cannot find I guess). I have added the fillables to my model too.
It never runs the catch(MassAssignmentException $e)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.