Containers

What are containers?

In Totara 13 we introduced a new internal tool called Container. This is built based on reusing the course's table and all of its associated components, which are module, course section and context.

How Totara containers work

The Container works based on Totara hooks that behave like middleware. For every page that either requires or optionally requires the course's ID, there is a hook in place in order to help the container implementation to redirect away from that page, as these pages are mainly built for courses. The hooks themselves are not smart enough to redirect away from the course-related page if the course record is not an actual course. The redirection will have to be a part of the container's implementation which the watcher(s) should check for the container type and decide what to do with it.

Changes within data layer:

  1. A new field within table ttr_course called containertype -  a flag to define the container implementation that associate with this record.
  2. A new field within table ttr_course_categories called issystem - a flag to tell which categories are able to be maintained by the user and which are maintained by the system itself. Since a container cannot exist without a category, this field in the course's category table is introduced to help us hide all non-course-related categories from the end-users - even the Site Administrator. Note that categories with this flag will not be able to be viewed/edited/deleted by the end users.

For every new record within the ttr_course table the field containertype will default to 'container_course' as this is backward compatible with any customisable tables in the course made by third parties. However, if the course record is inserted via container API, it will be sure that the containertype is set to the correct value as the container implementation.

Main API classes and methods

The main abstract classes for containers are:

Class namePurpose 
\core_container\container

An abstract class that contains all the metadata related to a container, which can be overridden by the implementation. It is also used to instantiate the container instance.

\core_container\section

Represents the course's section, which does not contain any business logics but a purely read/write access to data layer.
\core_container\moduleRepresents the course's module record, which it does not contain any business logics, and only read/write to data layer.

 List of hooks that are used within container's abstract layer:

Hook class namePurpose

report_log\hook\index_view

To help redirect away from a course-related page.
report_loglive\hook\index_viewTo help redirect away from a course-related page.
report_outline\hook\index_viewTo help redirect away from a course-related page.

report_participation\hook\index_view

To help redirect away from a course-related page.

gradereport_grader\hook\index_view

To help redirect away from a course-related page.
gradereport_history\hook\index_viewTo help redirect away from a course-related page.
gradereport_outcomes\hook\index_viewTo help redirect away from a course-related page.
gradereport_overview\hook\index_viewTo help redirect away from a course-related page.
gradereport_singleview\hook\index_viewTo help redirect away from a course-related page.
gradereport_user\hook\index_viewTo help redirect away from a course-related page.
totara_core\hook\backup_import_viewTo help redirect away from a course-related page.
totara_core\hook\backup_restore_file_viewTo help redirect away from a course-related page.
totara_core\hook\backup_viewTo help redirect away from a course-related page.
totara_core\hook\configure_enrol_instancesTo help redirect away from a course-related page.
totara_core\hook\edit_enrol_instancesTo help redirect away from a course-related page.
totara_core\hook\enrol_index_pageTo help redirect away from a course-related page.
totara_core\hook\group_indexTo help redirect away from a course-related page.
totara_core\hook\mod_addTo help redirect away from a course-related page.
totara_core\hook\mod_updateTo help redirect away from a course-related page.
core_badges\hook\index_viewTo help redirect away from a course-related page.
core_badges\hook\new_badge_viewTo help redirect away from a course-related page.
core_badges\hook\viewTo help redirect away from a course-related page.
core_completion\hook\completion_editorTo help redirect away from a course-related page.
core_completion\hook\course_archive_completionTo help redirect away from a course-related page.
core_completion\hook\course_completionTo help redirect away from a course-related page.
core_course\hook\competency_viewTo help redirect away from a course-related page.
core_course\hook\course_edit_viewTo help redirect away from a course-related page.

core_course\hook\course_view

To help redirect away from a course-related page.
core_course\hook\reminders_viewTo help redirect away from a course-related page.

core_course\hook\reset_view

To help redirect away from a course-related page.

core_course\hook\switchrole_view

To help redirect away from a course-related page.

core_grades\hook\edit_tree_view

To help redirect away from a course-related page.

core_grades\hook\letter_view

To help redirect away from a course-related page.

core_grades\hook\outcome_view

To help redirect away from a course-related page.

core_grades\hook\scale_view

To help redirect away from a course-related page.

core_grades\hook\settings_view

To help redirect away from a course-related page.

core_question\hook\category_view

To help redirect away from a course-related page.

core_question\hook\edit_view

To help redirect away from a course-related page.

core_question\hook\export_view

To help redirect away from a course-related page.

core_question\hook\import_view

To help redirect away from a course-related page.
core_container\hook\module_supported_in_containerTo help with filtering all the course modules that the container implementation does not need.

Helper scripts

There are helper scripts that will help the developer to provide/check the hooks for the newly introduced container.

The scripts are located in:

Scripts
/container/cli/create_hook_file.php		=> Help to create a hooks.php file which is located within '/container/type/{new-container}/db'.
/container/cli/check_hook.php			=> Help to check if all the redirect hooks are captured.

How to add new container

To introduce a new container type to the system, create a new directory under /server/container/type/{your-container-name}. For example, if the developer wants to add a new container 'micro_course' then there should be an expected directory created with a path as below:

Container path
/container/type/micro_course

Each of the containers will have these expected classes and files within the same path as above:

Directory tree
// Example of new container micro_course


/container/type/micro_course
.
+-- classes
|	+-- micro_course.php // This file has to have the same name as the container's type directory.
|
|	+-- module
|	|	+-- module.php
|
|	+-- section
| 	|	+ section.php
|
+-- version.php 

The file /container/type/micro_course/classes/micro_courses is the main container's class, which it will have to extend the class 'core_container\container'.

Container class
<?php
// File /container/type/micro_course/classes/micro_course
class micro_course extends \core_container\container {
	// Extending any abstract function needed. Mostly none.
}

Note

The class that introduces a container must have the same exact name as container's type, as this is for the container's factory to auto-pickup and auto-resolve whenever a part of a system is trying to retrieve and instantiate a container. 

Section

The section is a part of the container, and it represents a row of a table {course_sections}. It has pre-defined all the CRUD functionalities within itself, however these functionalities are only about writing to the data layer. There are no business logics running within the abstract layer, it is up to the implementation to add the business logics around it at the implementation layer. 

When the developer introduces a new container the section class will have to be included as a part of the container as well. 

Note

The section class itself will have to sit in a special namespace: \{container_namespace}\section\ and it is mandatory to exist.
Container class
<?php
// File /container/type/micro_course/classes/section/{section}.php

namespace container_micro_course\section;

class section extends \core_container\section\section {
}

It does not have to be implemented with functionalities if the newly introduced container does not want to integrate or use the old course section. However, it has to be included in order to let core container code pick it up.

Module

Module is a part of the section, and as with the section, when the developer introduces the new container, the module class will have to be included as a part of the container as well. 

Note

The module class itself will have to sit in a special namespace: \{container_namespace}\module\
Container class
<?php
// File /container/type/micro_course/classes/module/{module}.php

namespace container_micro_course\module;

class module extends \core_container\module\module {
}

It does not have to be implemented with functionalities if the newly introduced container does not want to integrate or use the old course's module. However, it has to be included in order to let the core container code pick it up.

Category

By default a container can only exist within the system with an associated category. If the category is not provided to the container record, the process will try to look up an available category with the ID number that contains the container type, and assumes that this category will be used. However, if the related category is not found then it will create a new category, which is a sub category of the main course category. If the container implementation wants to define what the ID number value can be and what the name of category can be, then all it needs to do is to implement these two interfaces:

  1. core_container\facade\category_id_number_provider    => To give the container category's ID number.
  2. core_container\facade\category_name_provider.           => To give the container category's name.


Container provide category info
<?php
// File /container/type/micro_course/classes/micro_course
class micro_course extends \core_container\container implements \core_container\facade\category_id_number_provider, \core_container\facade\category_name_provider {
	public static function get_container_category_name(): string {
		return get_string('category_name', 'micro_course');
	}

	public static function get_container_category_id_number(): string {
		return 'something-unique-id';
	}
}

Best practices

There are may ways to fetch the container record and instantiate it within the system, which it can become a nightmare to maintain or deprecate them in many places later on. Furthermore, fetching container/course record many times in process can consume resources and leads to a performance issue, hence the abstract layer of container has provided a factory functionality to look up for the record and automatically detect the container implementation that can instantiate a correct container type instance. This factory also caches the record(s) within a request and can invalidate them by hand(s) within a process (if needs).

Factory usage
<?php

// Example of a record:
// [
//	'id' => 42,
//  'containertype' => 'container_micro_course',
// 	'fullname' => 'This is micro course container'
// ]

// The first time requesting container record - the record will be looked up and cached for next request.
$micro_course = \core_factory\factory::from_id(42);

var_dump($micro_course->id); // 42
var_dump($micro_course->containertype); // 'container_micro_course'