Multi-entity copying and duplicates with ATK Data and DeepCopy

Romans Malinovskis
3 min readDec 13, 2018

--

Disclaimer: To those unfamiliar with ATK Data — it’s a high-level data abstraction PHP framework. (Read my article on why ATK Data was created).

Our current focus is on multi-record operations. Particularly we focus on reference mapping and complex data structures, so in this article I wanted to give you some update on our progress towards release of ATK Data 1.4.

Ingesting complex data structures

After describing some ideas on medium.com, the implementation have been pushed towards 1.4 release. Using $model->insert() now you can specify list of related records to insert and associate. Here is a basic example showing the import of a Country record and related User records:

$c = new LCountry($this->db); 
$c->insert([
'name'=>'Canada', 'Users'=>[
'Alain',
['Duncan', 'is_vip'=>true]
]
]);

Another form of this is through the aggregate field:

$user->hasMany('Phones', new PhoneNumber())
->addField('phones', ['concat'=>', ', 'field'=>'phone']);
$user->import([
['john', 'phones'=>'+123, +4444'],
['peter', 'phones'=>'+44 292929, +371 29292929'],
]);

The above insert would create 6 records — two for the “User” and four for the “PhoneNumber” linking them accordingly. Of course it also depends on how you map things.

This can be used for your PHPUnit suite to create complex scenarios or with Rest API.

Deep Copy Data

Initially drafted in our wiki, this feature is designed to copy records along with their related records either

  • between different database persistences (DB, Cache, Session, etc),
  • different models (Quote -> Invoice),
  • different scopes (User1 -> User2)
  • or simply duplicating data (copy of Invoice1)

To demonstrate how Deep copy work imagine a scenario where you write application where each “Client” have multiple Invoice and Payment records. Invoices also can have multiple Lines. Totals are calculated through aggregation on-the-fly inside the database. Payments also link to Invoices.

Next — to populate Invoice and related Lines:

$invoice = new DCInvoice($this->db);$id = $invoice->insert([
'ref'=> 'q1',
'client_id'=>$client_id,
'Lines'=> [
['tools', 'qty'=>5, 'price'=>10],
['work', 'qty'=>1, 'price'=>40],
]
]);

We can now check if the totals are correct:

$invoice->load($id);echo $invoice['totals'];

However what if we want to duplicate this invoice and all the lines? It used to be hard, but now with DeepCopy it’s very simple:

$invoice['date'] = new \DateTime('+1 month'); $dc = new DeepCopy();
$new_invoice = $dc
->from($invoice)
->to(new Invoice())
->with(['Lines'])
->copy();

This would create 3 new records — one for the invoice (with a new date) and two related lines.

Method “with()” can specify what related records need copying. For example, $new_invoice will still reference the old client, but what if you want to copy client data also?

$new_invoice = $dc
->from($invoice)
->to(new Invoice())
->with(['Lines', 'client_id'])
->copy();

As you copy larger chunks of your complex data model, ATK Data takes extra care not to duplicate records unnecessary. During the copy operations all records will be copied no more than one time.

Finally, here is an example, which copies entire “Client” record along with Invoice, Quotes, Payments and their Lines while keeping Payments linked with the corresponding Invoices too:

$client3 = $dc
->from($client1)
->to(new Client())
->with([
'Invoices'=>['Lines',],
'Quotes'=>['Lines',],
'Payments'=>['invoice_id'],
])
->copy();

The entire scenario can be seen here: https://github.com/atk4/data/blob/develop/tests/DeepCopyTest.php

After doing more tests and adding more documentation, both new features will be available in the upcoming ATK Data 1.4, so look forward the release!

--

--