Working with the plugin’s object types

Torro Forms introduces several object types representing its core data. This tutorial will get you familiar with the classes that you need to use when interacting with them. To learn about the different object types themselves, we recommend you to read our article “Object types and database schema” if you haven’t already.

The foundation

Quite a bit of the plugin’s codebase is built upon a plugin library, making several plugin development tasks easier and solves common issues that you otherwise would need to work on again and again. Particularly the classes dealing with the object types and the database heavily rely on base classes from that library. In the Torro Forms plugin directory, the codebase of that library can be found in the vendor/felixarntz/plugin-lib directory.

Concepts

For every object type, there are four crucial classes that are minimally required to interact with that type. Each content type has at least these four classes, and all of these classes extend a base class from the plugin library. Here is a list of the base classes and what they are responsible for:

  • Leaves_And_Love\Plugin_Lib\DB_Objects\Model: This is the base class representing a single object. It contains logic to read and modify an object’s properties, to ensure data integrity and to persist that object with the database.
  • Leaves_And_Love\Plugin_Lib\DB_Objects\Collection: This is a list of multiple objects. The base class implements the PHP interfaces ArrayAccess, Iterable and Countable, so the individual objects can be accessed as if the list was an actual array. Collections are commonly used as the result of database queries. Therefore they not only contain the objects found, but also data like the total number of objects that would have matched the query, for example in case a maximum amount of objects to look for was specified.
  • Leaves_And_Love\Plugin_Lib\DB_Objects\Query: This base class allows you to run an arbitrary query against the database to find objects that match certain criteria. They work largely similar to the WordPress core query classes, like WP_Query, WP_Term_Query, WP_Comment_Query etc. Every query returns one of the aforementioned collection objects, embedding the actual result in there.
  • Leaves_And_Love\Plugin_Lib\DB_Objects\Manager: The manager base class is the class you will interact with the most. Every interaction with objects of an object type starts at its manager class. It contains methods to get, add, update and delete objects from/to the database, and also factory methods to instantiate the above model, collection and query objects respectively. The class furthermore includes some low-level functionality, internal configuration of the object type and other things, which however are not as relevant for now.

As mentioned, every Torro Forms object type has its own set of these four classes. The class names follow a simple pattern, so if you wanna find a certain class to look at it more closely, you shouldn’t face any issues. Here is a rundown of the naming pattern:

Be aware that you must never instantiate any of these classes manually. All manager objects are services which can be accessed via the torro() main function (for example the form manager can be accessed via torro()->forms(). Then, to use any other related functionality, use the methods the manager provides. Here is a list of the most common manager methods to use:

  • Call the create() method to retrieve a completely new object with the default property values set. The return value of the function is an object of the model class. Note that it will not have been persisted with the database yet, therefore it won’t have an ID (it’s id property will have a value of 0).
  • Call the get( int $id ) method to retrieve an existing object. The return value of the function is either the found object of the model class, or null if no object with that ID was found.
  • Call the query( array $args ) method to run a database query against a set of arguments the results should match. The return value of the function is an object of the collection class, containing the actual objects found in the query (if any).

Note that the individual model objects are only instantiated once per ID. In other words, when you call get( 3 ) for the first time, an instance of the model class will be created for ID 3 and its data. However, when you call it again, the exact same instance will be returned. You won’t receive an isolated new instance. Therefore it’s important you clean up after yourself. If you modify an object’s properties in a hooked-in function, make sure to also persist them with the database or reset the values before the execution moves on to the core plugin or another extension. When you change the value of an object property, it is not immediately saved in the database – it is only changed on the local instance you’re using, to prevent unnecessary overhead. Persistence of objects should be managed through methods of the model class. While these internally call methods of the manager class, those manager methods are mostly for internal usage. Here are the model methods to use to interact with the database:

  • Call the sync_upstream() method to persist the current state of the object with the database. If it’s a new object, it will be inserted. If it’s an existing object, its data will be updated. Be aware that during the insertion process the ID of the object will be set. Therefore, after the initial sync_upstream() call in an object’s lifecycle, you can retrieve its ID via the read-only id property. The method returns a boolean true on success, or a WP_Error object on failure.
  • Call the sync_downstream() method to set the current state of the object to the one that is currently persisted with the database. This rarely needs to happen, but for example in case you changed some property values, but due to some logic those should then eventually not be persisted, it’s important to set the object back to its original state. The method returns a boolean true on success, or a WP_Error object on failure.
  • Call the delete() method to delete the object from the database. After a successful execution of this method, the object will no longer be persisted and it will no longer have an ID. You could, if you wanted to, still continue to interact with the object and even persist it again as a new object – however, the original object will be completely gone. The method returns a boolean true on success, or a WP_Error object on failure.

Example

Here is some example code that shows the usage of the above methods: It’s a utility class that allows some interactions with containers that belong to a certain form. The ID of the form is stored as a class property.

class MyPlugin_Form_Container_Helper {

	/**
	 * The form ID containers should be associated with.
	 *
	 * @var int
	 */
	private $form_id = 0;

	/**
	 * Constructor. Sets the form ID.
	 *
	 * @param int $form_id
	 */
	public function __construct( $form_id ) {
		$this->form_id = $form_id;
	}

	/**
	 * Gets a collection of all container associated with the form.
	 *
	 * @return awsmug\Torro_Forms\DB_Objects\Containers\Container_Collection
	 */
	public function query_containers() {
		$args = array(
			// Set 'number' to 0 so there is no query limit.
			'number'  => 0,
			'form_id' => $this->form_id,
		);

		return torro()->containers()->query( $args );
	}

	/**
	 * Creates a new container associated with the form, with a label as provided.
	 *
	 * The container is not persisted with the database yet.
	 *
	 * @param string $label
	 * @return awsmug\Torro_Forms\DB_Objects\Containers\Container
	 */
	public function create_container( $label = 'My Container' ) {
		$container = torro()->containers()->create();

		// Set properties on the container.
		$container->form_id = $this->form_id;
		$container->label   = $label;

		return $container;
	}

	/**
	 * Moves an existing container over to the form, i.e. associates it with the form.
	 *
	 * @param int $container_id
	 * @return bool
	 */
	public function move_container_to_form( $container_id ) {
		$container = torro()->containers()->get( $container_id );
		if ( ! $container ) {
			return false;
		}

		$container->form_id = $this->form_id;

		$status = $container->sync_upstream();

		// If persisting the modified data failed, set the container back to its
		// original state and return false.
		if ( is_wp_error( $status ) ) {
			$container->sync_downstream();

			return false;
		}

		return true;
	}

	/**
	 * Deletes an existing container associated with the form.
	 *
	 * @param int $container_id
	 * @return bool
	 */
	public function delete_container( $container_id ) {
		$container = torro()->containers()->get( $container_id );
		if ( ! $container ) {
			return false;
		}

		// Bail if the container is not associated with the form.
		if ( $this->form_id !== $container->form_id ) {
			return false;
		}

		$status = $container->delete();

		return ! is_wp_error( $status );
	}
}