Pass to content
Using the PerchCMS Factory pattern

Using the PerchCMS Factory pattern

The Perch Factory Pattern

Perch CMS uses a factory pattern when dealing with manipulating and reading data from the MySQL database, basically that means there are 2 main PHP objects for each MySQL database table:

The Base Objects:

  1. PerchFactory: Responsible for finding and creating the data in the table.
  2. PerchBase: Responsible for manipulating 1 line of data in the table.

The API Objects, used for custom apps, integrating with the PerchAPI which add an extra layer of functionality to the base objects:

  1. PerchAPI_Factory:  Extends the PerchFactory and is responsible for installing your app, essentially executing the activate.php file in your custom app folder.
  2. PerchAPI_Base: Extends the PerchBase and adds some resource logging. Not functionallity that is normally seen by you and runs in the background.

The names make a lot of sense if you think about it.  A factory in real life is responsible for dealing with creating and storing "things", but how we use those "things" (or what we can do with each individual "thing") is not the responsability of the factory but the "thing" itself.

These are the base objects, normally you won't instantiate a base object such as this, but rather you will use existing PerchCMS objects or create your own objects that extend one of these base or API objects.

Ok, but how do I use it?

I have yet to find a table in the Perch Database that I could not interact with its corresponding Factory and Base object.  These objects are in the global namespace, and follow a naming convention such as: Perch[App]_Objectnames for Factory object and Perch[App]_Objectname for Base objects, notice the pluralization on the Factory object which indicates multiple items.  

Here are some examples of Factories and their corresponding Base objects that exist in a base PerchCMS install:

  • PerchCategories_Categories and PerchCategories_Category
  • PerchContent_Pages and PerchContent_Page
  • PerchContent_Items and PerchContent_Item

And here are a couple from the Perch Shop and Perch Member Apps:

  • PerchShop_Products and PerchShop_Product
  • PerchShop_Addresses and PerchShop_Address
  • PerchMembers_Members and PerchMembers_Member

If you're using a decent IDE to develop your site, you'll be able to find these classes and drill down through thier inheritance tree to see what functions are available from thier parent objects (just follow the "extends" keyword in the class definition).

Ex:  All Factories extend at the base PerchFactory, so any class function that is public or protected between PerchFactory and PerchShop_Products will be able to be used by PerchShop_Products.  For use outside of PerchShop_Products in your own code, you have access to any public function.  Private and Protected are then out of scope.

A concrete example maybe ?

Recently it was asked on the Perch Community Slack:

How can I have a member delete a shop address?

This functionality is not provided by PerchCMS out of the box.  But you can achieve the same thing either in a custom app, or simply through a form post to some custom PHP, here is an example:

<?php
/**
  * File: deleteAddress.php
  * Description: Accepts form post and deletes a members address, 
  */
try {
    //Will need a form posting to this page with the address ID in a field named: "addressID"
    if (!$addressID = filter_input(INPUT_POST, 'addressID', FILTER_VALIDATE_INT)) {
        throw new \Exception('No valid address ID passed though POST vars');
    }
    $addressFactory = new PerchShop_Addresses();
    /** @var PerchShop_Address $address */
    $address = $addressFactory->find($addressID);
    //Get the customerID of the currently logged in member
    $memberID = perch_member_get('memberID');
    //Instantiate a factory object to find the current logged-in members customer
    $customerFactory = new PerchShop_Customers();
    $customer = $customerFactory->get_one_by('memberID', $memberID);
    //Make sure the address and customer were found
    if (!$address) {
        throw new \Exception('Could not load Address');
    }
    if (!$customer) {
        throw new \Exception('Could not load Customer');
    }
    //Validate that the address exists and belongs to the member logged in:
    if ((int)$address->customerID() !== (int)$customer->customerID()) {
        throw new \Exception('Address does not belong to member');
    }
    //Hard Delete:
    $address->delete();
    //Soft Delete:
    $address->update([
        'addressDeleted' => (new \DateTime())->format('Y-m-d H:i:s'),
    ]);
} catch (\Exception $e) {
    //Redirect to an error page, whatever you want if something doesn't work out.
    PerchUtil::redirect('/404');
}

What can I do with a PerchFactory object?

Here are in my opinion the most usefull features of the PerchFactory objects, there are others and you can open up the PerchFactory object to discover them yourself :

  • PerchFactory::find($id) : Finds a table record by primary key.
  • PerchFactory::all($Paging = false) : Get all the table records and optionally paginate the results.
  • PerchFactory::first() : Get the first record found while sorted by the default_sort_column and decault_sort_direction properties of the PerchFactory object.
  • PerchFactory::get_one_by($col, $val, $order_by_col=false) : Get one record by matching a column with a value, optionally order by a given column name.
  • PerchFactory::get_by($col, $val, $order_by_col=false, $Paging=false) : Get all records by matching a column with a value, optionally order by a given column name and optionally paginate the results.
  • PerchFactory::create($data) : Create a new table record, the $data parameter should be an associative array where the keys refrence the column names and the value is the actual data to create.

What can I do with a PerchBase object?

The PerchBase object is used to manipulate a single record. It uses the PHP __call() [refrence] magic function to retrieve column data through name matched function calls.  For example : If we have a PerchShop_Product object, and we want to get the product slug stored in the database, we can do something like the following:

<?php
$id = 123;
$products = new PerchShop_Products();
$product = $products->find($id);
echo $product->productSlug();

This code will create a new PerchFactory object for the Shop Products table.  Then find by productID, a record with productID equal to 123.  Then we echo the productSlug column data.

Any column can be returned by transforming the column name into a function.  

Here are in my opinion the most usefull features of the PerchBase objects, there are others and you can open up the PerchBase object to discover them yourself :

  • PerchBase::update($data) : Updates the table record, the $data parameter should be an associative array where the keys refrence the column names and the value is the actual data to be modified.
  • PerchBase::delete() : Removes the record from the database. There is no undo for this.
  • PerchBase::to_array() : Transforms the object to an associative array where the key is the column name and the value is the data, like what you get when you do a perch_content_custom() but pass 'skip-template' => true as an option.  Dynamic properties (those that are not thier own column, but data you have added to the initial template) will be prepended with 'perch_' for the array key.

What about my custom apps ?

If you're writing custom apps for perch and dealing with the database, hopefully you're already using the Factory pattern and extending the PerchAPI_Factory and PerchAPI_Base classes.  In anycase, here are a couple of tips : 

  1. Stick to the pattern.  Operations that involve multiple records should be found in a PerchFactory object. If you're dealing with a single record, that should be in the PerchBase object.
  2. Use the inherited properties to sort.  The PerchFactory has a number of inherited properties for things like sorting: PerchFactory::default_sort_direction and PerchFactory::default_sort_column. This will tell Perch how to sort the results by column name in either the ASC (ascending) or DESC (descending) directions.
  3. Use the PerchFactory::standard_restrictions() function.  When the PerchFactory builds its SELECT SQL statement, the first part of the WHERE clause gets the result of the standard_restrictions function before anything else.  You can override this function in your PerchFactory object to apply additional WHERE conditions to the SQL.  If you do something similar to the following, you can control the WHERE clause however you want within all functions of your PerchFactory object:
<?php
class MyItems extends PerchAPI_Factory {
    // Note that once you set the $where, it will be present for all select sql statments for this class.  You'll need to reset to '' when not needed.
    private $where = '';
   protected function standard_restrictions() {
        return $this->where;
    }
    public someFunction($param1) {
          $this->where = '  AND `column1`=' . $this->db->pdb($param1);
   }
    public someOtherFunction($param2) {
        $this->where = ' AND `column2`=' . $this->db->pdb($param2);
    }
}

Conclusion

We've seen an overview of the PerchCMS Factory pattern which is used to access data from the MySQL database.  We've noted some of the most usefull and frequently required functions of both the PerchFactory and PerchBase object with some examples of how to use it.  Now you'll be equipped with some new strategies for dealing with PerchCMS data and building more robust custom apps and extending the base functionality.


Happy Perching!

Categories: Web Development PHP Tips Perch CMS

Leave a comment