I'm integrating Doctrine2 into CodeIgniter.
My Entity class News.php
<?php
namespace Models\Entities;
/**
* News
*
* #Table(name="news", indexes={#Index(name="slug", columns={"slug"})})
* #Entity
*/
class News {
//HERE: properties, getter, setter, etc.
}
And my model class News_model.php
<?php
require_once(APPPATH."models/entities/News.php");
use Models\Entities\News;
class News_model extends CI_Model {
//Model code here
}
When I use $news = $this->em->getRepository('Entities:News')->findAll() in News_model class and printed, var_dump($news), I get an array of object(Models\Entities\News), like follow:
array (size=6)
0 =>
object(Models\Entities\News)[87]
private 'id' => int 1
private 'title' => string 'text here' (length=9)
private 'slug' => string '' (length=0)
private 'text' => string 'text here' (length=9)
private 'news' => null
)
But I expected an associative array, like follow:
array (size=6)
0 =>
array (size=4)
'id' => string '1' (length=1)
'title' => string 'text here' (length=9)
'slug' => string '' (length=0)
'text' => string 'text here' (length=9)
)
How can I convert the Doctrine Entity object (first showed array) result to a PHP associative array (second showed array)?
You are working with Doctrine ORM. ORM means Object Relational Mapper. You use ORM because you want to get results as objects. Otherwise you better start to read about Doctrine DBAL.
Then this line:
$news = $this->em->getRepository('Entities:News')->findAll();
If you use findAll() then you expect an Collection of objects. In Doctrine we talk about collections instead of array's.
Those collections you can simple walk through with a foreach just like a normal array. Then you can use each object inside the collection which has some benefits: especial directly call some custom methods
$newitems = $this->em->getRepository('Entities:News')->findAll();
foreach($newsitems as $newsitem)
{
echo '<h3>' . $newsitem->getTitle() . '</h3>';
}
why don't you use native doctrine method getArrayResult in your class repository?
In your controller :
/***/
$news = $this->em->getRepository('Entities:News')->yourMethodName();
/***/
In your class Repository :
class NewsRepository extends \Doctrine\ORM\EntityRepository
{
public function yourMethodName()
{
$query = $this->createQueryBuilder('n');
/***/
return $query->getQuery()->getArrayResult();
}
}
I agree with #Frank B, the reason you use Doctrine is that you get to work with objects instead of a magic array.
However, if you are set on having an array, you can use the Symfony Serializer to convert any object to an array.
Just add some annotations to your entity:
use Symfony\Component\Serializer\Annotation\Groups;
class News {
/**
* #Groups({"group1"})
*/
protected $id;
/**
* #Groups({"group1"})
*/
protected $title;
/**
* #Groups({"group1"})
*/
protected $slug;
}
Then you can convert your array collection like this:
$news = $this->em->getRepository('Entities:News')->findAll();
$serializer = $this->getContainer()->get('serializer');
$newsArray = $serializer->normalize($news, 'json', ['groups' => ['group1']]);
Related
In a Laravel project I have to store some data in json. For phpUnit tests I use a factory with faker. I try to fake a json structure for the tests, but it always fail on validation. Is there any proper way to create a json in factory that passes the validation for json?
I tried a simple json array, and the json array with json_encode, both of them failed at validation, and gives errors.
With simple json like: 'settings' => ['areas' => ['full', 'city']]
the error is:
Property [settings] is not of expected type [json].
Failed asserting that an array contains 'array'.
With json_encode like: 'settings' => json_encode(['areas' => ['full', 'city']])
the error is:
Property [settings] is not of expected type [json].
Failed asserting that an array contains 'string'.
My model:
class Example extends Model
{
protected $fillable = [
'name',
'settings'
];
public static $rules = [
'name' => 'required|string|max:255',
'settings' => 'nullable|json'
];
protected $casts =
'settings' => 'array'
];
}
My factory:
<?php
class ExampleFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Example::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'name' => $this->faker->words(3, 7),
'settings' => json_encode(['areas' => ['full', 'city']]) // or what?
];
}
}
In my test file:
/** #test */
public function shouldStore(): void
{
$item = $this->model::factory()->make();
$data = $item->toArray();
$this->post(action([$this->controller, 'store']), $data)
->assertOk();
}
Your issue is that you are casting the property to array, but you are storing a string, you should be passing an array.
Do this:
class ExampleFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Example::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'name' => $this->faker->words(3, 7),
'settings' => ['areas' => ['full', 'city']],
];
}
}
And the rule (no idea where are you using that) should be like this:
'settings' => 'nullable|array'
Your model works just fine with an array because the cast takes care of the conversion for you. However when posting to the controller you need to manually cast the data to JSON:
public function shouldStore(): void
{
$item = $this->model::factory()->make();
$data = $item->toArray();
$data['settings'] = json_encode($data['settings']);
$this->post(action([$this->controller, 'store']), $data)
->assertOk();
}
However this does mean that you may need to json_decode the data in the controller before creating the model.
Alternatevely you can do what #matiaslauriti is suggesting and post the data as an array to begin with
You need to cast the property to array:
SomeModel extends Model
{
protected $casts = [
'settings' => 'array',
];
}
foreach ($pElements as $pElement) {
var_dump($pElement);
}
If I execute the above, I notice it dumps each value in the 'nodes' array even though $pElement is an object. Can anyone explain this behavior?
At first I thought running foreach on an object automatically searches for a member array, but the first listed array namespaces seems to be ignored.
Here is the full object $pElements:
/var/www/html/phpTestArea/index.php:35:
object(Symfony\Component\DomCrawler\Crawler)[38]
protected 'uri' => null
private 'defaultNamespacePrefix' => string 'default' (length=7)
private 'namespaces' =>
array (size=0)
empty
private 'baseHref' => null
private 'document' =>
object(DOMDocument)[2]
public 'doctype' => string '(object value omitted)' (length=22)
public 'implementation' => string '(object value omitted)' (length=22)
<public properties removed>
private 'nodes' =>
array (size=2)
0 =>
object(DOMElement)[36]
<public properties removed>
1 =>
object(DOMElement)[35]
<public properties removed>
private 'isHtml' => boolean true
If you take a look at the source code of that class here, you can see that it implements two interfaces, defined as follow:
Countable: this define how the instances of the class should behave when passed to the native php count function. The counting depends on the implementation.
interface Countable {
abstract public count(void): int
}
IteratorAggregate: this is the one that return an iterator (which extends Traversable) that defines how and what should be traversed.
interface IteratorAggregate extends Traversable {
abstract public getIterator(void): Traversable
}
So if you look closely into the source code for these two functions, you will see how these are implemented:
/**
* #return int
*/
public function count()
{
return \count($this->nodes);
}
/**
* #return \ArrayIterator|\DOMElement[]
*/
public function getIterator()
{
return new \ArrayIterator($this->nodes);
}
So if you call count($pElements), the object's internal count function will be executed, therefore you would get the count of the nodes property.
In the same way, if you iterate with a foreach over $pElements, you are iterating over the nodes property, as this is the behaviour defined by getIterator().
I've been working on creating a clean interface for our various web application and I've run into a snag with Laravel's API Resources not properly converting the incoming json array into laravel collections.
I can do it with a single resource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
use App\Product;
class ProductResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'name' => $this->resource['product']['name'],
'description' => $this->resource['product']['description'],
'price' => $this->resource['product']['rental_rate']['price']
];
//return parent::toArray($request);
}
}
print this response outputs:
{"name":"Arri Skypanel S60-C","description":"Arri Sky Panel S60-C 450w input with a 2000w tungsten equivalent & Combo Stand","price":"260.0"}
However trying to take this single item and turn it into a collection of items isn't going anywhere.
Anybody got a clue what I'm missing?
Pulling the API data looks like this:
namespace App;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client;
class ThirPartyAPI
{
private $url = 'https://api.third-party.com/api/v1/';
public function pull($query, $additionalParams) {
$client = new Client;
$result = $client->get($this->url . $query . $additionalParams, [
'headers' => [
'Content-Type' => 'application/json',
'X-AUTH-TOKEN' => env('CURRENT-AUTH-TOKEN'),
'X-SUBDOMAIN' => env('CURRENT-SUBDOMAIN')
]
]);
$array = json_decode($result->getBody()->getContents(), true);
return $array;
}
}
The API returns a lot of json data.
This is the Product model:
public function getAllProducts() {
try {
$productData = [];
$query = "/products?page=1&per_page=3&filtermode=active";
$additionalParams = "";
$productData = new ThirdPartyAPI;
$productData = $productData->pull($query, $additionalParams);
$productData = $productData['products'];
return ProductsResource::make($productData);
} catch (\Exception $ex) {
return $ex;
} catch (\Throwable $ex) {
return $ex;
}
}
Right now I'm trying something this to convert all the returned arrays into something I can control more:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class ProductsResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'products' => $this->collection->mapInto(function($request) {
return[ 'name' => $this->resource['name'],
'description' => $this->resource['description'],
'price' => $this->resource['rental_rate']['price']
];
})
];
}
However var_dumping the data just returns this:
object(App\Http\Resources\ProductsResource)[200]
public 'resource' =>
array (size=3)
0 =>
array (size=37)
'id' => int 164
'name' => string '10A Dimmer' (length=10)
[Lots of data]
...
'sale_rates' =>
array (size=0)
...
1 => .....
[cont]
public 'with' =>
array (size=0)
empty
public 'additional' =>
array (size=0)
empty
I've tried various forms of data conversion on the return json info and haven't had a lot of results except errors and confusing business. I'm a little shady on how Laravel handles API Resource handling.
Ok after some investigation into Laravel's 'make', 'mapinto' and 'map' methods for collections, I eventually got a working result from this conversion here:
$productData = ThirdPartyAPI;
$productData = $productData->pull($query, $additionalParams);
$productData = $productData['products'];
$products = collect($productData)->map(function($row){
return ProductsResource::make($row)->resolve();
});
var_dump($products);
That var_dump returns this:
object(Illuminate\Support\Collection)[228]
protected 'items' =>
array (size=3)
0 =>
array (size=3)
'name' => string '10A Dimmer' (length=10)
'description' => string '10amp Dimmer (Max 2.4k)' (length=23)
'price' => string '5.0' (length=3)
....
[And so on]
The initial information that was returned was a multidimensional array.
$returnedArray = array(
['array' => 1, 'name' => 'name', etc],
['array' => 2, 'name' => 'name, etc]
);
Laravel's default collection method only turns the top array into a collection. In order to properly be able to control the results via the Resource models we have to convert the whole set of arrays to collections, which means we have to iterate through the returned data to convert it to something laravel can read properly. That's what the map method does.
According to the docs it, 'The map method iterates through the collection and passes each value to the given callback. The callback is free to modify the item and return it, thus forming a new collection of modified items'
the make method creates a new collection instance. I don't know what the resolve function does except that the docs mention that it 'resolves a given class or interface name to its instance using the service container'. I'm going to assume it means that it makes sure passes through the class properly?
Anyway I hope that helps people in the future.
EDIT: I realized the amount of text might be intimidating. The essence of this question:
How to implement ArrayAccess in a way that makes setting multidimensional values possible?
I am aware that this was discussed here already but I seem unable to implement the ArrayAccess interface correctly.
Basically, I've got a class to handle the app configuration with an array and implemented ArrayAccess. Retrieving values works fine, even values from nested keys ($port = $config['app']['port'];). Setting values works only for one-dimensional arrays, though: As soon as I try to (un)set a value (eg. the port in the previous example), i get the following error message:
Notice: Indirect modification of overloaded element <object name> has no effect in <file> on <line>
Now the general opinion seems to be that the offsetGet() method has to return by reference (&offsetGet()). That, however, does not solve the problem and I'm afraid I don't know how to implement that method correctly - why is a getter method used to set a value? The php doc here is not really helpful either.
To directly replicate this (PHP 5.4-5.6), please find a sample code attached below:
<?php
class Config implements \ArrayAccess
{
private $data = array();
public function __construct($data)
{
$this->data = $data;
}
/**
* ArrayAccess Interface
*
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
public function &offsetGet($offset)
{
return isset($this->data[$offset]) ? $this->data[$offset] : null;
}
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
}
$conf = new Config(array('a' => 'foo', 'b' => 'bar', 'c' => array('sub' => 'baz')));
$conf['c']['sub'] = 'notbaz';
EDIT 2: The solution, as Ryan pointed out, was to use ArrayObject instead (which already implements ArrayAccess, Countable and IteratorAggregate).
To apply it to a class holding an array, structure it like so:
<?php
class Config extends \ArrayObject
{
private $data = array();
public function __construct($data)
{
$this->data = $data;
parent::__construct($this->data);
}
/**
* Iterator Interface
*
*/
public function getIterator() {
return new \ArrayIterator($this->data);
}
/**
* Count Interface
*
*/
public function count()
{
return count($this->data);
}
}
I used this for my Config library libconfig which is available on Github under the MIT license.
I am not sure if this will be useful. I have noticed that the ArrayObject class is 'interesting'...
I am not sure that this is even an 'answer'. It is more an observation about this class.
It handles the 'multidimensional array' stuff correctly as standard.
You may be able to add methods to make it do more of what you wish?
<?php //
class Config extends \ArrayObject
{
// private $data = array();
public function __construct(array $data = array())
{
parent::__construct($data);
}
}
$conf = new Config(array('a' => 'foo', 'b' => 'bar', 'c' => array('sub' => 'baz')));
$conf['c']['sub'] = 'notbaz';
$conf['c']['sub2'] = 'notbaz2';
var_dump($conf, $conf['c'], $conf['c']['sub']);
unset($conf['c']['sub']);
var_dump('isset?: ', isset($conf['c']['sub']));
var_dump($conf, $conf['c'], $conf['c']['sub2']);
Output:
object(Config)[1]
public 'a' => string 'foo' (length=3)
public 'b' => string 'bar' (length=3)
public 'c' =>
array
'sub' => string 'notbaz' (length=6)
'sub2' => string 'notbaz2' (length=7)
array
'sub' => string 'notbaz' (length=6)
'sub2' => string 'notbaz2' (length=7)
string 'notbaz' (length=6)
string 'isset?: ' (length=8)
boolean false
object(Config)[1]
public 'a' => string 'foo' (length=3)
public 'b' => string 'bar' (length=3)
public 'c' =>
array
'sub2' => string 'notbaz2' (length=7)
array
'sub2' => string 'notbaz2' (length=7)
string 'notbaz2' (length=7)
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