This is the documentation for concrete5 version 5.6 and earlier. View Current Documentation

NOTE: The php class structures described below are an experimental technique and should (a) - be used with caution, and (b) - be very thoroughly tested.

Here is a solution for a block controller chicken and egg problem. You have a package with two block types in it, A and B. Block type B has a lot of shared functionality with block type A, so you would like the controller for block B to inherit from the controller for block A.

At first, it seems very simple. PHP provides class inheritance. You should be able to write:

blocks/my_block_a/controller.php

class MyBlockABlockController extends BlockController {
  protected $btTable = "btMyBlockA";
  // more block A class
}

and:

blocks/my_block_b/controller.php

Loader::block('my_block_a');
class MyBlockBBlockController extends MyBlockABlockController {
    protected $btTable = "btMyBlockB";
  // block B class inheriting from block A
}

The catch is loading the controller for block type A. Block controllers also need to be loaded when a package installs the blocks.

public function install() {
  $pkg = parent::install();
  BlockType::installBlockTypeFromPackage('my_block_a', $pkg);
  BlockType::installBlockTypeFromPackage('my_block_b', $pkg);
}

However, as far as concrete5 is concerned, neither block actually exists in the database until after the package is installed, so at the time you try to install block B, block A does not yet exist. If try to do the above, block B will fail to install and the package installation will end with an error!

A simple solution can be to split them into two separate packages, where package B requires package A. However, it seems a little heavy handed to create a second package when all you wanted was a small variation in a block.

Another solution that has been used in the past is to make both A and B inherit from a library class that inherits from the base BlockController class. It works, but it was never something I felt really comfortable with.

My alternate solution is a bit of class inheritance judo. The controller for block A is declared as normal.

blocks/my_block_a/controller.php

class MyBlockABlockController extends BlockController {
  protected $btTable = "btMyBlockA";
  // more block A class
}

Then, in the controller for block B, we create a conditional inheritance structure, testing to see if block A exists and inheriting from it when available (which is any time after the package installation is complete), or where block A is not available, simply inheriting from the base BlockController. To actually achieve this without repeating all of the code for block B's controller we need an intermediary class. the controller from B inherits from the intermediary, and the complete intermediary inherits from either A or the base BlockController.

blocks/my_block_b/controller.php

if(is_object(BlockType::getByHandle('my_block_a'))){
  Loader::block('my_block_a');
  abstract class MyBlockBIntermediary extends MyBlockABlockController {
    public function inheritance_complete(){
      return true;
    }
  }
}else{
  abstract class MyBlockBIntermediary extends BlockController {
    public function inheritance_complete(){
      return false;
    }
  }
}

class MyBlockBBlockController extends MyBlockBIntermediary {
  protected $btTable = "btMyBlockB";
  // block B class inheriting from block A
}

With just a few additional lines at the top of controller B we can now override and extend just where needed within the controller for B, simply inheriting everything we need from block A.

During installation, block B misses out all the inheritance from block A and just inherits from the core BlockController. After installation, A is available and B inherits from it.

You can test this by calling the test inheritance_complete() in the view for block B.

echo $controller->inheritance_complete();

You don't actually need to declare that function, its just there to demonstrate that everything is working as it should.

If you also want to re-use add/edit or view files from block a in block b, that is much simpler with a bit of path traversal in the inc() calls.

blocks/my_block_b/view.php

$this->inc('../my_block_a/view.php');

REMEMBER: The php class structures described above are an experimental technique and should (a) - be used with caution, and (b) - be very thoroughly tested.

Read more How-Tos by JohntheFish.

Loading Conversation