Using functions for HTML generation

This may allow you to adapt views of extensions without completely overriding them. For example I may have requirement that all external links in my website should have target="_blank" rel="nofollow noopener". I can create my custom helper with a() method which will do that transparently, but if some extension uses static call to Html::a() in its view, my helper will not be used. I need to override the whole view to change one Html::a() to use my helper instead of basic one. With DI I could just inject my helper to extension view and extension will use implementation I prefer.

1 Like

https://github.com/yiisoft/view/issues/48 not to miss it.

Yep, didn’t thought about extensions… But I was not thinking in making them global, just thought it could be a core based self-dynamic way to do it on app demand, for functions and for any namespaces and so. I was really in stratospheric sci-fi here. Thanks for the answer @samdark.

I honestly believe that we should maintain the array configuration in the widgets and static helpers, I think that injecting everything through the container di would be equal to Yii2, in my opinion the views are only the representation of the result, and using static helpers would be fine, for On the other hand we would leave things like Yii2 like helpers and widgets that do not need a great design.

For the ide we can use the Snippets, for all static helpers it should work very well.

Sorry if I am not completely relevant, to much to read.

I suppose it is possible to have both static method convinience and flexibility of object.
Static method class calls Service methods, and we can replace the Service.

For example ,we have two Services, BootstrapService and SemanticUIService, both implements IHtmlService. Then inside, for example, Html::input we call Yii::getApp()->htmlService->input.

So it Html class becomes actually a Facade, instead staticaly linked to code.

There’s no such global anymore.

1 Like

Html static class could be initialized first somewhere with required service, for example, BootstrapUIService, this service could use other services, for example, CoreHtmlService, InlineCssService, AssetsService. So we could easily change functionality.

Html {
   private static $UIService;
   public static Init(IUIService $UIService) {
      self::$UIService = UIService;
   }
}

To optimize performance:

Html {
   private static $serviceLocator: IServiceLocator;
   private static $UIService: ?IUIService;

   // Initialization in bootstrap
   public static Init(IServiceLocator $serviceLocator) {
      self::$serviceLocator =  $serviceLocator;
   }

  private static ensureUIService() {
      if (! self::$UIService)
           self::$UIService = self::$serviceLocator->createOrGet(IUIService::class); 
  }

  public static alert($message) {
	self::ensureUIService();
	// main code
 }
}

This will still rely on global state, which Yii 3 tries to avoid.

The problem is that UI code is tightly coupled, and for me this was en issue, actually the only real issue with Yii.
Do you know better solutions?

Yes, I proposed it already:

This is completely the same with as I understand. We just use instance instead static class.

But in your solution exists one advantage. We can easily use another UIService for some part of appication. But @samdark proposed to use static class, thats why I did such a solution. But there is no problem to compose those approaches and give possibility to choose.

And that changes everything :D. You can have only one Html::$UIService at the time, and changing it in one place will affect any other places too. With local instances you have much more control and less unexpected side effects.

Agreed, but we can combine those aproaches. For simple projects we can use Facade, but advanced user can type more.

And I think it is, in general, a viable approach. To give 2 options in framework, easy and highly customisable. Easy way would help new people to start and the hard way gives satisfaction to users with high requirments. “Easy to get, hard to master”

@rob006 implemented it like the following.

Setting/overwriting helper locally:

$view->render('index', [
    'html' => new Html(),
]);

Setting helper globally:

$view->setDefaultParameters([
    'html' => new Html(),
]);

Local overrides global in case name is the same.

If I’m writing an extension, how can I rely on this? If html helper will not be configured in default parameters at app level, then it will not be available in view - I need to inject Html instance myself to make sure that it is available. But in that case end user will not be able to overwrite this helper at app level.

What would you suggest?

I don’t know, but this should be more like a contract than optional feature.

On the other hand in view you could do something like:

$html ??= new Html();

And it would work as intended - helper can be injected at view or controller level, with default fallback. It should also work like a typehint (IDE should recognize that $html is Html instance).

A view should be an object taking params, so html would be a param.
An extension using views, has params too.

So you would pass a Html object to the view/extension. Well its up to the extension developer what he wants to be customizable.
He could also prevent manipulation of a(), by not providing any params for those methods/objects.