Categorise objects in a associative array grouped by a object property - php

Let me first explain my problem.
I have a simple value object Poi. Private properties and getters/setters are left out for sake of simplicity in this example.
class Poi implements JsonSerializable
{
public $latitude;
public $longitude;
public $category;
public function __construct($category, $lat, $long)
{
$this->category = $category;
$this->latitude = $lat;
$this->longitude = $long;
}
public function jsonSerialize()
{
return array(
'lat' => $this->latitude,
'lng' => $this->longitude,
);
}
}
Some dataproviders are responsible for returning a array of Poi's. i.e.
class PoiProvider1
{
public function getPois()
{
return array(
new Poi('airport', 10, 10),
new Poi('airport', 10, 15),
new Poi('restaurant', 30, 30),
)
}
}
class PoiProvider2
{
public function getPois()
{
return array(
new Poi('hotel', 20, 20),
new Poi('airport', 30, 30),
)
}
}
Now I want an array structured a follows, which I can json_encode()
array(
'airport' => array(
new Poi('airport', 10, 10),
new Poi('airport', 10, 15),
new Poi('airport', 30, 30),
),
'restaurant' => array(
new Poi('restaurant', 30, 30),
),
'hotel' => array(
new Poi('hotel', 20, 20),
)
);
Which after a json_encode will end up in the following structure:
{
"airport":[
{"lat":10,"lng":10},
{"lat":10,"lng":15},
{"lat":30,"lng":30}
],
"restaurant":[
{"lat":30,"lng":30}
],
"hotel":[
{"lat":20,"lng":20}
]
}
I can create such a structure using array_merge and some array copying, like this:
$provider1 = new PoiProvider1();
$provider2 = new PoiProvider2();
$pois = array_merge($provider1->getPois(), $provider2->getPois());
$poisNew = array();
foreach ($pois as $poi)
{
$poisNew[$poi->category][] = $poi;
}
Obviously this is memory consuming and slow when dealing with lots of poi's.
There must be some nicer and faster way (i.e. using Iterators), but I'm not sure how to approach this. Could anyone give me some pointers how to proceed?

For speeding things up eliminate post processing.
ELIMINATION OF POST PROCESSING:
You can use another global or class static container for pois which auto indexes and stores them during construction like this:
class Poi implements JsonSerializable {
public static $json; // this is our new hero.
public $latitude;
public $longitude;
public $category;
public function __construct($category, $lat, $long){
$this->category = $category;
$this->latitude = $lat;
$this->longitude = $long;
array_push(self::$json[$category], $this); // this does the trick...
}
public function jsonSerialize()
{
return array(
'lat' => $this->latitude,
'lng' => $this->longitude,
);
}
}
Poi::$json = array();
Now at each poi creation, the poi instances are stored into Poi::$json exactly in the form you need.
GENERATING POI'S:
If you do not need to process poi's, this will simplify your providers also:
class PoiProvider1 {
public function getPois(){ // no need to return if they are just for json.
new Poi('airport', 10, 10); // because each instance will be autoMagically
new Poi('airport', 10, 15); // added to Poi::$json
new Poi('restaurant', 30, 30);
}
}
But if you do something else on poi instances do not use the code above...
ENCODING:
Since $json is an associated array you can get the required output effect by using
json_encode((object)Poi::$json);
The drawback is poi's will stick in memory and survive gc because they are reachable via Poi::$json. So after you json_encode set
Poi::$json = null;
I hope this helps.

Related

Set object properties at constructor call time (PHP)

I wonder if it's possible to achieve similar functionality to C#'s compact instantiation syntax:
itemView.Question = new ItemViewQuestion()
{
AnswersJSON = itemView.Answer.ToJSONString(),
Modified = DateTime.Now,
ModifiedBy = User.Identity.Name
};
I wish to be able to create an object of arbitrary class passing their properties without having to set up constructor code for these properties.
To put up another example, this can be done with stdClass like this:
(object) ["name" => "X", "age" => 30]
Type juggling does not work for custom classes, however.
There is no such functionality natively in PHP, unfortunately.
But you can create a class in your project and extend it in the classes you wish to instantiate without a constructor. Something like this:
<?php
class Fillable{
public static function fill($props)
{
$cls = new static;
foreach($props as $key=>$value){
if (property_exists(static::class,$key)){
$cls->$key = $value;
}
}
return $cls;
}
}
class Vegetable extends Fillable
{
public $edible;
public $color;
}
$veg = Vegetable::fill([
'edible' => true,
'color' => 'green',
'name' => 'potato' //Will not get set as it's not a property of Vegetable. (you could also throw an error/warning here)
]);
var_dump($veg);
Checkout this fiddle for the working example
This is valid in PHP though:
<?php
class Demo {
public function getA() {
return $this->Options['A'];
}
}
$D = new Demo();
$D->Options = Array(
'A' => '1',
'B' => '2',
'C' => '3'
);
var_dump($D->getA());
Or, something like this:
<?php
class Demo {
public function __construct($Options) {
$this->Options = $Options;
}
public function getA() {
return $this->Options['A'];
}
}
$D = new Demo(Array(
'A' => '1',
'B' => '2',
'C' => '3'
));
var_dump($D->getA());
Or even this:
<?php
class Demo {
public function __construct($Options) {
foreach ($Options as $key=>$value) $this->$key = $value;
}
public function getA() {
return $this->A;
}
}
$D = new Demo(Array(
'A' => '1',
'B' => '2',
'C' => '3'
));
var_dump($D->getA());
I guess it really depends what are you trying to achieve? You said you do not want to use magic functions or setters, but is there more to it?
Obviously php doesn't have this. Somewhere a function is required. I did an implementation using a trait which is close.
<?php
Trait Init {
public function init($arr) {
$vars = get_object_vars($this);
foreach($arr as $k => $v) {
if ( array_key_exists($k, $vars) ) $this->$k = $v;
}
}
}
class Demo {
use Init;
public $answersJSON;
public $modified;
public $modifiedBy;
}
$obj = new Demo();
$obj->init(['modified' => 'now']);
print_r($obj);

Laravel late static binding as static::whereIn

Ok, I read and feel I have some understandings about PHP late static binding for methods and variables. But from line 28 in this code on Laravel 5, it uses with whereIn which is a Laravel Collection method. I don't understand what's going on here, static::whereIn(). Where is the collection so that you can use whereIn().
/**
* Add any tags needed from the list
*
* #param array $tags List of tags to check/add
*/
public static function addNeededTags(array $tags)
{
if (count($tags) === 0) {
return;
}
$found = static::whereIn('tag', $tags)->lists('tag')->all();
foreach (array_diff($tags, $found) as $tag) {
static::create([
'tag' => $tag,
'title' => $tag,
'subtitle' => 'Subtitle for '.$tag,
'page_image' => '',
'meta_description' => '',
'reverse_direction' => false,
]);
}
}
An example from php.net:
class a
{
static protected $test = "class a";
public function static_test()
{
echo static::$test; // Results class b
echo self::$test; // Results class a
}
}
class b extends a
{
static protected $test = "class b";
}
$obj = new b();
$obj->static_test();
So static::whereIn() refers to Tag::whereIn(). Same goes for static::create()

Updating object in specified array also updates arrays containing objects of the same ID

I'm sorry if the title was confusing or poorly worded, I'm relatively new to programming terminology and couldn't think of a better description.
Here is a basic structure of the script, with irrelevant functions/variables removed:
class User {
public $username;
public $binders;
function __construct($username, $binders) {
$this->username = $username;
$this->binders = $binders;
}
}
The variable $binders is an array of Binder objects:
class Binder {
public $name;
public $contents;
function __construct($name, $contents = array()) {
$this->name = $name;
$this->contents = $contents;
}
}
The variable $contents is an array of Card objects:
class Card {
public $id;
public $quantity;
function __construct($id, $quantity = 0) {
$this->id = $id;
$this->quantity = $quantity;
}
}
So a simple structure could be:
$users = array(
'TestName' => new User('TestName', array(
'BinderName1' => new Binder('BinderName1', array(
'CardID_1' => new Card('1', '20'),
'CardID_3' => new Card('3', '10'),
),
'BinderName2' => new Binder('BinderName2', array(
'CardID_1' => new Card('1', '7')
)
)
);
I'm currently attempting to update the quantity of the card with id of 1 in the binder BinderName1, using the following code:
$users['TestName']->binders['BinderName1']->contents['CardID_1']->quantity = 5;
However, when I use print_r($users) after applying the change, the response shows that the quantity of CardID_1 updates in both BinderName1 and BinderName2. Am I applying the change incorrectly?
Edit: it appears as though this only occurs when I create a clone of an object, eg:
$card = $users['TestName']->binders['BinderName1']->contents['CardID_1'];
$cloneCard = clone $card;
array_push($users['TestName']->binders['BinderName1']->contents, $cloneCard);

Accessing a specific instance of a class

Im pretty new to OOP PHP, and so I'm trying to learn.
I have a class called "Awesome_Car" that i define like so
class Awesome_Car {
public $attributes;
public $name;
function __construct($name, $atts) {
$this->name = $name;
$this->attributes = $atts;
}
} // end of class
And I instantiate this class x times somewhere in the code:
$car1 = new Awesome_Car('ford', array( 'color'=>'blue', 'seats' => 6 ));
$car2 = new Awesome_Car('bmw', array( 'color'=>'green', 'seats' => 5 ));
Now I would like to make a normal function that allows me to get - and manipulate - a specific instance of that class by name. Something like
function get_specific_car_instance($name) {
//some code that retrives a specific instance by name
//do stuff with instance
//return instance
}
I have seen people storing each instance in a global variable as an array of object, but I've also read that global variables are considered bad practice? And I do find them a bit annoying to work with as well.
What would be a better way of doing this? preferably an OOP approach?
If you are creating the instances dynamically, then storing them in an array is the generally accepted way. It doesn't have to be global however.
$cars = array();
$cars['ford'] = new Awesome_Car('ford', array( 'color'=>'blue', 'seats' => 6 ));
$cars['bmw'] = new Awesome_Car('bmw', array( 'color'=>'green', 'seats' => 5 ));
$ford = $cars['ford'];
This ofcourse can be abstracted by a function such as:
function get_car(&$cars, $name) {
if (! isset($cars[$name])) {
throw new \InvalidArgumentException('Car not found');
}
return $cars[$name];
}
$ford = get_car($cars, 'ford');
Or with more advanced container classes such as:
// Requires doctrine/common
use Doctrine\Common\Collections\ArrayCollection;
$cars = new ArrayCollection();
$cars->set('ford', new Awesome_Car('ford', array( 'color'=>'blue', 'seats' => 6 )));
$cars->set('bmw', new Awesome_Car('bmw', array( 'color'=>'green', 'seats' => 5 )));
$ford = $cars->get('ford');
How you store them for later use depends quite a bit on how you are dynamically creating them though.
You can create your own repository; a class whose only purpose is to create, track, and recover these cars. That'll allow you to avoid using a global variable. Of course, you'll need a way to access the repository then. You could always make it Static, but then you're basically back to a global in a way.
class CarRepository {
private $cars = array();
public function makeCar( $name, $atts ) {
$this->cars[] = new Awesome_Car($name, $atts);
}
public function findByName($name) {
foreach( $this->cars as $car ) {
if( $car->name == $name ) {
return $car;
}
}
}
}
// you'll need a way to obtain the repository to find cars; but it means you can have different repositories in your code
$repo = new CarRepository;
$repo->makeCar( 'ford', array( 'color'=>'blue', 'seats' => 6 ) );
$repo->findByName( 'ford' );
Or a fully static version:
class CarRepository {
private static $cars = array();
public static function makeCar( $name, $atts ) {
self::$cars[] = new Awesome_Car($name, $atts);
}
public static function findByName($name) {
foreach( self::$cars as $car ) {
if( $car->name == $name ) {
return $car;
}
}
}
}
// you can access this from ANYWHERE, but you can only ever have a single repository
CarRepository::makeCar( 'ford', array( 'color'=>'blue', 'seats' => 6 ) );
CarRepository::findByName( 'ford' );

phpunit dbunit #dataProvider doesn't work

I spent a lot of time by searching where is the problem, but i haven't find anything.
It sais "testAdd caused an ERROR: Missing argument". Simply the dataProvider isn't executed, when I run the test. I tried to put die() into the dataProvider and it hasn't died.
This is my code:
class LabelEntityModelTest extends PHPUnit_Extensions_Database_TestCase
{
private static $connection = NULL;
/**
* #var \CXNS\DB\Connections\Connection
*/
private static $appConnection;
private static $table;
public function __construct()
{
if (self::$connection) {
return;
}
$pdo = new \PDO($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']);
self::$appConnection = new \CXNS\DB\Connections\Connection(array("prefix" => "test_", "driver" => "pdo", "resource" => $pdo));
self::$appConnection->connect();
self::$connection = $this->createDefaultDBConnection($pdo, 'mysql');
self::$table = $this->createXMLDataSet(__DIR__ . '/fixtures/tables.xml');
}
protected function getDataSet()
{
return self::$table;
}
public function getConnection()
{
return self::$connection;
}
public function getAppConnection()
{
return self::$appConnection;
}
/**
* #group onlyThis
* #dataProvider providerAdd
*/
public function testAdd($labelId, $entityId)
{
$lem = new \appLibs\Labels\LabelEntityModel($this->getAppConnection(), "contacts");
$lem->add($labelId, $entityId);
$count = $this->getAppConnection()
->select("id")
->from("label_relationships")
->where("label_id = %i", $labelId)
->where("table_ref_id = %i", $entityId)
->count();
$this->assertEquals(1, $count, "insert failed");
}
public function providerAdd()
{
return array(
array(2, 3),
array(3, 4),
array(3, 4),
array(3, 4),
array(3, 4),
array(3, 4),
array(5, 7)
);
}
}
Thank you for your help.
You should never overwrite TestCase constructor.
PhpUnit has a specialized methods for initialization purposes called setUp and setUpBeforeClass, so I strongly suggest you to use that.
I´m pretty sure this is the cause of your problem.

Categories