SimpleFM is a robust choice for interacting with the FileMaker Server XML API. The core SimpleFM package is agnostic about coding style. It can be added to a project via Composer (as it is PSR-0 compliant for autoloading) or any other means, such as simply copying it into the project and including the class.

This means it is still a fine choice for legacy projects and/or procedural coding style.

With that said, SimpleFM includes opt-in support for the enterprise patterns that Zend Framework 2 is built for. Specifically, we mean strong SOLID principles and Patterns of Enterprise Application Architecture (in particular the Table Data Gateway pattern).

This optional material is not demonstrated in the core SimpleFM package. If you want to work with a demo, it is the focus of the SimpleFM-skeleton project, which demonstrates basic usage with the default FMServer_Sample file that ships with every copy of FileMaker Server, and can be used as a starter app for projects.

While at first blush all the enterprise design pattern mumbo-jumbo can sound overwhelming to procedural PHP and FileMaker developers, we’ve found that an experienced web developer can quickly bootstrap the web project, and then the rigorous separation of concerns makes it far easier to maintain and modify than traditional procedural programming style.

In this article we will lift the hood on the skeleton app and show how clean and importantly how intuitive the code is.

As you’ll see in these examples, after an initial burst of architectural decision-making and configuration, the code is extremely easy to pick up and modify. We sometimes hear concerns that Zend Framework might be overkill for a simple CWP project, but in practice the “steep ramp” argument is not an actual worry, given the right recipes to begin with. As an analogy, it would be unreasonable to assume that someone would have to know how to write VBS as a prerequisite to making useful spreadsheets in Microsoft Excel.

First a quick word about scalability: while using enterprise patterns can benefit a project of any size, these techniques will not change the fact that the project will be communication with the FileMaker Server XML API. There are significant advantages in terms of maintainability, security, etc. These techniques provide excellent encapsulation of all the FileMaker-specific DML, so if the day should come when some or all of the application outgrows the scaling limitations of FileMaker, the switch to an alternative data store such as MongoDb is much simpler.

Project Structure

This is the directory structure for the SimpleFM-skeleton application.

For the purpose of this post, module is the only one top-level directory we really dig into, but I’ll start out with a very brief explanation of the other top level directories first.

  • .vagrant is a Vagrant cache directory that we always ignore.
  • config is where all environment configuration files go.
  • data is optional, is ignored by Git and is used for things like caches.
  • documentation is self evident. Put things in there such as sample data and special instructions that are too detailed for the main README
  • module we’ll come back to.
  • public is the web root. Files in here are directly accessible in the browser.
  • vendor is a Composer-managed directory where all the library code is located.
SimpleFM_skeleton_project_structure

This is a very typical structure, and the modules directory is set up for PSR-0 autoloading. What this means is that everything inside a module’s src directory are classes with namespaces that match the directory names.

We can see that the application has two modules: Application and SimpleFMAuth. For the purpose of this post we’ll ignore the latter, except to say that it doesn’t have any controllers or views of it’s own. It’s entire job is to handle authentication and authorization, so it was broken out of the main module for clarity.

Focusing on module/Application, the structure is very typical as well.

  • config is where all module configuration files go (things which do not change depending on environment).
  • src/Application is the namespace root for the autoloader.
  • view is where the html templates go. These are not classes, so they have no namespace, however, they are typically organized in the same semantic hierarchy.
  • Module.php is required to define a ZF2 module. Everything else pivots off this special duck-typed class.

Once Module.php is configured, the two places that we make most changes are view/application for html, and src/Application for most other things. Let’s assume that view is self-explanatory, and move directly to src.

The src directory puts us into the pure OOP realm. All the classes in it are part of the Application namespace. We see a clear separation of concerns, as we’d expect if we’re following OOP best practices. ApplicationController and ApplicationForm should be fairly self explanatory in the context of an MVC (or Model/View/Controller) application.

SimpleFM_skeleton_module_structure

Given what we’ve discussed so far, we can clearly see where to go if we want to manage the views (the V in MVC) and where to go to manage the Controllers (the C in MVC), and Forms, which help bridge between views and controllers, do not get their own letter in MVC, but are a familiar concept, even to non-developers. So where is the model (the M in MVC)?

The Model

When it comes to intro-level discussions such as this one, the Model is arguably given short shrift, despite it arguably being the most important. I have hypothesized that one reason that it almost must be left under-addressed is because it is the most inherently variable aspect of an application. There are two primary things that the model encompasses: the persistence strategy (how to store and retrieve information), and the domain model (what to store, and the rules that govern the what). SimpleFM provides a solution for well defined persistence and domain model with an AbstractGateway, and AbstractEntity respectively.

Domain Model with AbstractEntity

We’ll address the concept of Entity first. Entity is the OOP term for what we might loosely call a record. Generally speaking, every record in a table can be loaded into an Entity that and vice versa. This is vastly over-simplifying the matter, so just mentally bookmark the fact that we’re only making a loose association here, but very often this is exactly what happens. So we will ignore the edge cases and exceptions here. When we work with data in OOP, we need collections of objects, not tables of data. When we convert a table record to an Entity, we hydrate it, in common parlance. In SimpleFM we use unserialize as a synonym for hydrate. An Entity in its pure form does not know anything about how it is serialized, unserialized, etc. A pure Entity should only define the attributes of what it represents. For example, if the domain model has the concept of Project, there will be a ProjectEntity class which defines the properties of a Project, including its relationships with other Entities. For example, a Project might have a collection of zero or more Tasks. So it follows that we have a TaskEntity with a mandatory Project property.

In SimpleFM, we have created an opt-in AbstractEntity which entity classes can extend, and which gives it some entity-specific meta-data characteristics and behaviors. Let’s take a look at an example Project entity with just it’s properties first:

<?php
namespace ApplicationEntity;

use SoliantSimpleFMZF2EntityAbstractEntity;
use DoctrineCommonCollectionsArrayCollection;
use ApplicationEntityTask;

class Project extends AbstractEntity
{
	/**
	 * Writable Fields
	 */
	protected $projectName;
	protected $description;
	protected $tag;

	/**
	 * Read-only Fields
	 */
	protected $id;
	protected $startDate;
	protected $dueDate;
	protected $daysRemaining;
	protected $daysElapsed;
	protected $statusOnScreen;
	protected $createdBy;
	protected $projectCompletionProgressBar;

	/**
	 * Child Associations
	 */
	protected $tasks;

// snip...

We see the properties grouped under three section comments. When defining a SimpleFM entity it’s important to identify which fields on the FileMaker layout will need to be updated. Clearly it’s impossible to write to things like calculation fields. For this reason, fields are segregated into writable and read-only. We also have associations, which are related entities which can be displayed in a portal in FileMaker. SimpleFM expects each writable property to have a getter and a setter defined, and each read-only property to have a getter defined, however, we omit the getters/setters here for clarity.

Because we have extended AbstractEntity, we inherit some base behaviors, and we’re required to implement some abstract methods so that the entity can be interrogated for it’s own meta information, such as the field mapping to be used for serialization and unserialization. Let’s take a look at the required mapping methods for our Project entity.

// snip...

public
function getFieldMapWriteable()
{
    $map = [
        'projectName' => 'Project Name',
        'description' => 'Description',
        'tag' => 'Tag',
    ];
    return $map;
}

public
function getFieldMapReadonly()
{
    $map = [
        'id' => 'PROJECT ID MATCH FIELD',
        'startDate' => 'Start Date',
        'dueDate' => 'Due Date',
        'daysRemaining' => 'Days Remaining',
        'daysElapsed' => 'Days Elapsed',
        'statusOnScreen' => 'Status on Screen',
        'createdBy' => 'Created By',
    ];
    return $map;
}

// snip...

These are arrays that in turn define which properties are writable and which are read-only. They also define the correlation between the property name and the FileMaker field name.

The final customization required for Project is to define how to handle the associated entity, Task. We start out by overriding the constructor, and defining the tasks property as an empty ArrayCollection. This way, even when empty, it will always be an ArrayCollection. Then we call the parent constructor normally.

SimpleFM design dictates that Associated entities cannot be persisted indirectly, but the entity needs special instructions for unserialization. We override unserialize and call the parent method. Then we handle the portal row by looping over it and adding a new Task instance to the ArrayCollection for each row.

Finally we are required to implement two public methods which define a default layout name for Project and a default route segment for use in constructing routes (urls) with this entity.

// snip...

public function __construct($simpleFMAdapterRow = array())
{
    $this->tasks = new ArrayCollection();
    parent::__construct($simpleFMAdapterRow);
}

public function unserialize()
{
    parent::unserialize();

    if (!empty($this->simpleFMAdapterRow["Tasks"]["rows"])){
        foreach ($this->simpleFMAdapterRow["Tasks"]["rows"] as $row){
            $this->tasks->add(new Task($row));
        }
    }

    return $this;
}

public function getDefaultWriteLayoutName()
{
    return 'Project';
}

public function getDefaultControllerRouteSegment()
{
    return 'project';
}

// snip...

This may sound a bit tricky at first, but I assure you, if you maintain your FileMaker web layouts and entities together, you will find that the centralization of the serialize/unserialize rules pays off many times over.

Next, let’s take a look at persistence of entities.

Persistence with AbstractGateway

In many ways the AbstractGateway is simpler to implement than AbstractEntity. The minimum requirements are that a Gateway class be defined along with a factory class to set it up, like these:

<?php
namespace ApplicationGateway;

use SoliantSimpleFMZF2GatewayAbstractGateway;
use ApplicationEntityProject as ProjectEntity;

class Project extends AbstractGateway
{

}
<?php namespace ApplicationGatewayFactory; use ZendServiceManagerFactoryInterface; use ZendServiceManagerServiceLocatorInterface; use ApplicationEntityProject as Entity; use ApplicationGatewayProject as Gateway; class ProjectGatewayFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $entity = new Entity(); $simpleFMAdapter = $serviceLocator->get('simple_fm');
        return new Gateway($entity, $simpleFMAdapter);
    }
}

Sometimes the Gateway class does not need to implement any of it’s own methods, as in the example above, because there are a number of existing useful methods already defined on AbstractEntity. Custom gateway methods, however, are where you can start to define basic business logic for your model. If you application gets more complicated, you should consider implementing service classes, and keep the Gateways strictly for the persistence methods, however simple use cases often don’t warrant a separate service layer in the model.

Here’s a fictional Project gateway method, which implements some nonsesnse business logic by chaining existing find and edit methods every time helloWorld is invoked.

<?php namespace ApplicationGateway; use SoliantSimpleFMZF2GatewayAbstractGateway; use ApplicationEntityProject as ProjectEntity; class Project extends AbstractGateway { public function helloWorld($id) { $project = $this->find($id);
        $project->setProjectName('Launch web site ' . $project->getModid())
            ->setDescription('myDescription')
            ->setTag('myTag');

        return $this->edit($project);
    }
}

Conclusion

By investing in a little bit of setup with SimpleFM and ZendFramework, it’s easy to write FileMaker CWP apps with excellent encapsulation and separation of concerns. You too can have controller actions that look as clean as this.

public function listMostPopularAction()
{
    return [
        'mostPopular' => $this->itemGateway->getMostPopular(),
        'isActive' => $this->userService->isActive(),
    ];
}

2 thoughts on “Enterprise-quality Web apps with SimpleFM”

  1. There is a way to add loader icon in “php/filemaker/data api” page, after form submit button, that show loader until Filenaker server end is job ?

    1. Thanks for the question, Mario. What you are seeking to do is certainly possible, but a specific answer would be impossible without knowing more about your whole environment. For what it’s worth, FileMaker has removed official support for PHP. This doesn’t mean what you want to do is impossible, but it means you’re probably going to find fewer resources to help figure it out. This article is almost a decade old!

Leave a Comment

Your email address will not be published. Required fields are marked *

We're celebrating 20 years! Read about our journey here.

Party horn and confetti
Scroll to Top