Skip to content

Latest commit

 

History

History
204 lines (178 loc) · 8.76 KB

5. Virtual Fields.md

File metadata and controls

204 lines (178 loc) · 8.76 KB

PHPFUI\ORM Virtual Fields

Note: Referenced namespaces in this document refer to the PHPFUI\ORM defaults.

You can define virtual fields with get and set semantics for any \App\Record class. Virtual fields are evaluated before any database field, so you can override the database defaults if needed, or create new functionality.

Every \App\Record class has a static $virtualFields array defined. The key of the array is the name of the virtual field. The value for each virtual field is an array of strings. The first string is the virtual field class name. Subsequent parameters are are passed to the getValue and setValue methods.

Note that virtual fields are created each time they are accessed and not stored or cached in the \PHPFUI\ORM\Record object. This means you can not change the virtual field on the Record object itself. You can only assign it to the Record object, which will store the value represented by the virtual field in the object, but not the virtual field itself.

The VirtualField class has two properties that will always be defined for use by the derived class:

  • $currentRecord is the current record that the virtual field should be based on.
  • $fieldName the field name that the VirtualField object was created from. This is the key of the $virtualFields array in the Record class.

One To One and Parent Related Record Relationships

If a field is named the same way as a corresponding table and suffixed with the proper ID, PHPFUI\ORM will automatically generate a One To One or parent relationship for you.

A child record with an ID field of the parent record automatically has a parent relationship via the build in related record functionality.

In the northwind schema, order_detail records are children of an order record, as they have an order_id key.

$orderDetail = new \App\Record\OrderDetail(27);
$parentOrder = $orderDetail->order;

Likewise, the Invoice record has a relationship to its Order record, and from the order, we can get the shipper's company field.

$invoice = new \App\Record\Invoice(7);
echo $invoice->order->shipper->company;

Custom Related Record Relationships

Sometimes you can't name a related record with the name of the table. For example you might have an Employee table, but yet need to have several references to different employees in the same table. You might have the following fields which are all Employee Ids:

  • salesPerson_id
  • packedBy_id
  • inspectedBy_id

You can make them all return employees with the following virtual field definitions:

class Order extends \Tests\App\Record\Definition\Order
  {
  protected static array $virtualFields = [
    'salesPerson' => [\PHPFUI\ORM\RelatedRecord::class, \App\Record\Employee::class],
    'packedBy' => [\PHPFUI\ORM\RelatedRecord::class, \App\Record\Employee::class],
    'inspectedBy' => [\PHPFUI\ORM\RelatedRecord::class, \App\Record\Employee::class],
    ];
  }

Usage

echo 'Sales Person : ' . $order->salesPerson->fullName() . "\n";
echo 'Packed By    : ' . $order->packedBy->initials() . "\n";
echo 'Inspected By : ' . $order->inspectedBy->first_name . "\n";

You can also assign records to the related record if it the right type.

$employee = new \App\Record\Employee(['last_name' => 'Zare']);
$order->salesPerson = $employee;

Custom Virtual Fields

You can write custom classes to create virtual fields on any record. Here we are adding a gross virtual to the OrderDetail record.

class Gross extends \PHPFUI\ORM\VirtualField
  {
  public function getValue(array $parameters) : mixed
    {
    return number_format($currentRecord->unit_price * $currentRecord->quantity - $currentRecord->discount, 2);
    }
  }

class OrderDetail extends \Tests\App\Record\Definition\OrderDetail
  {
  protected static array $virtualFields = [
    // define a new virtual field using the above Gross class
    'gross' => [Gross::class],
  ];
  }

Child Records

OrderDetail children for an Order record can be defined as:

class Order extends \Tests\App\Record\Definition\Order
  {
  protected static array $virtualFields = [
    // the OrderDetailChildren will be returned in order_detail_id order. Leave off the third array element to let SQL determine the order if you don't care.
    'orderDetailChildren' => [\PHPFUI\ORM\Children::class, \Tests\App\Table\OrderDetail::class, 'order_detail_id'],
  ];
  }

By default, child records will be automatically deleted when the parent record is deleted. You can disable this functionality for a specific \PHPFUI\ORM\Record class by setting the static property $deleteChildren to false, or by using your own Children class.

Usage

$order = new \App\Record\Order(31);
foreach ($order->orderDetailChildren as $orderDetail)
  {
  echo "Gross {$orderDetail->gross} for product {$orderDetail->product->product_name}\n";
  }

Many To Many

Many To Many relationships can be easily constructed with a junction table containing the primary keys of the two tables you want with a many to many relationship.

For the Product and Supplier tables, you need to add this:

class Product extends \Tests\Fixtures\Record\Definition\Product
  {
  protected static array $virtualFields = [
    'suppliers' => [\PHPFUI\ORM\ManyToMany::class, \Tests\App\Table\ProductSupplier::class, \Tests\App\Table\Supplier::class],
  ];
  }

class Supplier extends \Tests\Fixtures\Record\Definition\Supplier
  {
  protected static array $virtualFields = [
    'products' => [\PHPFUI\ORM\ManyToMany::class, \Tests\App\Table\ProductSupplier::class, \Tests\App\Table\Product::class],
  ];
  }

Many To Many relationships also support adding records to the relations with a simple assignment. The added record is inserted automatically and should not be previously inserted.

Morph Many

Morph Many relationships (ala Eloquent) can be easily constructed with a junction table containing the primary keys of the two tables you want with a many to many relationship.

For the Product and Employee tables to share image records, you need to add this:

class Product extends \Tests\Fixtures\Record\Definition\Product
  {
  protected static array $virtualFields = [
    'photos' => [\PHPFUI\ORM\MorphMany::class, \Tests\App\Table\Image::class, 'imagable', ],
  ];
  }

class Employee extends \Tests\Fixtures\Record\Definition\Employee
  {
  protected static array $virtualFields = [
		'photos' => [\PHPFUI\ORM\MorphMany::class, \Tests\App\Table\Image::class, 'imagable', ],
  ];
  }

Morph Many relationships also support adding records to the relations with a simple assignment. The added record is inserted automatically and should not be previously inserted.

Usage

$product = new \App\Record\Product(4);
$suppliers = $product->suppliers;
echo "There are {$suppliers->count()} for product {$product->product_code} - {$product->product_name}:\n";
foreach ($suppliers as $supplier)
  {
  echo $supplier->company . "\n";
  }

Cast Virtual Field

Often you want to use PHP class instead of a native scalar type (string, int, float, bool) to make your life easier. The Carbon class is an excellent example of a widely used package. It would be nice to get and set Carbon objects instead of strings formatted to the MySQL date format.

Use \PHPFUI\ORM\Cast virtual field to accommplish this. The Cast virtual field works with a wide variety of packages, as its only requirements are to implement __toString and a construct from a value.

Usage

class Invoice extends \Tests\App\Record\Definition\Order
  {
  protected static array $virtualFields = [
    'due_date' => [\PHPFUI\ORM\Cast::class, \Carbon\Carbon::class],
    'invoice_date' => [\PHPFUI\ORM\Cast::class, \Carbon\Carbon::class],
    ];
  }
$invoice = new Invoice(20);
echo 'Lead Weeks: ' . $invoice->invoice_date->diffInWeeks($invoice->due_date);

Type Safe Enum Support

In PHP 8.1 and above, you can add enum support easily. Assume this is your enum:

namespace App\Enum;
enum IncludeMembership : int
  {
  case NO = 0;
  case NEW_MEMBERS_ONLY = 1;
  case EXTEND_MEMBERSHIP = 2;
  case RENEW_MEMBERSHIP = 3;
  }

You can define the event.includeMembership field to use enums instead of integer values.

class Event extends \App\Record\Definition\Event
 {
 protected static array $virtualFields = [
 'includeMembership' => [\PHPFUI\ORM\Enum::class, \App\Enum\IncludeMembership::class],
 ];
 }

Your code would now look like this:

if (\App\Enum\IncludeMembership::NEW_MEMBERS_ONLY == $event->includeMembership)

You can also set and save the enum directly:

$event->includeMembership = \App\Enum\IncludeMembership:NO;
$event->update();

Enum assignments are type safe. Attempting to set the enum with an incorrect type will throw an exception.