I'm looking for a way to represent a family tree in PHP. This means that children will need to inherit from two (or more) parents.
Here are the requirements:
1, 2, or more parents
Bonus points if I can attach metadata like a last name or relationship status
Here is my non-working attempt (no arrays as keys, sadly):
$tree = array(
'uncle' => false, // no children
array('mom', 'dad') => array(
'me' => false,
array('brother', 'sister-in-law') => array(
'niece' => false
)
)
);
The question is, how can I represent a family tree with these requirements?
You won't be able to do it all in a single array() like that. You can set up trees like that, but setting up more complicated graphs with multiple parents and other relations requires multiple lines of code.
It'll help a lot if you throw some OO at this. Let's create a Person class to help manage the relationships. Fundamentally, we've got people and their relationships with other people, so we'll start there.
Person class
What I imagine is each person having an array of relationships. This array will be indexed first by the type of relationship, for example "parents" or "children". Each entry will then be an array of Persons.
class Person {
var $name, $relations;
function __construct($name) {
$this->name = $name;
$this->relations = array();
}
function addRelation($type, $person) {
if (!isset($this->relations[$type])) {
$this->relations[$type] = array();
}
$this->relations[$type][] = $person;
}
// Looks up multiple relations, for example "parents".
function getRelations($type) {
if (!isset($this->relations[$type])) {
return array();
}
return $this->relations[$type];
}
// Looks up a single relation, for example "spouse".
function getRelation($type) {
$relations = $this->getRelations($type);
return empty($relations) ? null : $relations[0];
}
function __toString() {
return $this->name;
}
Friendly adders and getters
With the above as a foundation we can then add some friendlier-named methods. For illustration we'll handle the parent/child relation and spouses.
function addParents($mom, $dad) {
$mom->addChild($this);
$dad->addChild($this);
}
function addChild($child) {
$this ->addRelation('children', $child);
$child->addRelation('parents', $this);
}
function addSpouse($spouse) {
$this ->addRelation('spouse', $spouse);
$spouse->addRelation('spouse', $this);
}
function getParents () { return $this->getRelations('parents'); }
function getChildren() { return $this->getRelations('children'); }
function getSpouse () { return $this->getRelation ('spouse'); }
}
Creating people
Now we can create a couple of people and setup their relationships. Let's try Billy and his parents John and Jane.
$john = new Person('John');
$jane = new Person('Jane');
$billy = new Person('Billy');
$john ->addSpouse ($jane);
$billy->addParents($jane, $john);
And we can check out their relationships like so:
echo "John is married to " . $john->getSpouse() . ".\n";
echo "Billy's parents are " . implode(" and ", $billy->getParents()) . ".\n";
Output:
John is married to Jane.
Billy's parents are Jane and John.
Display family tree
We can traverse the graph recursively if it grows bigger. Here's an example tree-walking function which displays a rudimentary family tree. I've added Sara, her husband Mike, and their son Bobby to the mix.
$john = new Person('John');
$jane = new Person('Jane');
$sara = new Person('Sara');
$mike = new Person('Mike');
$bobby = new Person('Bobby');
$billy = new Person('Billy');
$john ->addSpouse ($jane);
$sara ->addParents($jane, $john);
$sara ->addSpouse ($mike);
$bobby->addParents($sara, $mike);
$billy->addParents($jane, $john);
function displayFamilyTree($root, $prefix = "") {
$parents = array($root);
if ($root->getSpouse() != null) {
$parents[] = $root->getSpouse();
}
echo $prefix . implode(" & ", $parents) . "\n";
foreach ($root->getChildren() as $child) {
displayFamilyTree($child, "....$prefix");
}
}
displayFamilyTree($john);
Output:
John & Jane
....Sara & Mike
........Bobby
....Billy
Edit: Here's #Wrikken's comment below, reproduced for readability:
About it indeed. IMHO add a from-until date to every relationship though (possibly NULL for no end). Divorces happen, as do adoptions, etc. Also: I'd add a reverse types & 'ping-back' to the addRelation() function:
function addRelation($type, $person, $reverseType, $pingback = false) {
if (!isset($this->relations[$type])) {
$this->relations[$type] = array();
}
if (!in_array($person, $this->relations[$type], true)) {
$this->relations[$type][] = $person;
}
if (!$pingback) {
$person->addRelation($reverseType, $this, $type, true);
}
}
GEDCOM is an open specification for exchanging genealogical data between different genealogy software. A GEDCOM file is plain text (usually either ANSEL or ASCII) containing genealogical information about individuals, and meta data linking these records together. Most genealogy software supports importing from and/or exporting to GEDCOM format.
A major advantage of using GEDCOM, would be that you could use desktop programs like Aldfaer (Dutch only), Gramps or Legacy Family Tree as well as online tools like Geneanet to build or modify your family tree and compare it with the family trees of others.
Another major advantage of using the GEDCOM format, is that there are libraries in multiple programming language at your disposal to save and load your data. Examples of PHP libraries would be GEDCOM Import/Export-Filter, GenealogyGedcom or PHP GEDCOM.
Using PHP GEDCOM, reading and parsing a GEDCOM file would be as simple as this:
$parser = new \PhpGedcom\Parser();
$gedcom = $parser->parse('gedcom.ged');
A major disadvantage of using GEDCOM, is that GEDCOM's data format is built around the nuclear family, which means that the standard is limited in its support of non-traditional family structures, like same-sex partnerships, blended families or cohabitation. While it is possible to extend GEDCOM to support this type of relationships, inter-operability between different software is limited for such extensions.
Another major disadvantage of using GEDCOM, is that GEDCOM files are monolithic. If you have a dataset of thousands of people or your want to frequently change your data structure, you're likely to experience performance problems. Especially in those cases, it's best to store your data in a database instead. Still, that doesn't mean GEDCOM is useless. In those cases, you might want to consider using a database schema based on the GEDCOM format that allows you to import/export between your database and the GEDCOM format. For this, libraries exist as well. Oxy-Gen would be an example.
Alternatives to GEDCOM would be GenTech's data model or Gramps data model. While they aren't as commonly used as the GEDCOM standard, they might better suit your needs.
If you want to use the Gramps data model, you can use eg. the Gramps PHP exporter to export your data into an SQLite database. See also this source on how to design your database to be suitable for the Gramps data model.
Related
I have a Chat.php file containing everything query-related. Then there is a Core.php containing connection to database and basic functions used in Chat.php like "query" and "rows" which processes the "query" into array.
In Chat.php there are two functions, the second one printing content of the first when using print_r($this->rows());. checkForLastMessage() is supposed to check one table to see if there are new messages to be pulled from another table with function getNewMessages().
This is how it looks like:
Core.php
<?php
class Core
{
...
public function query($sql)
{
$this->result = $this->db->query($sql);
}
public function rows()
{
for($x = 1; $x <= $this->db->affected_rows; $x++)
{
$this->rows[] = $this->result->fetch_assoc();
}
return $this->rows;
}
}
Chat.php
<?php
class Chat extends Core
{
public function checkForLatestMessage($chatid, $iam)
{
$userinchat='for'.$iam;
$this->query("SELECT anonchat.$userinchat FROM anonchat WHERE anonchat.chatid=$chatid");
$printarray = Array();
$printaray = '';
foreach( $this->rows() as $id )
{
$printarray[] = $id[$userinchat];
}
if($printarray[0] != '')
{
$this->getNewMessages($chatid, $printarray[0]);
}
}
public function getNewMessages($chatid, $requiredMessages)
{
$this->query("SELECT anonmessage.content, anonmessage.timeposted FROM anonmessage WHERE anonmessage.messageid IN ($requiredMessages) ORDER BY anonmessage.timeposted ASC");
print_r($this->rows());
}
The last print_r contains elements from the previous function. I don't know why that is.
Edit. This is the output:
Array ( [0] => Array ( [for1] => 2,4,6 ) [1] => Array ( [content] =>
Message 2 [timeposted] => 2017-08-04 16:12:34 ) [2] => Array (
[content] => Message 4 [timeposted] => 2017-08-04 16:12:48 ) [3] =>
Array ( [content] => Message 6 [timeposted] => 2017-08-04 16:13:03 ) )
Element [0] of array (the one with "for1") is remaining from previous function.
To answer your question, you're initializing the class property $this->rows in the first method and then the 2nd method is appending to it. You need to reset $this->rows before adding to it.
public function getNewMessages($chatid, $requiredMessages)
{
$this->rows = null;
$this->query("SELECT anonmessage...");
print_r($this->rows());
}
Or better yet, reset the variable in the query() method. That way you don't have to do it each time.
Please don't take offense when I say that this is a bad design.
You're trying to write your own DAL (Data Abstraction Layer). This is a very complicated task and there are already lots of implementations out there. The Core class is going to become massive, complicated, and unwieldy when you try to adapt it to a dozen other classes.
PHP only supports a single inheritance so right off the bat you shot your self in the foot because any class that needs DB interactions will have to extend Core and you won't be able to extend anything else.
Consider keeping things simple for now and let each method handle their own queries and DB interactions. Focus on major concepts like DRY, encapsulation and keeping your classes focused on their responsibility.
Ex. checkForLatestMessage() what is this supposed to do? It sounds like should check for messages and then return a boolean (true|false) but instead it is calling getNewMessages() which outputs some data.
I don't know enough about your application to really suggest something useful but this feels a bit better than the path you're heading down. Notice we're not inheriting from Core so you're free to inherit from something else. The methods are concise and do what they say. You'd probably also save a few lines of code this way and it's easier to read.
<?php
class Chat
{
public function hasNewMessages($chatid, $iam)
{
// Query using PDO properly
return (bool)$hasNewMessages;
}
public function getNewMessages($chatid, $requiredMessages)
{
// Query using PDO properly
// An array of data or objects
return $messages;
}
}
/******** Client Code ***********/
$chat = new Chat();
if ($chat->hasNewMessages()) {
foreach ($chat->getNewMessages($id, $required) as $message) {
// $message
}
}
Just some of my thoughts... good luck.
I have code like this:
$something_type = $this->db
->where('something_type_id', $something->something_type_id)
->get('something_types')
->row();
if(!$something_type) {
$something->type = lang('data_not_specified');
} else {
$something->type = $something_type->something_type_name;
}
// everything is exactly the same here except for one word
$something_category = $this->db
->where('something_category_id', $something->something_category_id)
->get('something_categories')
->row();
if(!$something_category) {
$something->category = lang('data_not_specified');
} else {
$something->category = $something_category->something_category_name;
}
...
// and so on, about four times
One solution I thought of was:
$classfications = array('type', 'category');
foreach ($classifications as $classification) {
$id_property = "something_{$classificiation}_id";
$something_classification = $this->db
->where("something_{$classification}_id", $something->$id_property)
->get("something_{$classification}s")
->row();
if(!$something_classification) {
$something->$classification = lang('data_not_specified');
} else {
$name_property = "something_{$classificiation}_name";
$something->$classification = $something_classification->$name_property;
}
}
Of course, reading that will probably result in someone having the fits, so what do I do about this? This is probably a very common problem but I can't name it so having trouble Googling it.
Are you looking for Inflection?
The biggest challenge with the code snippet in the question is that the classifications you have provided have different pluralized forms (e.g., "type" becomes "types", yet "category" becomes "categories"). In order to structure this data without inflection, you could create a nested array hash, e.g.,
$classifications = array(
'type' => array(
'plural' => 'something_types',
'id' => 'something_type_id',
),
// etc.
);
foreach ($classifications as $singular => $data) {
/*
* Produces:
* $singluar = 'type';
* $data['plural'] = 'something_types';
* $data['id'] = 'something_type_id';
*/
}
However, most of the PHP frameworks I have used include an Inflector class (or similar) to handle the nuances in language that make using singular and plural names together problematic (and would avoid the need for a nested data structure, as described above).
Have a look at CodeIgniter's Inflector Helper to get an idea of what this entails. If you are already using a framework (your use of a $db helper suggests you might be) then also be sure to see if it supports ORM, which handles this kind of scenario automatically.
I stumbled onto this PHP: Iterators page today and am wondering why these classes are even needed. I'd like to make the assumption that they serve a purpose, otherwise they wouldn't be in PHP. I'm struggling to see the benefit of such items when there are already very simple ways of doing these.
Is PHP making an error to be a more respectable/object oriented programming language? Or is there really a benefit to doing this?
A good example of how there's 2 ways of doing this was found in a PHP comment on the ArrayIterator:
<?php
$fruits = array(
"apple" => "yummy",
"orange" => "ah ya, nice",
"grape" => "wow, I love it!",
"plum" => "nah, not me"
);
$obj = new ArrayObject( $fruits );
$it = $obj->getIterator();
// How many items are we iterating over?
echo "Iterating over: " . $obj->count() . " values\n";
// Iterate over the values in the ArrayObject:
while( $it->valid() ) {
echo $it->key() . "=" . $it->current() . "\n";
$it->next();
}
// The good thing here is that it can be iterated with foreach loop
foreach ($it as $key=>$val)
echo $key.":".$val."\n";
/* Outputs something like */
Iterating over: 4 values
apple=yummy
orange=ah ya, nice
grape=wow, I love it!
plum=nah, not me
?>
Iterators are included in PHP because it lets you use common language constructs (such as foreach) with arbitrary objects, instead of being restricted to looping over built-in objects like arrays. It's a hassle (and breaks encapsulation) to force an object to transform it's internal state into an array for you to iterate over, and for large datasets can make your system run out of memory. An iterable lets you get past both of those issues by letting the object itself return individual elements only when requested.
Your example is not really the why to use Iterator objects. In most ways the Iterator object is used to iterate over a class and use a class direct as an iterator.
http://php.net/manual/de/class.iterator.php
here is a short example.
When you have a single array its really easier to make a foreach over the array.
For my example I won't limit the answer to Iterator but also include Traversable.
Take a look at SimpleXML (SimpleXMLElement and SimpleXMLIterator).
Highly useful and easy to use and allows you to convert your XML into an object which can be iterated over/traversed through simply because it extended the classes that provide that functionality. I have foreach'd my way through a fair share of XML and am pretty glad it didnt have to reinvent the wheel to provide that. I am sure there are many more examples of their use and reason for existing but these were the first that popped up that I have immediately benefited from that aren't arrays.
You may come across an instance where you wish to implement the IteratorAggregate into a class, so you can iterator over a private member without having to make it public, or create a public function:
class BaseballTeams implements IteratorAggregate
{
private $teams;
public function __construct( )
{
$this->teams = explode( ',', "Tigers,Cubs,Orioles,Mariners,Yankees,Blue Jays,Marlins" );
}
public function getIterator( )
{
return new ArrayIterator( $this->teams );
}
}
Now, you can loop through the private $teams member, like so:
$baseball = new BaseballTeams( );
foreach ( $baseball as $n => $t )
{
echo "<p>Team #$n: $t</p>";
}
I have a set of Organizations and their Board Members.
All organizations have board members and many board members are on the board of more than one organization.
I am using JIT Hypertree to illustrate their relationships. The JIT Hypertree schema requires that one item be the parent of all and is drawn based on a single JSON array.
I would love to have the re-centering event query and re-populate the graph based on the change. Then 2 levels would be fine but I have not been able to work out how to do that.
The code I have at present recurses manually for three levels from the starting organization but what I want is to re-curse through all related records.
So it would start with an Org and add Org's array of children (board members). Then fetch all of the boards (other than current Org) for each board member and add those as children of the board member.
This would continue until each trail dead ends - presumably at a board member who only belongs to one board.
Anyone have advice on how to create this array and avoid duplicates?
$board = $center->board();
$top['id'] = $center->ID;
$top['name'] = $center->Org;
$top['children'] = array();
if ($board) {
foreach ($board as $b) {
$child['id'] = $b->ID;
$child['name'] = (strlen(trim($b->Last)) > 0) ? $b->First . ' ' . $b->Last : 'Unknown';
$child['data']['orgname'] = $center->Org;
$child['data']['relation'] = $b->Role;
$child['data']['occupation'] = $b->Occupation;
$child['children'] = array();
$childboards = $b->boards();
if ($childboards) { foreach ($childboards as $cb) {
$gchild['id'] = $cb->ID;
$gchild['name'] = $cb->Org;
$gchild['data']['orgname'] = (strlen(trim($b->Last)) > 0) ? $b->First . ' ' . $b->Last : 'Unknown';
$gchild['children'] = array();
$childboardmembers = $cb->board();
if ($childboardmembers) { foreach ($childboardmembers as $cbm) {
$ggchild['id'] = $cbm->ID;
$ggchild['name'] = (strlen(trim($cbm->Last)) > 0) ? $cbm->First . ' ' . $cbm->Last : 'Unknown';
$ggchild['data']['orgname'] = $cb->Org;
$ggchild['data']['relation'] = $cbm->Role;
$ggchild['data']['occupation'] = $cbm->Occupation;
$ggchild['children'] = array();
$gchild['children'][]= $ggchild;
}}
$child['children'][]= $gchild;
}}
$top['children'][] = $child;
}
}
$top['data'] = array();
$top['data']['description'] = $center->Desc;
echo json_encode($top);
// Edit 2011.10.24 In Re hakre response
My data structure is a table of Organizations with unique IDs, a table of People with Unique IDs, and then a bridging table for the two specifying Organization (Entity) and Person and the Role the Person is playing in the Entity. A typical many-to-many. No sub-boards at all. I made an image of it which now seems kind of pointless but I'll add it at the bottom.
The JIT library data structure is a little nuts (to me) in that it goes like this in their band example:
Top: Nine Inch Nails
Child: Jerome Dillon
Child: Howlin Maggie (another band)
{all the bands' members and then all of their bands...}
So the organization (band) is treated as though it is a Person even though it is comprised of a number of Persons. And when I recurse using the code above I get (I think) terrible bloat but the JSON it makes works correctly despite bloat.
Example JSON and Example Visualization
// End Edit
Your question is hard to answer in the sense that your data-structure is mainly unknown.
For the graphical represenation you only need to provide simple relationships if I understand that correctly:
*- Parent
+- Child
+- Child
...
`- Child
Your data structure has a different format, I don't know specifically but it's something like:
Org <-- 1:n --> Board
Board <-- n:n --> Board # If you have sub-boards
Board <-- n:n --> Member
Whichever your data is represented, to map or transpose your data onto the required structure for the graphical representation, you need some functions that take care of that.
To do that you need to share classification/type between both and specific keys, so that you can look-up the needed data from the event to return the data. For example:
if (request_parent_is_org())
{
$id = request_parent_id();
$parent = data_get_board($id);
$parent->addChildrent(data_get_board_children($id));
}
else
{
... # All the other decisions you need to take based on request type
}
view_response_to_json($parent);
What you have with your many-to-many data model is a graph. JIT is designed for trees.
To put it another way, JIT will not correctly show the crossing lines that are represented in the data whenever a single person is connected to multiple organizations.
I'd recommend a proper network graph visualization - D3.js has a great implementation for modern browsers.
The JSON data format it uses is actually easier to implement given your table structure - for all the organizations and people, you define objects:
{
"name": "Mme.Hucheloup",
"group": 1
},
{
"name": "Afton School Board",
"group": 2
}
And for each association in your association table you define connection objects that wire them together:
{
"source": 1,
"target": 2
},
The fancy coding in D3 takes care of the rest. Good luck!
You can use function below:
function get_orgs_and_childs ($child_id, $found = array())
{
array_push ($found, $child['name']);
if($child['children'])){
$found[] = get_parents($child['id'], $found);
}
return $found;
}
Call it using:
$orgs = get_orgs_and_childs($child['id']);
I have a smaller web app that republishes content from one social source to another. Users have, let's say 5 options for how to filter that content. Their prefs are stored in my user DB as 1 or 0 markers.
When I do my major "publish" action hourly, what's the best way to break up my code to implement these preferences? To make it more clear, my current implementation is something like this:
mysql_query(SELECT * FROM users);
foreach ($user as $row){
get_json_data($userID);
if ($pref1 == 1){
/code that enacts preference 1, adds results to array $filtereddata
}
if ($pre2 == 1 ){
/code that filters out preference 2, adds results to $filtereddata
{
postfinalarray($filtereddata);
}
Obviously this is mock code, but that's the general flow I've been using. Is there a better way to implement customizing a function with user's preferences? Should I design the function to accept these preferences as parameters? Will that save processing time or be more maintainable?
Sorry if this is too general, please ask questions so I can clarify.
Well of course there are many possibilities to improve this kind of code. Just imagine, if you would add another preference you would have to rewrite your whole code.
I always try to use the object oriented way so creating a user class always makes sense:
<?php
class User
{
private $_data;
public function __construct($userdata, $preferences)
{
$this->_data = $userdata;
}
public function getPreferencesData($preferences)
{
$result = array();
$prefs = $this->_data['preferences'];
foreach($preferences as $key => $value)
{
if($value == "1")
$result[] = $this->_data[$key];
}
return $result;
}
}
$mypreferences = array("name" => 1, "birthdate" => 0); // show e.g. name and birthdate
$mydata = array("name" => "Gaius Julius Caesar", "birthdate" => "July 13th -100");
$testuser = new User($mydata);
print_r($test->getPreferencesData($mypreferences));
?>
Then you can easily adapt to whatefer preferences the user has chosen without having to change your code.
From the limited info you've provided, your method seems fine.
I would do something slightly different probably. I would have a table of your 5 prefs (or would just hard code them if they're very unlikely to change). I would create a cross-reference table linking the prefs and users. Then you can do something like:
foreach($users as $user)
{
$filtereddata = array();
foreach($user->getPreferences() as $pref)
{
$filtereddata += $this->getFilteredData($user, $pref);
}
}
This is more overhead than what you're using, but more easily extensible if/when you add preferences/features.