Creating array of objects in data mapper pattern - php

EDIT: OUTPUT CODE AT BOTTOM OF QUESTION
I just posted a question, thinking my problem was the query, but it turns out it's my PHP code.
Here's the problem. I have a GoalChallenge class, which has numerous properties, one of which should be one, or an array of ProductService objects; see GoalChallenge class below (note I have stripped out the other getters and setters, and left the ones that relate to the ProductService class.
When I use GoalChallenge::findByPersonaId, a ProductService object is created and relates to the matching GoalChallenge object, but there should be 2 ProductService objects within the GoalChallenge->product_service property (the query should match 2 rows). Instead, a duplicate GoalChallenge object is created, containing the same property values for everything other than the product_service property, which contains the 2nd matching object from the query.
I need the two matching ProductService objects to be part of the same GoalChallenge object (as matched by the query) - how can I make this happen?
If you need anything else, please ask. Really appreciate any help! Code below;
GoalChallenge.class.php
<?php
class GoalChallenge
{
private $id;
private $persona_id;
private $title;
private $item_category;
private $description;
private $solution;
private $product_service;
private $research_checklist;
private $subtopics;
private $keywords;
private $status;
public function __construct(
$id = null,
$persona_id = null,
$title = null,
$item_category = null,
$description = null,
$solution = null,
ProductService $product_service = null,
$research_checklist = null,
$subtopics = null,
$keywords = null,
$status = null
) {
$this->id = $id;
$this->persona_id = $persona_id;
$this->title = $title;
$this->item_category = $item_category;
$this->description = $description;
$this->solution = $solution;
$this->product_service = $product_service;
$this->research_checklist = $research_checklist;
$this->subtopics = $subtopics;
$this->keywords = $keywords;
$this->status = $status;
}
public function getProductService()
{
return $this->product_service;
}
public function setProductService(ProductService $product_service)
{
$this->product_service = $product_service;
}
}
And my GoalChallengeMapper.class.php;
class GoalChallengeMapper
{
protected $dblayer;
public function __construct(PDO $dblayer)
{
$this->dblayer = $dblayer;
}
public function saveField($id, $field, $data)
{
try {
$this->dblayer->beginTransaction();
$stmt = $this->dblayer->prepare("UPDATE goals_challenges SET $field = :data WHERE id = :id");
$stmt->bindParam(':id', $id);
$stmt->bindParam(':data', $data);
$stmt->execute();
$this->dblayer->commit();
return $stmt->rowCount();
} catch(PDOException $e) {
$this->dblayer->rollBack();
echo $e->getMessage();
exit;
}
}
public function findByPersonaId($persona_id)
{
try {
$this->dblayer->beginTransaction();
$stmt = $this->dblayer->prepare("SELECT goals_challenges.*, products_services.id as psid, products_services.url, products_services.feature_benefit from goals_challenges LEFT JOIN products_services ON goals_challenges.id = products_services.goal_challenge_id WHERE goals_challenges.persona_id = :persona_id");
$stmt->bindParam(':persona_id', $persona_id);
$stmt->execute();
$this->dblayer->commit();
$result_set = array();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$result_set[] = $this->mapObject($row);
}
return $result_set;
} catch (PDOException $e) {
$this->dblayer->rollBack();
echo $e->getMessage();
exit;
}
}
public function mapObject(array $row)
{
$entry = new GoalChallenge();
$entry->setId($row['id']);
$entry->setPersonaId($row['persona_id']);
$entry->setTitle($row['title']);
$entry->setItemCategory($row['item_category']);
$entry->setDescription($row['description']);
$entry->setSolution($row['solution']);
$entry->setProductService(new ProductService($row['psid'], $row['id'], $row['url'], explode(',', $row['feature_benefit'])));
$entry->SetResearchChecklist($row['research_checklist']);
$entry->setSubtopics($row['subtopics']);
$entry->setKeywords($row['keywords']);
$entry->setStatus($row['status']);
return $entry;
}
}
And finally, my ProductService class (minus getters and setters)
class ProductService
{
private $id;
private $goal_challenge_id;
private $url;
private $feature_benefit = [];
public function __construct($id = null, $goal_challenge_id = null, $url = null, array $feature_benefit = null)
{
$this->id = $id;
$this->goal_challenge_id = $goal_challenge_id;
$this->url = $url;
$this->feature_benefit = $feature_benefit;
}
}
THIS IS THE OUTPUT
GoalChallenge Object
(
[id:GoalChallenge:private] => 173
[persona_id:GoalChallenge:private] => 14
[title:GoalChallenge:private] => Lead Gen
[item_category:GoalChallenge:private] => Business Challenge
[description:GoalChallenge:private] =>
[solution:GoalChallenge:private] => Advertising
[product_service:GoalChallenge:private] => ProductService Object
(
[id:ProductService:private] => 1
[goal_challenge_id:ProductService:private] => 173
[url:ProductService:private] => www.google.com
[feature_benefit:ProductService:private] => Array
(
[0] => good for testing
[1] => mobile
)
)
[research_checklist:GoalChallenge:private] => 0,0,0,0,0,0
[subtopics:GoalChallenge:private] =>
[keywords:GoalChallenge:private] => ,,,,
[status:GoalChallenge:private] => 1
)
GoalChallenge Object
(
[id:GoalChallenge:private] => 173
[persona_id:GoalChallenge:private] => 14
[title:GoalChallenge:private] => Lead Gen
[item_category:GoalChallenge:private] => Business Challenge
[description:GoalChallenge:private] =>
[solution:GoalChallenge:private] => Advertising
[product_service:GoalChallenge:private] => ProductService Object
(
[id:ProductService:private] => 3
[goal_challenge_id:ProductService:private] => 173
[url:ProductService:private] => www.test.com
[feature_benefit:ProductService:private] => Array
(
[0] => good for searching
[1] => well known
)
)
[research_checklist:GoalChallenge:private] => 0,0,0,0,0,0
[subtopics:GoalChallenge:private] =>
[keywords:GoalChallenge:private] => ,,,,
[status:GoalChallenge:private] => 1
)
mysql> SELECT goals_challenges.*, products_services.id as psid, products_services.url, products_services.feature_benefit FROM goals_challenges LEFT JOIN products_services ON goals_challenges.id = products_services.goal_challenge_id WHERE goals_challenges.persona_id = 14;
+-----+------------+----------+--------------------+-------------+-------------+-----------------+--------------------+-----------+----------+--------+------+----------------+--------------------------------+
| id | persona_id | title | item_category | description | solution | product_service | research_checklist | subtopics | keywords | status | psid | url | feature_benefit |
+-----+------------+----------+--------------------+-------------+-------------+-----------------+--------------------+-----------+----------+--------+------+----------------+--------------------------------+
| 173 | 14 | Lead Gen | Business Challenge | | Advertising | NULL | 0,0,0,0,0,0 | NULL | ,,,, | 1 | 1 | www.google.com | good for testing, mobile |
| 173 | 14 | Lead Gen | Business Challenge | | Advertising | NULL | 0,0,0,0,0,0 | NULL | ,,,, | 1 | 3 | www.test.com | good for searching, well known |
+-----+------------+----------+--------------------+-------------+-------------+-----------------+--------------------+-----------+----------+--------+------+----------------+--------------------------------+
2 rows in set (0.00 sec)
print_r($goals_challenges)
Array
(
[173] => Array
(
[id] => 173
[persona_id] => 14
[title] => Lead Gen
[item_category] => Business Challenge
[description] =>
[solution] => Advertising
[research_checklist] => 0,0,0,0,0,0
[subtopics] =>
[keywords] => ,,,,
[status] => 1
[psid] => 1
[url] => www.google.com
[feature_benefit] => good for testing, mobile
[product_services] => Array
(
[0] => Array
(
[0] => 1
[1] => www.google.com
[2] => good for testing, mobile
)
[1] => Array
(
[0] => 3
[1] => www.test.com
[2] => good for searching, well known
)
)
)
)

As suspected, the JOIN query's result set requires a little more logic to format the way you want than you have given it. A SQL result set is always a 2 dimensional structure, even when the data it contains have more complicated relationships (like your one-to-many relationship).
There are a few ways to approach this, and the one I think will be closest to your existing pattern is to change the way you fetch rows a little. Instead of fetching a row then immediately mapping it, build some logic into the fetch loop to create the nested structure your join expresses, wherein ProductService is an array of one or more objects. Then you'll be able to modify the mapObject() method to handle an array of nested ProductService objects.
So instead of mapping as you fetch, create an array onto which the fetched rows are appended. On each iteration, you must check if the common values (those of GoalChallenge) have changed. If not, you continue building an array for ProductService. If they have changed (like if your query returns more than one different GoalChallenge) you start a new outer structure.
$result_set = array();
// Temp variable to remember what goals_challenges.id is being grouped
$current_id = null;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// Create a new structure if the id changed
if($row['id'] !== $current_id) {
$current_id = $row['id'];
// Store a new row for goal_challenges, holding all
// the common columns in its outer structure
$goal_challenges[$row['id']] = $row;
// and it has a sub-array for product services
$goal_challenges[$row['id']]['product_servies'] = array();
}
// Append the product_services columns as an array onto the subarray
$goal_challenges[$row['id']]['product_services'][] = array('psid'=>$row['psid'], 'url'=>$row['url'], 'feature_benefit'=>$row['feature_benefit']);
}
// Now you can pass each row of the $goal_challenges array
// to mapObject. There should be only one row, but if you
// use the same pattern for queries that return many rows it
// will work without much modification
$result_set = array();
foreach ($goal_challenges as $gc) {
$result_set[] = $this->mapObject($gc);
}
// Return the array of results (which probably has only one element)
return $result_set;
Okay, that should fix the fetch pattern to do what you need. The other issue is to make the mapObject() method handle the inner array of product services. That's easy enough with a loop.
public function mapObject(array $row)
{
$entry = new GoalChallenge();
$entry->setId($row['id']);
$entry->setPersonaId($row['persona_id']);
$entry->setTitle($row['title']);
$entry->setItemCategory($row['item_category']);
$entry->setDescription($row['description']);
$entry->setSolution($row['solution']);
$entry->SetResearchChecklist($row['research_checklist']);
$entry->setSubtopics($row['subtopics']);
$entry->setKeywords($row['keywords']);
$entry->setStatus($row['status']);
// Create ProductService objects for each item in the sub-array
foreach ($row['product_services'] as $ps) {
$entry->setProductService(new ProductService($ps['psid'], $row['id'], $ps['url'], explode(',', $ps['feature_benefit'])));
}
return $entry;
}
And finally, make the setProductService() method append to an array instead of setting a single property:
public function setProductService(ProductService $product_service)
{
// Append onto an array
$this->product_service[] = $product_service;
}
In the GoalChallenge::__construct() parameters, make it accept and default an array instead of a single ProductService object, changing to $product_service = array()
So this is all sort of complicated, and it speaks to why pre-built ORM libraries like Doctrine are commonly used. This logic is abstracted away for you in an easily reusable way. PDO does have a FETCH_GROUP methodology, but it is meant to group just one column (like the id) as an outer array key and all other columns as sub-arrays. Your situation is such that most columns belong at an outer level, with only those relevant to the joined ProductService as an inner sub-array, so that doesn't really work.

Related

Optional setter

How can I set a specific setter inside of a child class to be optional
I have a web page build with PHP that prints a recursive objects array, I have a parent class called Person and a child class called Teachers.
Now the teachers can also teach another teacher, I have divided my teachers with the following types "Master, Senior, Junior", and as a matter of fact I already solve that problem, I can see an array of objects within an array of objects, something like this:
Array
(
[0] => Teacher Object
(
[subject:Teacher:private] => Matematicas
[teacherType:Teacher:private] => Master
[teachers:Teacher:private] => Array
(
[0] => Teacher Object
(
[subject:Teacher:private] => Matematicas 2
[teacherType:Teacher:private] => Senior
[teachers:Teacher:private] => Array
(
[0] => Teacher Object
(
[subject:Teacher:private] => Ciancias 1
[teacherType:Teacher:private] => Junior
[teachers:Teacher:private] => Array
(
)
[id:Person:private] => 7
[name:Person:private] => Pedro
[nickname:Person:private] => PedroBoy
)
)
I imagine you get the idea, my problem is that the junior teacher does not receive any teachers array, because he is a jnuior teacher and he can't teach teachers yet. But if I write nothing on it, it will launch an error saying that the teachers array of junior teachers is undifined and it will assign an empty teachers array object to it.
Is there any way to nake that specific setter optional?.
Here is my PHP Code:
class Teacher extends Person{
private $subject = '';
private $teacherType = '';
private $teachers = array();
public function __construct($data) {
parent::__construct($data);
$this->setSubject($data["subject"]);
//OPTIONAL
$this->setTeachers($data["teachers"]);
$this->setTeacherType($data["type"]);
}
?>
Maybe a simple if statement?
class Teacher extends Person
{
private $subject = '';
private $teacherType = '';
private $teachers = array();
public function __construct($data)
{
parent::__construct($data);
$this->setSubject($data["subject"]);
if (isset($data['teachers']))
$this->setTeachers($data["teachers"]);
$this->setTeacherType($data["type"]);
}

PHP Array Check the attribute, return value based on attribute

I am trying to return pull a value based on an attribute from an array, and it seems straight forward enough but I can't seem to nail down the correct way to accomplish this.
Here is the array I am trying to pull from:
[1] => InfoOptions Object
(
[description] => INFO
[optSequence] => 2
[eqpObject] => CUSTOMER NTWK ENG
[attribute] =>
[eqpValue] =>
[dlrSequence] => 10
)
[2] => InfoOptions Object
(
[description] =>
[optSequence] => 3
[eqpObject] => CUSTOMER TEST
[attribute] => CUSTOMER
[eqpValue] => Jon Doe
[dlrSequence] => 10
)
Here is what I have so far:
if (is_array($provisionCVResult->path->infoOptions-_InfoOptions)) {
foreach ($provisionCVResult->path->infoOptions ->InfoOptions as $cv_obj) {
$CVA = array();
$result = null;
foreach ($CV_obj as $value) {
if($value['attribute'] == 'CUSTOMER') {
$CVA["eqpValue"] = $cv_obj->eqpValue;
break;
}
}
$this->cvArrayDataList[] = $CVA;
}
}
Where am I going wrong?
If $provisionCVResult->path->InfoOptions is an array, it does not make sense to write $provisionCVResult->path->InfoOptions ->InfoOptions in the foreach
EDIT: I red in the comments that the array is $provisionCVResult->path->InfoOptions->InfoOptions
PHP is case sensitive so $cv_obj and $CV_obj are two different variables
The second foreach is not needed
So, assuming $provisionCVResult->path->InfoOptions->InfoOptions is returning an array of InfoOptions Object, I think you should do something like this:
if (is_array($provisionCVResult->path->InfoOptions->InfoOptions))
{
$result = null;
foreach($provisionCVResult->path->InfoOptions->InfoOptions as $cv_obj)
{
if($cv_obj->attribute == 'CUSTOMER')
{
$this->cvArrayDataList[] = array("eqpValue" => $cv_obj->eqpValue);
}
}
}
Having a quick look, try changing
$value['attribute'] == 'CUSTOMER'
To
$value->attribute == 'CUSTOMER'
As the element is an "InfoOptions object" and not an array.
Note I would also recommend using strict comparison, e.g '===' instead of '=='.

Use an array as an index to access a multidimensional array in PHP

I am having a problem accessing an object in a multidimensional array.
THE CONTEXT
Basically, I have an object (category) which consists of Name, ID, ParentID and more. I also have an array ultimateArray which is multidimentional.
For a given category, I am writing a function (getPath()) that will return an array of ids. For example, an object named Granny Smith has a parentID of 406 and is therefore a child of Food(5) -> Fruits(101) -> Apples(406). The function will return either an array or string of the ids of the objects parents. In the above example this would be: 5 -> 101 -> 406 or ["5"]["101"]["406"] or [5][101][406]. Food is a root category!
THE PROBLEM
What I need to do is use whatever is returned from getPath() to access the category id 406 (Apples) so that I can add the object Granny Smith to the children of Apples.
The function $path = $this->getPath('406'); is adaptable. I am just having difficulty using what is returned in the following line:
$this->ultimate[$path]['Children'][]= $category;
It works when I hard code in:
$this->ultimate["5"]["101"]["406"]['Children'][]= $category;
//or
$this->ultimate[5][101][406]['Children'][]= $category;
Any help is much appreciated.
Suppose you have the array like below
<?php
$a = array(
12 => array(
65 => array(
90 => array(
'Children' => array()
)
)
)
);
$param = array(12, 65, 90); // your function should return values like this
$x =& $a; //we referencing / aliasing variable a to x
foreach($param as $p){
$x =& $x[$p]; //we step by step going into it
}
$x['Children'] = 'asdasdasdasdas';
print_r($a);
?>`
You can try referencing or aliasing it
http://www.php.net/manual/en/language.references.whatdo.php
The idea is to make a variable which is an alias of your array and going deep from the variable since we can't directly assigning multidimensional key from string (AFAIK)
output
Array
(
[12] => Array
(
[65] => Array
(
[90] => Array
(
[Children] => asdasdasdasdas
)
)
)
)
You can use a recursive function to access the members. This returns NULL if the keys do not correspond with the path, but you could also throw errors or exceptions there. Also please note that i have added "Children" to the path. I have done this so you can use this generically. I just did an edit to show you how to do it without children in the path.
<?php
$array = array(1 => array(2 => array(3 => array("Children" => array("this", "are", "my", "children")))));
$path = array(1, 2, 3, "Children");
$pathWithoutChildren = array(1, 2, 3);
function getMultiArrayValueByPath($array, $path) {
$key = array_shift($path);
if (array_key_exists($key, $array) == false) {
// requested key does not exist, in this example, just return null
return null;
}
if (count($path) > 0) {
return getMultiArrayValueByPath($array[$key], $path);
}
else {
return $array[$key];
}
}
var_dump(getMultiArrayValueByPath($array, $path));
$results = getMultiArrayValueByPath($array, $pathWithoutChildren);
var_dump($results['Children']);

Get nr of items in an array

I have an array that has this format:
array(
info(
Date-today => '09/04/2013'
)
clients(
id => 1001,
name => Fred
)
more_info(
weather-today => "cloudy"
)
)
But sometimes, I receive the data with more clients:
array(
info(
Date-today => '08/04/2013'
)
clients(
0(
id => 1001,
name => Fred
),
1(
id => 1045,
name => Fritz
)
)
more_info(
weather-today => "Sunny"
)
)
I want to count how many cients I got returned, because I need to access the client-data differently if there is only one or more then one.
I tried several "count()" options, such as:
count(array['client'])
but of course if there is only 1 client, it doesn't return 1, it returns 2 (since there are 2 items of client-data in the array).
Any tips?
You would have to find out whether $array['clients'] has only numeric indices:
$size = count($array['clients'];
if (count(array_filter(array_keys($array['clients']), 'is_int')) == $size) {
return $size;
} else {
return 1;
}
Alternatively, use the existence of one numeric index as the condition:
if (isset($array['clients'][0])) {
return count($array['clients']);
} else {
return 1;
}
If you have an array-like structure (as the collection of clients in your example), you should always index the entries, even if there is only one entry.
That way you don't end up with the problem you described.
EDIT:
Didn't see your comment.
You can first check if $array['clients'] has any content (via count()).
-> If it hasn't, cnt = 0.
Then check if $array['clients']['0'] is defined.
-> If it isn't defined, cnt = 1.
-> If it is, cnt = count().

How do I declare 'sub-objects' in PHP

I'm relatively new to OOP in PHP, and I'm not sure if what I'm trying to do is possible or recommended. In any case, I can't figure it out. I'd appreciate any pointers to tutorials or documents which might help - I'm not expecting a full-blown answer here.
I have a system in which each user has a number of 'Libraries'. Each Library contains a number of 'Elements'.
DB set up is as follows:
user_libraries
- id (unique)
- user_id (identifies user)
- name (just a string)
elements
- id (unique)
- content (a string)
library_elements
- id (unique)
- library_id
- element_id
where library_id is the id from user_libraries, and element_id is that from elements.
I want to be able to access a given user's library, and their elements.
I've set up the library class, and can use it to retrieve the list of libraries (or a sub-list).
I do this like this:
$mylibraryset = new LibrarySet();
$mylibraryset->getMyLibraries();
which gives (when I use print_r):
LibrarySetObject (
[user_id] => 105
[data_array] => Array (
[0] => Array (
[id] => 1
[user_id] => 105
[type] => 1
[name] => My Text Library
)
[1] => Array (
[id] => 2
[user_id] => 105
[type] => 2
[name] => Quotes
)
)
)
Now, what I'd like to be able to do is for each of those libraries (the elements in data_array), to retrieve all the elements.
The best idea I've had so far is to do something like:
foreach($mylibrary->data_array as $library) {
$sublibrary = new Library();
$sublibrary -> getAllElements();
}
where Sublibrary is another class which has the function getAllElements. I can't quite get it to work though, and I'm not sure I'm on the right lines...
Is there a way that I can then end up being able to do something like this:
$mylibrary->sublibraries[0]->element[0]
to retrieve a specific element?
As I say, I don't expect a full-blown explanation here - just pointers to get me started.
<?php
class Library {
public $element;
public $data;
public function __construct($sublibrary) {
$this->data = $sublibrary;
}
public function getAllElements() {
// populate $this->element using $this->data
}
}
class LibrarySet {
public $user_id;
public $data_array;
public $sublibraries;
public function getMyLibraries() {
// populate $this->data_array
$this->sublibraries = Array();
foreach($this->data_array as $index => $sublibrary) {
$this->sublibraries[$index] = new Library($sublibrary);
$this->sublibraries[$index]->getAllElements();
}
}
}
$mylibraryset = new LibrarySet();
$mylibraryset->getMyLibraries();
$mylibraryset->sublibraries[0]->element[0]
?>

Categories