Continue running after exception is thrown

Hi guys,

I’m in the process of building a site editor that allows admins to insert widgets onto a page dynamically.

The widgets available for dropping onto the page will be developed and maintained by developers of varying skill levels.

Currently I have a block of code which encapsulates the rendering of each section which contains a group of widgets inside a try/catch statement.

My problem is that if one of these widgets has a bug or hasn’t been configured correct it will throw some sort of exception/error.

When this happens I want to just display some text in place of that widget to the effect of "This block could not load" and then continue loading the rest of the page with all the other widgets.

At present when an error/exception is thrown it seems to halt the entire loading of the page rather than continuing the rendering process.

I’ve included the code below, just to clarify “Block” refers to the widget and “Section” refers to the container of the group of widgets. So a page would have a layout with multiple sections (named header-left, header-right, menu, above-content, below-content, footer etc.) which would contain multiple widgets / blocks (such as MainMenuBlock, HtmlBlock, BookListBlock etc.)

Layout would look like:




<html>

<body>

<div id="top">

    <div class="container">

        <div class="row">

            <?= SectionWidget::widget([

                'id' => 'header-left',

            ]); ?>


            <?= SectionWidget::widget([

                'id' => 'header-right',

            ]); ?>

        </div>


        <div class="row">

            <?= SectionWidget::widget([

                'name' => 'main-menu',

            ]); ?>

        </div>

    </div>

</div>

</body>

</html>



And then SectionWidget::widget would call renderBlocks() as seen below:




...

    public function run()

    {

        $this->openTag();

        $this->renderBlocks();

        $this->closeTag();

    }


    public function renderBlocks()

    {

        $items = ConfigSiteBlocks::getBySectionNameAsArray($this->name);


        $count = 0;

        foreach ($items as $item)

        {

            try

            {

                // prepare config

                $config = Json::decode($item['Config']);


                // set id if not passed in via config

                if (empty($config['id']))

                {

                    $config['id'] = "block-".Inflector::camel2id($this->name)."-$count";

                    $count++;

                }


                // get instance of block

                $block = Block::getInstance($item['ConfigSiteBlocksId'], $item['ConfigSiteSharedBlocksId'], $item['ClassName'], $config);


                // render the block

                $block->openTag();

                $block->run();

                $block->closeTag();

            }

            catch (Exception $e)

            {

                echo '<div>Block could not be loaded</div>';

            }

        }

    }

...



You need to check for every type of config setting and make sure they are correct (always assume a user is going to screw something up) before letting a user save the settings.

Also, if you put error checks in all of the working components (model , views, and controllers) for each of your widgets then you will not have any issue.

So in your case check before a user save the


$item['Config']

Put checks in the process that generates the


$config['id']

$item['ConfigSiteBlocksId']

$item['ConfigSiteSharedBlocksId']

in your functions


getBySectionNameAsArray

camel2id



Then if there are other config settings in those functions put checks in those functions too.

If you do this correctly then you will dramatically reduce chance of errors being thrown and halting your loading process.

The main issue I’m trying to solve is catching any errors inside the widget code that might be thrown.

Because its not me developing these widgets all the time I can’t be certain that the developers will always write error catching code in their widgets.

The part of the code above where it executes the "$block->run()" is where all the code from the developer is held. An example of this might look like:


class HtmlBlock extends Block

{

    /**

     * @var string

     */

    public $content;


    /**

     * Validation rules

     * @return array

     */

    public function rules()

    {

        return array_merge_recursive(parent::rules(), [

            ['content', 'safe']

        ]);

    }


    /**

     * @return array

     */

    public function attributeLabels()

    {

        return array_merge_recursive(parent::attributeLabels(), [

            'content' => 'Content'

        ]);

    }


    /**

     * @return string

     */

    public static function getName()

    {

        return 'Html Content';

    }


    /**

     * Render/init this block

     */

    public function run()

    {

        echo $this->content;

    }


    /**

     * @param \yii\widgets\ActiveForm $form

     * @return string

     */

    public function renderForm($form)

    {

        return $this->render('form', ['form' => $form]);

    }

}

Where the "Block::run()" method executes the displaying of the block, this is where the troublesome code might arise and where I need to be able to catch anything that gets thrown here with the wrapper code in my original post, the problem is that even when I have the wrapper code in place it still halts execution of the rest of the page.