Eloquent Many to Many Attach Function sending a Null ID - php

I have the following code which is attempting to attach a product to a product_slot.
$product_slot = new ProductSlot;
$product_slot = $product_slot->where('type', $slot['id'])->first();
$product = new Product;
$product = $product->where('type', (String)$allowed_product)->first();
$product->productSlots()->sync([$product_slot->product_slot_id]);
I can do a getAttributes() and both return the following:
array (size=6)
'product_slot_id' => int 1
'type' => string 'breakfastplatter1' (length=17)
'default_product_id' => string 'bigbfhotcakebiscplat_3590' (length=25)
'allow_empty' => int 0
'created_at' => string '2016-08-17 19:04:41' (length=19)
'updated_at' => string '2016-08-17 19:04:41' (length=19)
array (size=7)
'product_id' => int 185
'type' => string 'bigbfhotcakebiscplat_3590' (length=25)
'label' => string 'Big Breakfast with Hot Cakes (Biscuit)' (length=38)
'min_option_slots' => int 0
'max_option_slots' => int 0
'created_at' => string '2016-08-17 19:05:40' (length=19)
'updated_at' => string '2016-08-17 19:05:40' (length=19)
Here is the Product Model relationship:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = ['type', 'label', 'min_option_slots', 'max_option_slots'];
public function productSlots()
{
return $this->belongsToMany('App\ProductSlot');
}
}
However, when I try to sync these models together I get the following error.
SQLSTATE[23000]: Integrity constraint violation: 1048 Column
'product_id' cannot be null (SQL: insert into product_product_slot
(product_id, product_slot_id) values (, 1))

You are using custom primary key names instead of the Laravel convention of using id as the primary key. Try adding this to your models:
class Product {
public $primaryKey = 'product_id';
}
class ProductSlot {
public $primaryKey = 'product_slot_id';
}
Also note that if you don't follow Laravel conventions, you also sometimes need to specify the non-standard primary keys when defining relationship functions:
public function productSlots()
{
return $this->belongsToMany('App\ProductSlot', 'product_slot_id', 'product_id');
}

Related

Why query statement results on eloquent orm is returning null values

I'm new to this questions thing so please bear with me.
I'm using Eloquent as my PHP database library. So I created a class that extends from Illuminate\Database\Eloquent\Model and tried to query one single record. When I print the results I know it is fetching the information, as you can see by the protected attributes, but somehow the public attributes of the record are NULL.
Am I missing some previous configuration, or is there another reason for that?
Here's my structure:
The Model, Plantilla.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Plantilla extends Model
{
/**
* #var string
*/
protected $primaryKey = 'cod_plantilla';
/**
* #var string
*/
protected $table = 'plantilla';
protected $connection = 'mysql';
public function __construct()
{
#attributes
parent::__construct();
Database2::init();
}
}
Database.php
<?php
namespace App\Models;
use Illuminate\Database\Capsule\Manager as Capsule;
class Database2
{
private static $db;
static public function init()
{
if (is_null(self::$db)) {
$capsule = new Capsule;
$capsule->addConnection([
'driver' => 'mysql',
'host' => getenv('DB_HOST'),
'database' => getenv('DB_NAME'),
'username' => getenv('DB_USER'),
'password' => getenv('DB_PASS'),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
], 'mysql');
// Make this Capsule instance available globally via static methods... (optional)
$capsule->setAsGlobal();
// Setup the Eloquent ORM... (optional; unless you've used setEventDispatcher())
$capsule->bootEloquent();
}
}
}
index.php
$p = Plantilla::where('cod_plantilla', 35)->first();
var_dump($p);
Result
object(App\Models\Plantilla)[251]
protected 'primaryKey' => string 'cod_plantilla' (length=13)
protected 'table' => string 'plantilla' (length=9)
protected 'connection' => string 'mysql' (length=5)
# Values I need
public 'cod_area_interna' => null
public 'cod_tipo_plantilla' => null
public 'nombre' => null
public 'detalle' => null
public 'personalizada' => null
public 'fecha' => null
# Values I need
protected 'keyType' => string 'int' (length=3)
public 'incrementing' => boolean true
protected 'with' =>
array (size=0)
empty
protected 'withCount' =>
array (size=0)
empty
protected 'perPage' => int 15
public 'exists' => boolean true
public 'wasRecentlyCreated' => boolean false
# Same values I need but they're protected
protected 'attributes' =>
array (size=7)
'cod_plantilla' => int 35
'cod_area_interna' => int 2
'cod_tipo_plantilla' => int 1
'nombre' => string 'Some' (length=32)
'detalle' => string 'Some' (length=142)
'personalizada' => null
'fecha' => string '2020-06-25 12:15:13' (length=19)
protected 'original' =>
array (size=7)
'cod_plantilla' => int 35
'cod_area_interna' => int 2
'cod_tipo_plantilla' => int 1
'nombre' => string 'Some' (length=32)
'detalle' => string 'Some' (length=142)
'personalizada' => null
'fecha' => string '2020-06-25 12:15:13' (length=19)
protected 'changes' =>
...
As the documentation states, you can do something like this
<?php
$flights = App\Models\Flight::all();
foreach ($flights as $flight) {
echo $flight->name;
}
So you can access the attributes aka table columns values.
In my case those are:
cod_plantilla
cod_area_interna
cod_tipo_plantilla
nombre
detalle
personalizada
fecha
I am not sure what you are actually asking about here.
The attributes are not 'properties' of the class. They are held in a protected array named $attributes. If you want to access them you can do that in the way the documentation says you can.
You could access them via the dynamic property:
$p = Plantilla::find(35);
echo $p->nombre;
Via array access:
echo $p['nombre'];
You can get the array of attributes themselves:
dump($p->getAttributes());
Serialize the model's attributes (and loaded relationships) to an array:
dump($p->toArray());
Or even get the serialized model as JSON:
echo $p->toJson();
It looks to me like Eloquent is actually performing exactly as it should be!
Eloquent does a lot of automagic with PHP magic methods. What looks like regular PHP object properties are actually dynamically accessed via __get() and __set() from the $attribute property.
In your example you have this:
$p = Plantilla::where('cod_plantilla', 35)->first();
Using Tinker (if you're new to Laravel, it's easiest to use php artisan tinker to figure out this sort of stuff), you should be able to just try accessing your database columns like this:
> $p->cod_plantilla;
35
> $p->fecha;
'2020-06-25 12:15:13'
You should find your values are returned, even though var_dump() shows nothing!

PHP display nested Object with array of other Object as 1 nested array

I have big Object with protected properties and a property can be an array of other Objects. My goal is to print this entire Object as a single nested array. So I need to convert the object to an array.
I've tried doing:
$result = (array) $object;
But this converts only the highest lever object to an array and it messes up my protected properties names with weird question mark signs.
I've also tried something like this but this simply returns an empty array:
$result= json_decode(json_encode($object), true);
Here is what my object looks like:
object(Handling\Model\SearchBooking\Booking)[133]
protected 'jabooknr' => string '018024709' (length=9)
protected 'jitsbooknr' => string '' (length=9)
protected 'status' => string 'Y' (length=1)
protected 'platform' => int 4
protected 'agentid' => string '' (length=6)
protected 'paymentInfo' => null
protected 'transports' =>
array (size=2)
0 =>
object(Handling\Model\SearchBooking\Transport)[145]
protected 'depdate' =>
object(DateTime)[146]
public 'date' => string '2016-12-06 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
protected 'carriercode' => string 'TB' (length=2)
protected 'carriernumber' => string '2067' (length=4)
protected 'brochure' => string '' (length=6)
protected 'pax' =>
array (size=2)
0 =>
object(Handling\Model\SearchBooking\Pax)[147]
protected 'id' => int 1
protected 'title' => string 'MRS' (length=3)
protected 'firstname' => string 'MA' (length=7)
protected 'name' => string 'BEN' (length=5)
protected 'age' => int 58
protected 'luggage' => int 20
protected 'handLuggage' => null
1 =>
object(Handling\Model\SearchBooking\Pax)[148]
protected 'id' => int 2
protected 'title' => string 'MR' (length=2)
protected 'firstname' => string 'P' (length=6)
protected 'name' => string 'FT' (length=4)
protected 'age' => int 60
protected 'luggage' => int 20
protected 'handLuggage' => null
protected 'departureAirport' => string 'BRU' (length=3)
protected 'arrivalAirport' => string 'AGP' (length=3)
1 =>
object(Handling\Model\SearchBooking\Transport)[149]
protected 'depdate' =>
object(DateTime)[150]
public 'date' => string '2016-12-13 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
protected 'carriercode' => string 'TB' (length=2)
protected 'carriernumber' => string '2068' (length=4)
protected 'brochure' => string '' (length=6)
protected 'pax' =>
array (size=2)
0 =>
object(Handling\Model\SearchBooking\Pax)[151]
protected 'id' => int 1
protected 'title' => string 'MRS' (length=3)
protected 'firstname' => string 'MANE' (length=7)
protected 'name' => string 'BN' (length=5)
protected 'age' => int 58
protected 'luggage' => int 20
protected 'handLuggage' => null
1 =>
object(Handling\Model\SearchBooking\Pax)[152]
protected 'id' => int 2
protected 'title' => string 'MR' (length=2)
protected 'firstname' => string 'PIRE' (length=6)
protected 'name' => string 'FYT' (length=4)
protected 'age' => int 60
protected 'luggage' => int 20
protected 'handLuggage' => null
protected 'departureAirport' => string 'AGP' (length=3)
protected 'arrivalAirport' => string 'BRU' (length=3)
protected 'extraLuggage' => null
EDIT
I have a method in my class where I "find" the result that looks like this:
public function findBooking()
{
//here happens a bunch of logic to get the right result
var_dump($object); exit; // this is the result that is show above
return $object;
}
There are a few issues, that make this difficult.
Property visibility, (private, protected) can cause issues when trying to read them outside of the class, proper. This is expected behavior as that's the point to not use public.
Classes are different. They are well defined and we know them ahead of time, but they are too diverse to account of all property names, at least not with a lot of wasted effort. Not to mention defining them "hard coding" would bite you later as it would make it difficult to maintain. For example if one of the packages does an update and you have coded the property names in you may have issues if they change them. On top of this given that these properties are not part of the classes Public "API" but instead part of the internals, it would not be unreasonable for them to change.
Properties can contain a mix of data types, including other classes or objects. This can make it challenging to handle.
Classes are part of other packages/frameworks and editing them is not practical, this restricts us to working outside of these classes.
So given these difficulties I would recommend using reflection to access the protected properties. Reflection allows you to inspect the definition of classes (and other stuff).
function jsonSerialize($obj){
return json_encode(toArray($obj));
}
function toArray($obj){
$R = new ReflectionObject($obj);
$proerties = $R->getProperties();
$data = [];
foreach($proerties as $k => $v){
$v->setAccessible(true);
$property = $v->getName();
$value = $v->getValue($obj);
if(!is_object($value)){
$data[$property] = $value;
}else if( is_a($obj,'\\DateTime')){
//if its a descendant of Datetime, get a formatted date.
// you can add other special case classes in this way
$data[$property] = $value->format('Y-m-d H:i:s');
}else{
$data[$property] = toArray($value); //call recursively
}
}
return $data;
}
So assume we have these classes
class foo{
private $bar; //private nested object
public function __construct(){
$this->bar = new bar();
}
}
class bar{
private $something = 'hello';
}
$obj = new foo;
echo jsonSerialize($obj);
See it in a sandbox here
Outputs:
{"bar":{"something":"hello"}}
Also of note is we have a special consideration for the DateTime class. Instead of getting all the properties of this we just want the date (probably) formatted in some standard way. So by using is_a() (I'm old school) we can tell if the Object $value has a given class as an ancestor of it. Then we just do our formatting.
There are probably a few special cases like this, so I wanted to mention how to handle them.
Though it is an old query, most answers are not easy to follow. So I tried to simplify the code for this specific question.
The cleaner way to get JSON objects is by implementing the JsonSerializable interface.
class Booking implements JsonSerializable
{
protected $jabooknr;
protected $platform;
//Other attributes ....
//Array of tronsport
protected $transports;
protected $extraLuggage;
public function jsonSerialize()
{
return [
'jabooknr'=> $this->jabooknr,
'platform'=> $this->platform,
'transports' => [json_encode($this->transports)
],
'$extraLuggage' => $this->extraLuggage
];
}
public function __construct($jabooknr, $platform){
$this->jabooknr = $jabooknr;
$this->platform = $platform;
$this->transports=[new Transport()];
}
}
class Transport implements JsonSerializable{
protected $carriercode;
protected $carriernumber;
//Array of Pax
protected $pax ;
public function jsonSerialize()
{
return [
'carriercode'=> $this->carriercode,
'carriernumber'=> $this->carriernumber
];
}
}
$booking = new Booking('018024709',25);
echo json_encode($booking);

JMS serializer - Why are new objects not being instantiated through constructor

Why are new entities instantiated with null for all values except the data in the json, why is the entity constructor not setting defaults - putting a die() in the constructor never gets executed.
Update:
Ok so digging into the code, when no managed entity is found, JMSS will use the doctrine instantiator class to create the entity - its sole job, to create entities without calling the constructor. Is there a reason for this? this is inside JMS\Serializer\Construction\UnserializeObjectConstructor
I've configured the object constructor to use the doctrine object constructor written by JMS, but the same issue happens with and without this.
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
Existing entities are updated without trouble, however new entities are missing all constructor set defaults.
Under 'fields' element 0 is existing, element 1 is new.
array (size=3)
'id' => int 2
'name' => string 'Categories' (length=10)
'fields' =>
array (size=2)
0 =>
array (size=7)
'id' => int 49
'displayName' => string 'Car Branded' (length=11)
'type' => string 'checkboxlist' (length=12)
'required' => boolean false
'disabled' => boolean false
'name' => string 'h49' (length=3)
1 =>
array (size=3)
'type' => string 'email' (length=5)
'name' => string 'field3491' (length=9)
'displayName' => string 'Email' (length=5)
The entity looks like this after deserializing:
object(stdClass)[2000]
public '__CLASS__' => string 'AppBundle\Entity\FormElement' (length=28)
public 'id' => null
public 'label' => string 'Email' (length=5)
public 'type' => string 'email' (length=5)
public 'defaultValue' => null
public 'required' => null
public 'mappedField' => null
public 'garbageCollection' => null
public 'sortOrder' => null
public 'disabled' => null
public 'uuid' => null
public 'form' => null
public 'multiOptions' => null
public 'filters' => null
public 'submissions' => null
The entity constructor:
public function __construct()
{
$this->required = false;
$this->disabled = false;
$this->garbageCollection = false;
$this->sortOrder = 0;
$this->type = 'text';
}
And finally this is how im deserializing:
$serializer = $this->get('jms_serializer');
$entryForm = $serializer->deserialize($json_data, 'AppBundle\Entity\EntryForm', 'json');
The issue is the default ObjectConstructor uses Doctrine's Instantiator, which does not call the class' constructor. To solve this, you can create your own ObjectConstructor that just returns a new instance of the class.
Example:
<?php
namespace AppBundle\Serializer;
use JMS\Serializer\Construction\ObjectConstructorInterface;
use JMS\Serializer\DeserializationContext;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\VisitorInterface;
class ObjectConstructor implements ObjectConstructorInterface
{
/**
* {#inheritdoc}
*/
public function construct(
VisitorInterface $visitor,
ClassMetadata $metadata,
$data,
array $type,
DeserializationContext $context
) {
$className = $metadata->name;
return new $className();
}
}
If you're using the bundle, just set jms_serializer.unserialize_object_constructor.class parameter to that new class. Otherwise in your builder, use the class as your object constructor.
What worked for me was simply adding this to the jms_serializer config:
jms_serializer:
object_constructors:
doctrine:
fallback_strategy: "fallback"

In a Laravel 5 Collection how do you return an array of objects instead of an array of arrays?

I am using Laravel 5 and a Blade template. In a view I want to iterate over an array of Model objects, not an array of arrays. If I did want to iterate over an array of arrays I would do the following, which works as expected:
$models = Foo::where('id', '>', 5)->get();
return view('home.index', ['models' => $models->toArray()]);
However I want an array of objects with accessible properties. If I were to run:
$models = Foo::where('id', '>', 5)->get();
return view('home.index', ['models' => $models->all()]);
The var_dump would look like this:
object(Illuminate\Support\Collection)[164]
protected 'items' =>
array (size=3)
0 =>
object(App\Foo)[172]
public 'id' => null
public 'foo' => null
private 'created_at' => null
private 'updated_at' => null
protected 'connection' => null
protected 'table' => null
protected 'primaryKey' => string 'id' (length=2)
protected 'perPage' => int 15
public 'incrementing' => boolean true
public 'timestamps' => boolean true
protected 'attributes' =>
array (size=4)
'id' => int 1
'foo' => string 'Foo!' (length=4)
'created_at' => string '2015-02-27 15:44:09' (length=19)
'updated_at' => null
Not only is the Model in an 'items' object the properties are not filled.
In a view I would like to do something like this:
#foreach ($models as $model)
#include('_partial') {
'id' => $model->id,
'foo' => $model->foo,
}
#endforeach
How do I get an array of Models instead of an array of an array of Models?
According to the Docs:
$array = $collection->all();
"The all method returns the underlying array represented by the collection."
Your code is just fine, except you don't need to call toArray on your Eloquent query result. Let me explain what the code does, so you can understand why the following is what you want:
$models = Foo::where('id', '>', 5)->get();
return view('home.index', ['models' => $models]);
The first statememt Foo::where('id', '>', 5)->get(); returns a value of type Illuminate\Support\Collection.
That Collection class holds the collection elements in a protected property called $items (as you could see from your dump protected 'items' =>), which is of type array. The class also implements an interface called IteratorAggregate, which basically means it allows any variable of that type to be iterated using a foreach statement.
In your case this means that, even if $models is of type Illuminate\Support\Collection it will behave as an array when you go over it with foreach:
#foreach ($models as $model)
{
{{ $model->foo }}
}
So in short Collection is an iterable object that can be treated as an array, but is better than an array because if offers extra methods that allow you to manipulate the items from the collection. You can check the Collection API to see a complete list of available methods.
So in reality you're getting an improved array of models.
Also, don't worry that the properties are not filled, they are in fact filled, I just think you're looking in the wrong place.
If you look closely at your var_dump, you'll see that you have some lines that start with public, protected or private. Those keywords mean that those lines contain object properties. In the case of Laravel Eloquent models, the values fetched from the database are not stored directly in the properties that are named like the database columns. The values are in fact stored in a single property called attributes and are fetched using PHP's magic _get. Take a look at the comments on the code below:
object(Illuminate\Support\Collection)[164]
protected 'items' =>
array (size=3)
0 =>
object(App\Foo)[172]
public 'id' => null // <<< THE VALUES ARE
public 'foo' => null // <<< NOT STORED HERE
private 'created_at' => null
private 'updated_at' => null
protected 'connection' => null
protected 'table' => null
protected 'primaryKey' => string 'id' (length=2)
protected 'perPage' => int 15
public 'incrementing' => boolean true
public 'timestamps' => boolean true
protected 'attributes' =>
array (size=4)
'id' => int 1 // <<< THEY ARE HERE
'foo' => string 'Foo!' (length=4) // <<< AND HERE
'created_at' => string '2015-02-27 15:44:09' (length=19)
'updated_at' => null
Laravel does a lot of trickery behind the scenes to allow you to get things done with only a few lines of code. That's why a var_dump will not always display the simple data structures that you might expect.
Figured out the problem. I was explicitly defining the attributes in the Model. Laravel uses __get() in a particular way which causes passed parameters to be overridden whatever attributes are explicitly defined.
In other words, I was getting null values in the partial because the info I was passing to the partial was overridden.

laravel problems with mutators

My journey into laravel 4 (from laravel 3) continues....
I have an Article model, accessing a table called articles.
I have set up the model with the following mutators:
class Article extends Eloquent {
public function getArticleDateAttribute($value)
{
return date('d/m/Y', strtotime($value));
}
public function getValidUntilAttribute($value)
{
return date('d/m/Y', strtotime($value));
}
}
Now when I query the database with the following AND Delete the mutators everything works as expected and I get the data I expect:
public function getTest() {
$data = Article::select(array(
'articles.id',
'articles.article_date',
'articles.image_link',
'articles.headline',
'articles.category'
)) ->get()
->toArray();
var_dump($data);
//return View::make('_layouts.master');
}
In my test I get the results as expected as this sample:
array (size=5)
'id' => int 3
'article_date' => string '2008-06-03 00:00:00' (length=19)
'image_link' => string '' (length=0)
'headline' => string 'Sussex Amateur Course Closure' (length=29)
'category' => int 6
Now, when I add back the mutators, with the exact query I get the following data:
array (size=6)
'article_date' => string '03/06/2008' (length=10)
'valid_until' => string '01/01/1970' (length=10)
'id' => int 3
'image_link' => string '' (length=0)
'headline' => string 'Sussex Amateur Course Closure' (length=29)
'category' => int 6
the column order is changed and it's included a column I didn't originally request. How should I correctly implement mutators and why do the columns change?
Have I misunderstood this?
Thanks
Ray
The mutators will be called, because the code is built that way. See the implementation of this function in the Eloquent Model class (which is called by toArray()):
/**
* Convert the model's attributes to an array.
*
* #return array
*/
public function attributesToArray()
{
$attributes = $this->getAccessibleAttributes();
// We want to spin through all the mutated attributes for this model and call
// the mutator for the attribute. We cache off every mutated attributes so
// we don't have to constantly check on attributes that actually change.
foreach ($this->getMutatedAttributes() as $key)
{
if ( ! array_key_exists($key, $attributes)) continue;
$attributes[$key] = $this->mutateAttribute($key, $attributes[$key]);
}
return $attributes;
}
https://github.com/laravel/framework/blob/master/src/Illuminate/Database/Eloquent/Model.php

Categories