Recursive function to find all the dependencies of a MySQL table - php

I have a database with a couple of tables related between them. For example, the table User contains all the users in the system.
Then I have an index table named User_friend with the relation between a user an it's friends.
I have a function loadObject($class, $id) which is called like:
loadObject('User', 1);
and returns the User with id = 1 as an array with the following format:
array(
'id' => 1,
'username' => 'My user',
// the following array contains all the entries in User_invited
'friends' => [2, 3, 4, 5],
// same for comments
'comments' => [6, 7]
'type' => 'User'
);
I'm trying to come up with a recursive function that checks the User with id = 1, finds all the friends (inside the 'friends' array) and then loops through each value, find those Users and it's friends until it reaches the end of the chain without duplicating any entries.
This seems pretty straight forward. The problem is that apart from friends we can have other relations with Comments, Events and many other tables.
The tricky part is that this function should not only work with the 'User' class, but also with any class we define.
What I'm doing is using some sort of Indexed array to define which index tables refer to which main tables.
For example:
$dependencies = [
'friends' => 'User'
];
This means that, when we find the 'friends' key, we should query the 'User' table.
Here's my code:
<?php
$class = $_GET['class'];
// if we receive a collection of ids, find each individual object
$ids = explode(",", $_GET['ids']);
// load all main objects first
foreach($ids as $id) {
$error = isNumeric($id);
$results[] = loadObject($class,$id);
}
$preload = $results;
$output = [];
$output = checkPreload($preload);
print json_encode($output);
function checkPreload($preload)
{
$dependencies = [
'comment' => 'Comment',
'friend' => 'User',
'enemy' => 'User',
'google' => 'GoogleCalendarService',
'ical' => 'ICalCalendarService',
'owner' => 'User',
'invited' => 'User'
];
foreach($preload as $key => $object)
{
foreach($object as $property => $values)
{
// if the property is an array (has dependencies)
// i.e. event: [1, 2, 3]
if(is_array($values) && count($values) > 0)
{
// and if the dependency exists in our $dependencies array, find
// the next Object we have to retrieve
// i.e. event => CatchAppCalendarEvent
if(array_key_exists($property, $dependencies))
{
$dependentTable = $dependencies[$property];
// find all the ids inside that array of dependencies
// i.e. event: [1, 2, 3]
// and for each ID load th the object:
// i.e. CatchAppCalendarEvent.id = 1, CatchAppCalendarEvent.id = 2, CatchAppCalendarEvent.id = 3
foreach($values as $id)
{
$dependantObject = loadObject($dependencies[$property], $id);
// if the object doesn't exist in our $preload array, add it and call the
// function again
if(!objectDoesntExist($preload, $dependantObject)) {
$preload[] = $dependantObject;
reset($preload);
checkPreload($preload);
}
}
}
}
}
}
return $preload;
}
// 'id' and 'type' together are unique for each entry in the database
function objectDoesntExist($preload, $object)
{
foreach($preload as $element)
{
if($element['type'] == $object['type'] && $element['id'] == $object['id']) {
return true;
}
}
return false;
}
I'm pretty sure I'm close to the solution but I'm not able to understand why is not working. Seems to get stuck in an infinite loop even if I'm using a function to check if the object has been inserted in the $preload array. Also, sometimes doesn't check the next set of elements. Could it be because I'm appending the data to the $preload variable?
Any help is more than welcome. I've been trying to find algorithms for resolving dependencies but nothing applied to MySQL databases.
Thanks

After some failed tests I've decided to not use a recursive approach but an iterative approach.
What I'm doing is start with one element and put it in a "queue" (an array), find the dependencies for that element, append them to the "queue" and then step back and re-check the same element to see if there are any more dependencies.
The function to check the dependencies is a bit different now:
/**
* This is the code function of our DRA. This function contains an array of dependencies where the keys are the
* keys of the object i.e. User.id, User.type, etc. and the values are the dependent classes (tables). The idea
* is to iterate through this array in our queue of objects. If we find a property in one object that that matches
* the key, we go to the appropriate class/table (value) to find more dependencies (loadObject2 injects the dependency
* with it's subsequent dependencies)
*
*/
function findAllDependenciesFor($element)
{
$fields = [
'property' => 'tableName',
...
];
$output = [];
foreach($element as $key => $val) {
if(array_key_exists($key, $fields)) {
if(is_array($val)) {
foreach($val as $id) {
$newElement = loadObject($fields[$key], $id);
$output[] = $newElement;
}
}
else {
// there's been a field conversion at some point in the app and some 'location'
// columns contain text and numbers (i.e. 'unknown'). Let's force all values to be
// and integer and avoid loading 0 values.
$val = (int) $val;
if($val != 0) {
$newElement = loadObject($fields[$key], $val);
$output[] = $newElement;
}
}
}
}
return $output;
}
I'm also using the same function as before to check if the "queue" already contains that element (I have renamed the function to be "objectExists" instead of "objectDoesntExist". As you can see I check the type (table) and the id because the combination of these two properties is unique for the whole system/database.
function objectExists($object, $queue)
{
foreach($queue as $element) {
if($object['type'] == $element['type'] && $object['id'] == $element['id']) {
return true;
}
}
return false;
}
Finally, the main function:
// load all main objects first
foreach($ids as $id) {
$error = isNumeric($id);
$results[] = loadObject($class,$id);
}
$queue = $results;
for($i = 0; $i < count($queue); $i++)
{
// find all dependencies of element
$newElements = findAllDependenciesFor($queue[$i]);
foreach($newElements as $object) {
if(!objectExists($object, $queue)) {
$queue[] = $object;
// instead of skipping to the next object in queue, we have to re-check
// the same object again because is possible that it included new dependencies
// so let's step back on to re-check the object
$i--;
}
}
$i++;
}
As you can see, I'm using a regular "for" instead of a "foreach". This is because I need to be able to step forward/backward in my "queue".

Related

How to create single array of id's from parent and child array

I'm Trying to create single array that contains all the ID of parent and child from the database.
But all I was getting is single data.
My ideal output is:
array('160', '161', '162', '163', '164');
what am I getting is only
array('160');
Here is what I've done so far.
public function arrayId(array $elements) {
$where_in = array();
foreach($elements as $element){
if($element->isArray) {
$elems = $this->my_model->select_where('tbl_policies', array('parent_id' => $element->id));
$this->arrayId($elems);
}
$where_in[] = $element->id;
}
return $where_in;
}
$id = 160; //for instance
$elements = $this->my_model->select_where('tbl_policies', array('id' => $id));
$where_in = $this->arrayId($elements);
die(print_r($where_in));
and the data I'm fetching here:
tbl_policies
It's kinda difficult for me to construct questions. So please if something is not clear, do comment below, I'll try my best to make it more understandable. Thanks in advance.
I understand, that you want to delete a parent with all its children and grandchildren. But you do it not directly and sequentially rather want to collect all ids of the records to be deleted. You should go following steps:
Parent-Id (example 160) is already known. Add this to your list.
Write a recursive function such as getChildrenIds(parentId).
Within this function you should iterate over children. And if a child has the flag "isArray" (according to your application logic) then you should call getChildrenIds(currentChildId)
I have written following function. It should work.
public function getChildrenIds( int $parentId, array &$idList) {
$idList[] = $parentId;
$records = $this->my_model->select_where('tbl_policies', array('parent_id' => $parentId));
foreach($records as $r){
if($r->isArray)
$this->getChildrenIds($r->id, $idList);
else
$idList[] = $r->id;
}
return;
}
public function CollectIds(){
$id = 160; //for instance
$where_in = array();
$this->getChildrenIds($id, $where_in);
}
Please notice, that $where_in passed by reference to the recursive function getChildrenIds() and filled there.

How dou you write/call 2 tables in $this->db->table('table_name') in Codeigniter 4

Hello i have 2 tables that i want to call right now, for the EDIT (part of the CRUD)
tables:
table_a
table_b
i found in youtube how to update/edit from 2 tables, i need to call bot of the tables.
here's the code for the model
public function edit_this($ID_A)
{
return $this->db->table('table_a', '*i don't know how to insert the 2nd table')->where('ID_A', $ID_A)->get()->getRowArray();
}
Here's the controller
public function this_edit($ID_A)
{
$data = [
'title' => 'Admin',
'navbartitel' => 'You know this',
'alledit' => $this->theModel->edit_this($ID_A),
'validation' => \Config\Services::validation()
];
return view('this/all/edit', $data);
}
it works but i only can accsess the tabel_a, but i need them both so i can show what i've written in the edit form, from the database
anyone can help? thank you
$this->db->table(...) returns an instance of QueryBuilder and will happily accept a single string of comma-separated tables ("table1, table2..."), or even an array for that matter (['table1', 'table2'...]), as its first parameter. You are doing neither and instead passing multiple parameters.
When you call table(), the value passed in the first parameter is used during the creation of the database-specific Builder class:
public function table($tableName)
{
if (empty($tableName))
{
throw new DatabaseException('You must set the database table to be used with your query.');
}
$className = str_replace('Connection', 'Builder', get_class($this));
return new $className($tableName, $this);
}
The DB-specific Builder class has no constructor of its own so falls back on the __construct defined in BaseBuilder, which it extends:
public function __construct($tableName, ConnectionInterface &$db, array $options = null)
{
if (empty($tableName))
{
throw new DatabaseException('A table must be specified when creating a new Query Builder.');
}
$this->db = $db;
$this->from($tableName);
...
I've truncated this for brevity because the important part is that call to $this->from, which is in the end how multiple tables get processed:
public function from($from, bool $overwrite = false)
{
if ($overwrite === true)
{
$this->QBFrom = [];
$this->db->setAliasedTables([]);
}
foreach ((array) $from as $val)
{
if (strpos($val, ',') !== false)
{
foreach (explode(',', $val) as $v)
{
$v = trim($v);
$this->trackAliases($v);
$this->QBFrom[] = $v = $this->db->protectIdentifiers($v, true, null, false);
}
}
else
{
$val = trim($val);
// Extract any aliases that might exist. We use this information
// in the protectIdentifiers to know whether to add a table prefix
$this->trackAliases($val);
$this->QBFrom[] = $this->db->protectIdentifiers($val, true, null, false);
}
}
return $this;
}

PHP - Search for value inside a multidimensional array

I have a multidimensional $array. One of its column is customer name.
I have to save all the rows of array in a db, except for the rows with the same customer name.
I've written a function called searchMultidimensionalArray which creates an array that contains all the rows with different customer name. Before inserting a row in the db, I search for the current row in that array. If present, I do nothing, else I store the current row.
Here's my solution, but It's not working properly.
public function searchMultidimensionalArray($valueName, $arrayCustomer)
{
foreach ($arrayCustomer as $key => $val) {
if ($val['name'] === $valueName) {
return true;
}
return false;
}
}
This is where I loop through the array:
for ($i = 1; $i < $countArray; $i++) {
$customer = new Customer();
$customer->setName($customerName); // I already have $customerName data
if ($i == 1) {
$arrayCustomer[$i] = [
'name' => $customerName,
];
$em = $this->getDoctrine()->getManager();
$em->persist($customer);
$em->flush();
}
if ($this->searchMultidimensionalArray($customerName, $arrayCustomer) == true) {
$repository = $this->getDoctrine()->getRepository('AppBundle:Customer');
$customer = $repository->findOneBy(['name' => $customerName]);
} else {
$em = $this->getDoctrine()->getManager();
$em->persist($customer);
$em->flush();
$arrayCustomer[$i] = [
'name' => $customerName,
];
}
}
Unless I am misunderstanding your case, the desired result can be efficiently achieved a couple of ways.
The DB way: Set up your database table so that the name column is a unique key. That way if you try to INSERT a duplicate, the row will fail as desired.
The PHP way: I would recommend array_column() and array_unique() like this:
$unique_customer_names=array_unique(array_column($arrayCustomer,'customer_name'));
Then use $unique_customer_names to identify duplicates.
I hope one of these options provides a preferable solution for you.

Searching an array for an object's member variable

How would something like this be possible:
I have an object called Player:
class Player
{
public $name;
public $lvl;
}
and I have an array of these players in: $array.
For example $array[4]->name = 'Bob';
I want to search $array for a player named "Bob".
Without knowing the array key, how would I search $array for a Player named "Bob" so that it returns the key #? For example it should return 4.
Would array_search() work in this case? How would it be formatted?
Using array_filter will return you a new array with only the matching keys.
$playerName = 'bob';
$bobs = array_filter($players, function($player) use ($playerName) {
return $player->name === $playerName;
});
According to php docs, array_search would indeed work:
$players = array(
'Mike',
'Chris',
'Steve',
'Bob'
);
var_dump(array_search('Bob', $players)); // Outputs 3 (0-index array)
-- Edit --
Sorry, read post to quick, didn't see you had an array of objects, you could do something like:
$playersScalar = array(
'Mike',
'Chris',
'Steve',
'Bob'
);
class Player
{
public $name;
public $lvl;
}
foreach ($playersScalar as $playerScaler) {
$playerObject = new Player;
$playerObject->name = $playerScaler;
$playerObjects[] = $playerObject;
}
function getPlayerKey(array $players, $playerName)
{
foreach ($players as $key => $player) {
if ($player->name === $playerName) {
return $key;
}
}
}
var_dump(getPlayerKey($playerObjects, 'Steve'));

Reference PHP array by multiple indexes

This may be some sort of weird longer shortcut, and please correct me if I'm mistaken in this train of thought...
I have a matrix of data that looks like:
unique_id | url | other random data...
unique_id | url | other random data...
unique_id | url | other random data...
I want to be able to reference an item by either it's url, or it's unique_id - is there a fancy way to do this?
I suppose the cheating solution would be to just make two arrays, but I was wondering if there is a better way.
Only way I can think of that doesn't involve iterating the array for each search (see Jacob's answer) is to store references to each item in two arrays.
Edit: As the URLs and IDs cannot collide, they may be stored in the same reference array (thanks Matthew)
$items; // array of item objects
// Use objects so they're implicitly passed by ref
$itemRef = array();
foreach ($items as $item) {
$itemRef[$item->unique_id] = $item;
$itemRef[$item->url] = $item;
}
// find by id
$byId = $itemRef[$id];
// find by url
$byUrl = $itemRef[$url];
You could probably encapsulate this nicely using a collection class that implements getById() and getByUrl(). Internally, it could store the references in as many arrays as is necessary.
Of course, what you're essentially doing here is creating indexed result sets, something best left to database management systems.
Try something like this:
function selectByIdOrURL($array, $data) {
foreach($array as $row) {
if($row['unique_id'] == $data || $row['url'] == $data) return $row;
}
return NULL;
}
$array = array(
array('unique_id' => 5, 'url' => 'http://blah.com'),
array('unique_id' => 3, 'url' => 'http://somewhere_else.com')
);
$found = selectByIdOrURL($array, 5); //array('unique_id' => 5, 'url' => 'http://blah.com')
$nfound = selectByIdOrURL($array, 10); //NULL
It appears your fancy solution was only available as of PHP 5.5.
You can combine the use of array_search and array_column to fetch your entry in a single line of code:
$items = [
[
'unique_id' => 42,
'url' => 'http://foo.com'
],
[
'unique_id' => 57,
'url' => 'http://bar.com'
],
[
'unique_id' => 36,
'url' => 'http://example.com'
],
];
$bar = $entries[array_search(57, array_column($items, 'unique_id'))];
var_dump($bar);
//outputs
array (size=2)
'unique_id' => int 57
'url' => string 'http://bar.com' (length=14)
Surely an object would be the easy way?
class Item {
public $unique_url;
public $url;
public $other_data;
public function __construct($unique_url, $url, $other_data)
{
$this->unique_url = $unique_url;
$this->url = $url;
$this->other_data = $other_data;
}
}
class ItemArray {
private $items = array();
public function __construct()
{
}
public function push(Item $item)
{
array_push($items, $item); //These may need to be reversed
}
public function getByURL($url)
{
foreach($items as $item)
{
if($item->url = $url)
{
return $item;
}
}
}
public function getByUniqueURL($url)
{
foreach($items as $item)
{
if($item->unique_url = $unique_url)
{
return $item;
}
}
}
}
Then use it with
$itemArray = new ItemArray();
$item = new Item("someURL", "someUniqueURL","some other crap");
$itemArray->push($item);
$retrievedItem = $itemArray->getItemByURL("someURL");
This technique has a little extra overhead due to object creation, but unless you're doing insane numbers of rows it would be fine.

Categories