Dblist - tool za lakše hendlanje "one-to-many" relacija

Napravih jedan PHP alatić, pa reko da ga podijelim…pa da me malo drvljem i kamenjem kao i obično. :slight_smile:

Znači ideja je već poznata, razni database model-engini postoje …svejedno mi nešto nije dalo mira da napravim svoju jednostavniju verziju.
Jednostavniju sa aspekta mogućnosti, ali i sa aspekta korištenja. … ovo sam sveo doslovno na razinu da mi je isti vrag upravljati listom u bazi, kao i klasičnom PHP listom. Možda jednog dana, preko kompajlera to svedem i na to da će se pisati i identična sintaksa za obje varijante …a za sada je sintaksa za db-liste malkoc drugačija, pošto bez prekompajliranja code-a postoje ograničenja u igranji sa sintaksom.
Unatoč tome, metode nad db-listom su u suštini iste kao i metode nad klasičnim listama. (Sem par sitnica/inovacija sa kojima sam se poigrao. “Double-index” aproach)

Nadalje, ovo nije ni zamišljeno kao full database model engine …nego samo pojednostavljenje za handlanje listi koje imamo negdje u bazi. “one-to-many” relacije. Ako bi se već radio full database model engine, onda je ovo tek jedna od puzzli sa kojom bi se hendlao cijeli engine. A ja sam htio imati baš ovu puzzlu izdvojenu, funkcionalnu samu za sebe.

Ako se nekome učini to korisno na taj način, može pitat za code…poslat ću mu.

P.S. Unaprijed isprika nad lošim engleskim, al nekako sam dokumentaciju navikao praviti na engleskom unatoč nepoznavanju istoga. Preživit ćete to…kome će se dati zaviriti.

Ako sam propustio ugraditi neku metodu koju smatrate da nebi trebala biti propuštena, vičite. :slight_smile:
Ili ako smatrate da se nepisanom konvencijom neka metoda treba drugačije zvati ili parametrizirati, vičite. :slight_smile:

Lets journy begin :slight_smile:

// dblist - elegant way to manage records "one-to-many";  

$x = new dblist("Users","John","telefons");

/*

We can imagine $x as array for all telefons numbers from user Jhon from table "Users".
All methods on $x is similar like usual methods on some array. Only what we got all synchronized recorded to database.

Lets see parametars for initialization some dblist instance:  // $x = new dblist($tableName, $entitetID, $listName);

"Users" -> its base for create table. Table will named as "Users_dblist_attributes"
"Jhon"  -> its some ID. In this case, its ID of user from table "Users"
"telefons" -> this is attribute of Users, but this attribute we can't put in origin table "Users" cause any user can have multiple telefon numbers. So dblist will manage for us table "Users_dblist_attributes" and there will be all records for "one-to-many" relation data associated with table "Users".

So, if we have more "one-to-many" attributes related to table "Users", they all will be recorded in single table "Users_dblist_attributes". In this way we dont grow up database with new table for every new relation "one-to-many"


So let's see how manipulate with instance of dblist.

NOTE: dblist will not change on any way origin table "Users". All records refferenced with table "Users" will live in table "Users_dblist_attributes"; 

*/


$x->set(['091/111-111', '092/222-222', '097/777-777']);  // We can simple set some list. Standard PHP syntax would be::  $x = [...];
	//For retrieving data ... there is get() method.
	print_r($x->get());  // >> Array ( [0] => 091/111-111 [1] => 092/222-222 [2] => 097/777-777 )   // Standard PHP syntax would be:  print_r($x);
	print_r($x->get(0)); // >> 091/111-111  // retrieving data from the first position.Standard PHP syntax would be:  print_r($x[0]);

$x->set(2, '098/888 - 888'); 		//seting element at index=2. Standard PHP syntax would be: $x[2] = '098/888 - 888';
	print_r($x->get()); 		    // >> Array ( [0] => 091/111-111 [1] => 092/222-222 [2] => 098/888 - 888 ) 

// So lets explore some different behaviour unlike standard PHP array. This approach is named "Double-index" and it's mean that you can retrieve any element of list 
// by they order-index (position) , or by associative index. But you can't manualy manage order-index, cause it is allways order of element in list.  Anyway, it's not so big differents...

//$x->set(10, '098/888 - 888');   // "Error cause there is no any element on index 10."  You cant manualy adding new order index!
// ..so there is assoc index available...
$x->set('Tele2', '095/555-555');
	print_r($x->get());  		// >> Array ( [0] => 091/111-111 [1] => 092/222-222 [2] => 098/888 - 888 [Tele2] => 095/555-555 ) 
	print_r($x->get('Tele2'));  // >> 095/555-555
	print_r($x->get(3)); 		// >> 095/555-555
	print_r($x->get('3'));     	// >> null // unlike integer, string 3 reffers at assoc index which is not defined
	print_r($x->length()); 	 	// >> 4	

$x->set(['vip'=>'091/111-111', 'tomato'=>'092/222-222', 'bonbon'=>'097/777-777']);	
	print_r($x->get());   		// >> Array ( [vip] => 091/111-111 [tomato] => 092/222-222 [bonbon] => 097/777-777 ) 
	print_r($x->get(1));  		// >> 092/222-222
	print_r($x->get('tomato'));	// >> 092/222-222
	print_r($x->length()); 		// >> 3

//The rules about "Double-index" is simple. Integr allways reffers to order index, string reffers to assoc index. You cant on your own make new order index, it's happen automatically while adding some item. But you have choice to add any assoc index if you want. 
//And there is no empty index between min and max index.

//Negative index reffers from back side of list:
$x->set(-1, '097/123-456');
	print_r($x->get());   		// >> Array ( [vip] => 091/111-111 [tomato] => 092/222-222 [bonbon] => 097/123-456 )  


$x->unset(); 			//  $x->get() >> []  	// unset make list empty. There is no any records in table, but $x instance is still here for manipulating with array.
$x->push(1); 			//  $x->get() >> [1] 
$x->addRange([5,6]); 	//  $x->get() >> [1,5,6] 
$x->addRange(1, [2,3]); //  $x->get() >> [1,2,3,5,6]   // Adding range on custom index position
$x->insert(-3, 4); 		//  $x->get() >> [1,2,3,4,5,6] // Inserting value 4 on index -3

$x->indexOf(2); 		// >>  1 
$x->indexOf("2"); 		// >> -1  // cause, it is different type, 2 as string is not in list. 

$x->contains(2);		// >> true
$x->contains("2");		// >> false
gettype($x->get(0));	// >> 'integer'  // so, wen you retrive element from list, you dont lose origin type of inserted value.

$x->set([1,2,3,3,3,3,4]);
$x->countOf(3); 		// >> 4  // number of occurence number 3 in list

$x->deleteByIndex(0); 		// $x->get() >> [2,3,3,3,3,4];
$x->deleteByValue(3); 		// $x->get() >> [2,3,3,3,4];  	// deleted only first occurence of targeted value.
$x->deleteByValue(3,2); 	// $x->get() >> [2,3,4];  	 	// deleted 2 occurence, as setted 2
$x->deleteByValue(3, true); // $x->get() >> [2,4];  	 	// true -> for delete all occurence

$x->set([1,2,3,4,5]);
$x->deleteRange(1,2); // $x->get() >> [1,4,5]  // deleted range start from index=1 and delete count = 2.

$x->deleteByIndex(1); // $x->get() >> [1,5]
$x->isset(1); 	// >> true // cause there is element at index 1. Even we just deleted element at index 1, the index are automatic re-range and there is no empty index between min and max index.
$x->any();		// >> true // true if we have any item in list

// ..and some standard array methods
$x->set([1,2,3,4,5]);
$x->shift();    // $x->get() >> [2,3,4,5]   //removed first element
$x->first();    // >> 2 	
$x->last();     // >> 5
$x->pop();      // >> 5
$x->get(); 	    // >> [2,3,4]
$x->unshift(1); // >> [1,2,3,4] //push element at beggining of array


//Nested array
$x->set([[1,2,3], [4,5,6]]);
$x->get(0); // >> [1,2,3] //So, you can record or retrive nested elements. But you cant apply any dblist methods direct on nested value. 

$x->get(0)->push(4); //Error, cause this is not suported. You should do that as...
	$nestedList = $x->get(0);
	array_push($nestedList, 4);
	$x->set(0, $nestedList);




// === Reffer to other attributes in same table ===

//First, lets return telefon numbers in game:
$x->set(['091/111-111', '092/222-222', '097/777-777']);


// IF we want in same table for same entitetID (Jhon) add some items in other attribute ..we can do that on standard way:
$x = new dblist("Users",'John',"friends"); 
// ...after we make new instance that reffers to John list of "friends" ...everything is already known... 
$x->set(['Mark', 'Laura']);

// But we could done that in a simpler way!
// ... if we already have instance of dblist, we can just change attribute of already existing dblist instance:

$x->setAttr("friends");
$x->set(['Mark', 'Laura']); 
//we also could done that one-line chained:
$x->setAttr("friends")->set(['Mark', 'Laura']); 

//Lets se what have at $x
print_r($x->get()); // >> ['Mark', 'Laura']

//But what if we want approach to some other attributes with out change $x ..let's see
$x->attr('telefons')->set(['092/222-222', '097/777-777']);
$x->get(); // >> ['Mark', 'Laura']  // !!! $x is not modified, and still refers to list of "friends"
$x->attr('telefons')->get(); // >> ['092/222-222', '097/777-777']  // So, use method "attr" if you dont want to modify $x, or method "setAttr" if you want modify $x attributes refference.


// And some global actions:
dblist::delete('Users', 'Jhon', 'telefons');  	  // delete all records from list 'telefons' associated with Jhon from table "Users";
dblist::delete('Users', 'Jhon');  			      // delete all records associated with Jhon from table "Users". Any list attached to Jhon will be deleted.
dblist::delete('Users');						      // delete all of dblist records associated with table 'Users'. This will no affect table "Users", as any of dblist action will not.


// pre-creating table is optional. dblist manage table on its own. But we can pre-create table with some additional option included per any of table. Let's see..
dblist::createTables(["table1", ['table'=>'table2','searchable_boost'=>true]]);  
// ... we have make referenced table for "table1" and for "table2"; 
// In this case, there is added searchable_boost to 'table2'. This boost speed for methods which deal with "serach_by_value" operation. These methods are: indexOf($value), contains($value)
// basicly it is just database index on "value" filed in table.
1 Like

Haha, ja neću. :smiley:

Preletio sam brzinski, fora je. Pohvala na trudu.

Par stvari koje nisam vidio:

  • Je li ta relacijska tablica ima 3 kolone? ID modela, assoc index i vrijednost?
  • Kako se spremaju podaci u bazi? Kako je definirano to polje u kojem spremaš vrijednosti atributa?
  • Možeš li u tablici atributa spremit i dodatna vrijednost, recimo da možeš uz broj telefona u prvom primjeru spremit i još nešto? Pomoćna kolona

Ja preferiram postojeća rješenja kad mi treba nešto zahtjevnije. Laravel ima odvojen database package:

I s njim možeš koristit Laravel Eloquent model:
https://laravel.com/docs/7.x/eloquent

Fala onda. :smiley:

Može

$simpaBroj = ['broj'=>'098/888-888','mreza'=>'simpa','racun'=>'10kn'];
$vipBroj = ['broj'=>'091/111-111','mreza'=>'vip','racun'=>'50kn'];

$x->set([$simpaBroj, $vipBroj]);  
// ili 
$x->set(['simpa'=>$simpaBroj, 'vip'=>$vipBroj]); 

//dohvat broja:
$vipBroj = $x->get('vip');


// ali ne mogu onda više direktno pristupati nekom attributu pojedinog itema, tipa računu od broja, nego samo cijelom tom zapisu. 
Znači ako pozovem

$y = $x->get('vip');  // $y >> ['broj'=>'091/111-111','mreza'=>'vip','racun'=>'50kn'];

znači ako trebaju izmjene na vip broju, moram ga cijelog pozvati…pa na njemu nešto izmjeniti…pa ga cijelog puknut natrag u bazu.
čim cijeli ide van, pa cijeli unutra…da bi se izmjenio samo jedan attribut od $y, taj proces očito nije optimiziran.

Ali to je zato jer ovome gledam svrhu u jednostavnijim stvarima, gdje imamo jednodimenzionalne liste. (Tu je sve jako dobro postavljeno u optimizacijskom smislu. Dosta pikanterija uzeto u obzir)

Kod kompleksnijih modela mi je ok odabir nešto poput Eloquent model-a koji spominješ.
Ali, ako se ne varam…ti tamo moraš preddefinirati modele koje imaš. I to je ok za kompleksnije stvari. …ali ako imam neke sitnice kada mi naleti “one-to-many” relacija za jednodimenzionalni model,
onda ne želim gubiti vrijeme na preddefiniranje takvih modela …i da mi to stvara extra tablicu u bazi.
Nisam siguran kakva je situacija sa Eloquent model kojeg spominješ, ali znam kako se Entity framework u C# ponaša, i mislim da je tu Eloquent model sličan, ako ne isti.

Znači za takve sitnice to želim u konačnici svesti da se identično ponaša kao i PHP array. Velim, jednog dana možda i na razini sintakse. :slight_smile:
Znači developer jednostavno kreira novu listu i zna da će mu ta lista postojati i u bazi…a u programu je hendla kao i svaku drugu listu i sve se reflektira na bazu. I to je glavni target, jednostavnost s tog aspekta.
To bi se dalo svesti da sve postoji i u jednoj tablici…sve te liste…ali tu je onda mana kod exporta baze, što se ne mogu te liste separativno exportati.
Zato sam ostavio polje Table, ono u suštini nije ništa drugo neko jedan “segment” koji postoji kao zasebna tablica i koji se kao takav može exportati ili importati u bazu.

A što se tiče ove optimizacije kod nestanih listi … to bi se moglo puno bolje napraviti. Ajmo reći da sam ovo sve složio poprilično brzo, pa je to sa nestanim strukturama tako reć preskočeno.
Ostavljeno je kao mogućnost, radi ako zatreba… ali nije napravljeno kako treba

. U suštini, kod zapisa takvog recorda, zapisujem ga kao JSON u bazu i kod čitanja iz baze ga vraćam natrag iz JSON-a. Samo to. :slight_smile:

Malo više. Dva index polja …za assoc key i order index. Dva value polja …varchar + text. i polje gdje se zapisuje type varijable, koje služi za recover varijable u originalni type nakon čitanja.

Ovo je vjerovatno odgovoreno sa ostatkom… samo da dodam razlog za dva value polja.
Sve kraće od 250char držim u Varchar polju, ostatk u text polju. Nisam siguran jel to ispravno…al mi nešto smrdi isplativnost držanja puno kratkih zapisa u TEXT polju …i načinu kako se indeksira to polje.
Neke baze niti ne mogu staviti klasičan index na TEXT polje i tu mi nešto jednostavno smrdi…

Pa ja radije indeksiram varchar polje … i preko toga indeksa mogu lako dohvaćati i zapise iz TEXT polja, koje onda ne moram niti indeksirati.

Znači za slučaj da je varijabla zapisana u TEXT polje, indeksiran je signature te varijable koji se i dalje nalazi u varchar polju…pa tako imam samo jedan index na varchar koji je zasigurno bolja opcija nego index na TEXT polju…ili ti potreba za dva indeksa. Minus ovoga je ipak nešto sporija potraga za vrijednosti koja će biti tražena u TEXT polju…ali vjerujem da je over-all postignut benefit, jer u praksi većina vrijednosti će završiti u varchar polju. (Tako da bi po meni zgorega bilo imati samo TEXT polje i index na njemu)

Moguće i nepotrebno kompliciranje…mada u codeu to nije ništa značajno zakompliciralo.

1 Like

Postoji full text search polje na bazi, a ima i drugih alata s kojima se to radi.

@bozoou

Znam da se trudis, ali ne vidim prednost.
Jer postoje migracije koje su mocne, a koje imaju up i down.

Sto se tice relacija, to se lako hendla.

Ma ovo je malo i eksperimentalno igranje. …hocel bit u praksi prakticno, tek cu saznat. :slight_smile:

A sto se tice migracija i database model enginea … ovo je ipak malo drugačiji pristup tome…tj. druga vrsta alata. Jesu ograničenja veća …ali bi trebala biti i neka oslobođenja…u svrhu jednostavnosti.

I ovo je prototip… putem kojeg ću tek saznat što je sve potrebno. Već mi se rasvjetlilo podosta toga.

Nego jedno “bedasto” pitanje.

Kako bi nazvali metodu nad array-om koja dodaje element u listu samo ako isti već nije unutra. Ta metoda mi je nekako must have nad array-om, ali uopće ne znam da sam se sreo sa nekim imenom koji bi se mogao uzeti da je poznat za tu metodu?

pushIfNotExist

U slucaju da zelis dodati element na kraj liste.Isti pattern mozes primjeniti ako zelis uraditi neke druge operacije.

pushIfNotExist je umalo rečenica. :slight_smile:
Zanima me ako ima neka riječ koja je elegantna…a da je dobra asocijacija na tu operaciju.

Zamisli da se trim() metoda zove: ‘cutEmptyFromEdge’ :slight_smile:

Sta fali, prilicno je jasno sta metoda radi ? Ja i dan danas moram ici na MDN da vidim razliku izmedju slice i splice :smiley:

Koliko ja znam nema.Postoji struktura podataka zvana Set, koja drzi samo unikatne elemente (duplikati nisu moguci), i tu metoda za dodavanje se zove add.Ali kod tebe se ne radi o Set-u ovdje, tako da add i nema bas nekog smisla.

Osim ako mu se doda sufiks Missing. :stuck_out_tongue:

I u pravu si i nisi. :slight_smile: …često dvojim oko takvih stvari. Ali ako postoji prepoznatljiva riječ, volim ići u tom smjeru. Znalo mi se često desiti da neku metodu koju nisam smatrao bitnom nazovem nekom glupom riječi koja mi slučajno padne na pamet (koja nema baš nikakvog ni značenja ni smisla ) … i onda ispadne da mi je ta metoda korisna, da tu riječ često upotrebljavam…i baš mi bude gušt imati tako izmišljen i prepoznatljiv naziv, a bude zapravo i lako pamtljiv. (Stekao sam dojam da se puno toga tako random nazove…recimo pojasni naziv “SQL” ili “ajax”. Sigurno da imaju on priču iza svojih slova…SQL je očito kratica početnih slova nekog dužeg imena…ali hoću reći kako su random slova ponekad dobra za pamtit i koristit.)

S druge strane neku metodu koju smatram bitnom…nazovem je tako kombinacijom više riječi (da zacementiram kao njeno značenje) i svaki puta me sjećanje vara jel ova ili ona kombinacija riječi bila u pitanju. Jer kombinacije riječi su “rotabilne” …pa opet moraš upamtiti jel “pushIfNotExist” …ili “addIfNotExist” …ili “ifNotExistPush” …itd. itd. …a i elegantnije je pisati kratki naziv metode.

…tako da mi je nekako onda simpatičnije imati jednu kratku riječ. Makar nastala i igrom slova. :slight_smile:
Ali ako postoji bilo kakva zanimljiva asocijacija kratke riječi i operacije…to mi je zgoditak. :slight_smile:

Inače mi se ova metoda u programima i zove “Add” :slight_smile: …ali nekako smatram da to nije dovoljno dobro, pa reko da pitam da čujem kakvu bolju sugestiju. :slight_smile:

Možda da ostaviš metodu koju imaš, a dodaš novi parametar?

function add($data, $unique = false) {
    if ( $unique ) {
         // dodaj u listu ako element nije u listi
    }

    // dodaj u listu
}

Ti si stvarno smjehotresan cirkus.

SQL
AJAX

Vala’, iz tvoje programerske™ kuhinje ni oraje’.

Tom logikom se metoda može nazvati pine() i sa tim sam ok :wink:

A PINE je kratica od Push If Not Exist. :slight_smile:

Bravisima, mislim da si upravo smislio ime metodi. PINE(). elegantno i pamtljivo. :slight_smile:

Nope. Već sam odgovorio prije par postova kako bi’ ja nazv’o.

To je još teže pamtljivo…mada programski može biti i praktičnije za koristiti, jer imaš flag sa kojim možeš kontrolirati ponašanje metode.

Ali recimo ako pogledaš metode unshift(), push(), insert()

-unshift je direktna metoda za insert na 0 poziciju
-push je direktna metoda za insert na zadnju poziciju
-insert je metoda koja će sa parametrom moći biti jedno ili drugo ili treće.

Mislim tako da svaka metoda koja je učestala, treba prvo imati svoje vlastito ime. A tek onda da se može izvesti parametrom, ili kombinacijom drugih metoda.

A baš mi se sviđa pine()…ili još bolje pin(). Array.pin() zvuči kak bi se reklo “pro”. :slight_smile:

Ali ako se i ne nazove tako…moj post na koji si se ismijavački osvrnuo…htio je poručiti da se mnogo toga nazove upravo tako “random”. Ok, često to budu asocijacije na prva slova nekih dužih izraza…ali u konačnici dobiješ riječ koja sama za sebe nema nikakvo značenje…a praktična je za uporabu. Tj. ta riječ tim procesom upravo dobije svoje značenje.

U početku biješe Riječ.

1 Like

Kako god, sa 6 slova možemo nazvati ukupno 729 milijuna metoda. :smiley:
Mislim da je to dovoljno opravdan razlog da se većina metoda ne zove duže od toga.

A povezivanje prvih slova smislenih izraza je čisto dobra procedura za smišljanje imena metodama. :smiley: