yoyoma
yoyoma

Reputation: 3536

Map array structure in php

I need to find a nice solution to map one database model to another, third party model.

The code currently has to do something like:

'HomePhone' => $customer['details']['phone']['home']

for example.

As you can imagine, if $customer['details']['phone']['home'] is used multiple times, it becomes a nightmare to modify the keys if the database changes (as it does).

So I would like to have a config to use for the mapping, for example

$map = [
    'HomePhone' => 'customer.details.phone.home'
]

Such that, I can simple use $map['HomePhone'] throughout, and only need to change the one spot for the data model, eg to change it to customer.details.homephone

If this is the structure of the config item, how would I take customer.details.phone.home and explode it to [customer][details][phone][home] within the assignment above ?

Is this thinking valid, or is there a better way ? Thanks

Upvotes: 1

Views: 1931

Answers (1)

Mulan
Mulan

Reputation: 135217

welcome to data abstraction

Generally speaking, if you have some datum and you want to get a specifc piece of data out, you write a selector. The good news is, you're on the right path – only you're just trying to solve it a little incorrectly …

// some data
$customer = [
  // ...
  'details' => [
    // ...
    'phone' => [
      // ...
      'home' => '0123456789'
    ]
  ]
];

// a selector
function customerHomePhone($customer) {
  return $customer['details']['phone']['home'];
}

Now whenever you need to access this property in your code, you will always do so using

<span>Home Phone: <?php echo customerHomePhone($customer) ?></span>

The benefits of this are absolutely tremendous.

The person trying to get data from $customer doesn't need to know anything about how it's structured.

What if the data were to become extremely complex, with hundreds of fields and dozens of levels of nesting? It might be really frustrating to have to look at a reference object to re-learn how to access a field you need to read.

I can always remember customerHomePhone($customer) but it's much harder to remember …

$customer['details']['phone']['home']

"Was it detail singular or details plural? Wait, yeah, I think phones was pluralized but detail was singular... dammit !"


What it also means is that you have freedom to change where the home phone data (or any data) is stored – change it however you want – and all you have to do is update it one place in your app.

Say you change it to be stored as …

$customer['contacts']['home_phone']

… all you have to do is update customerHomePhone to …

function customerHomePhone($customer) {
  return $customer['contacts']['home_phone'];
}

Or $customer might even change more significantly. Maybe you'll use stdClass instead of array so the selector becomes …

function customerHomePhone($customer) {
  return $customer->details->phone->home;
}

It doesn't matter. As long as the selector points to the correct value in whatever data structure you're using, the rest of your app is shielded from the change and continues to work perfectly.


Of course it would make sense to build other selectors as well …

function customerDetails($customer)
  return $customer['details'];
}

function customerPhones($customer) {
  return customerDetails($customer)['phone'];
}

function customerHomePhone($customer) {
  return customerPhones($customer)['home'];
}

Would you like to learn more about data abstraction? Check out these links:


💩 OOP

Maybe you don't like the functional approach, or maybe OOP gels better with your existing app. classes are OOP's golden hammer when it comes to data abstraction – or any abstraction for that matter.

class Customer {
  private $data;
  public function __construct(array $data) {
    $this->data = $data;
  }
  // maybe we only use details internally, thus private
  private function details() {
    return $this->data['details'];
  }
  public function first_name() {
    return $this->details()['first_name'];
  }
  public function last_name() {
    return $this->details()['last_name'];
  }
  public function full_name() {
    return $this->first_name() . ' ' . $this->last_name();
  }
}

$customer = ['details' => ['first_name' => 'N', 'last_name' => 'K']];
$c = new Customer($customer);
echo $c->full_name(); //=> 'N K'

This might seem like a lot of work, but that's only because your $customer data is so complicated. If your customer data was flat, you'd still want to use data abstraction, but you could do so in a much more simple way …

class Customer {
  private $data;
  public function __construct(array $data) {
    $this->data = $data;
  }
  public function __get($key) {
    return array_key_exists($key, $this->data) ? $this->data[$key] : null;
  }
  public function full_name() {
    return $this->first_name . ' ' . $this->last_name;
  }
}

// flat data
$customer = ['first_name' => 'N', 'last_name' => 'K'];
$c = new Customer($customer);
echo $c->full_name(); //=> 'N K'

Upvotes: 2

Related Questions