Helpers

Permalink 2 users found helpful
Ok, I've tried hours to figure out how can I use my old 5.6 (not talking about built-in helpers) helpers in 5.7.

Or is there a better way to use similar approach as old helpers? I would like to access my classes from several places.

Temposaur
View Replies: View Best Answer
Mainio replied on at Permalink Best Answer Reply
Mainio
First of all, one thing you should know before reading this. What used to be "helpers" in concrete5 are now called "services", so don't be confused by the naming.

1. Place your services (i.e. helpers) in your package's /src/Service folder, e.g. for a service named "Praise" /src/Service/Praise.php
2. Create your service class (this can be a copy of your old helper with new naming)
Should look something like this:
<?php
// /packages/your_package/src/Service/Praise.php
namespace Concrete\Package\YourPackage\Src\Service;
defined('C5_EXECUTE') or die("Access Denied.");
class Praise
{
    public function praise($name = 'concrete5')
    {
        return t("%s is cool", $name);
    }
}


3. Create a service provider in your src-folder. E.g. /src/PackageServiceProvider.php.
The contents of that should be something like this:
<?php
// /packages/your_package/src/PackageServiceProvider.php
namespace Concrete\Package\YourPackage\Src;
defined('C5_EXECUTE') or die("Access Denied.");
use \Concrete\Core\Foundation\Service\Provider as ServiceProvider;
class PackageServiceProvider extends ServiceProvider
{
    public function register()
    {
        $pkgHandle = 'your_package';
        $singletons = array(
            'helper/praise' => '\Concrete\Package\YourPackage\Src\Service\Praise',
        );
        foreach($singletons as $key => $value) {
            // This is for the legacy Loader, i.e. for calling Loader::helper('praise', 'your_package');


This is by the way optional step, you could also register the singletons without a service provider but this example just follows the conventions set by the core. In core, these are registered in service providers.

4. Initiate the service provider in your package.

<?php
// /packages/your_package/controller.php
namespace Concrete\Package\YourPackage;
defined('C5_EXECUTE') or die("Access Denied.");
use Core;
use Package;
use \Concrete\Package\YourPackage\Src\PackageServiceProvider;
class Controller extends Package
{
    protected $pkgHandle = 'your_package';
    protected $appVersionRequired = '5.7.2';
    protected $pkgVersion = '0.0.1';
    public function on_start()
    {
        $app = Core::getFacadeApplication();


5. Use it where you wish:
<?php echo Core::make('your_package/helper/praise')->praise(); ?>


This naming convention of the singleton (i.e. your_package/helper) is my own since I think this is much more convenient than what the core proposes for loading any package specific singletons. I guess this is just up to the package developer to decide since I don't think there are any existing conventions / suggestions for this.

With this example, you could also use the old style loading of the helper if you want but the new style is encouraged. But just for the record, this would work too:
<!-- Legacy way, not encouraged -->
<?php echo Loader::helper('praise', 'your_package')->praise(); ?>


6. ???

7. Profit!
Phallanx replied on at Permalink Reply
Phallanx
@Mainio
The "legacy" method should be encouraged as it is the abstraction that hides the implementation detail. Form over function again!.Sigh.
Mainio replied on at Permalink Reply
Mainio
OK, where did you read that? I haven't had much look at the docs.

Doesn't the IoC container also hide the implementation detail?

Why is the "Loader" class under the "Legacy" folder in the core? I don't think putting stuff in a "Legacy" folder would be encouraging their use... Although I might be wrong but it does sound very strange to me.

And what's the main benefit of using the legacy loader? I didn't quite get your point.

EDIT:
Additionally, why is core using Core::make() to get the helpers if its use is not encouraged? Or it's actually using both ways but in the refactored stuff it's using Core::make().
Phallanx replied on at Permalink Reply
Phallanx
@Mainio

Perhaps I should clarify.

It should be encouraged, but it will not be because I prefer function over form. The Loader::helper has been implemented as an abstraction to make it backwards compatible and it removes the requirement for the developer to know how to resolve the helper paths/namespaces and creates the routes. Basically they have implemented the helper API with the new architecture but conceptually it sits above where they want you to be programming.
JohntheFish replied on at Permalink Reply
JohntheFish
Strikes me as a step backwards. 2 files and classes plus an on_start handler where we used to just declare a helper class and call Loader::helper.

Does such complexity really mark progress and a superior architecture?
Korvin replied on at Permalink Reply
Korvin
@jtf Loader::helper went from this: https://github.com/concrete5/concrete5/blob/master/web/concrete/core... to this: https://github.com/concrete5/concrete5-5.7.0/blob/develop/web/concre...

That is orders of magnitude less complex. @manio is not correct in saying that helpers have become "services", there is no construct in the core named "service". Service providers simply manage registering groups of related instructions. The only difference here is that helpers are no longer IMPLICIT, they are explicitly defined by whoever expects to provide them.

Here's an example of the power of the new setup:
\Core::bind('helper/count_helper', function($app, $base=10, $start_int=0) {
    switch ($base) {
        case 2:
            return new \My\Site\Counter\BinaryCounter($start_int);
        case 10:
            return new \My\Site\Counter\DecimalCounter($start_int);
     }
     throw new \My\Site\Counter\InvalidBaseException($base);
});


@jtf I highly suggest you look into IOC and SOLID and come to these conclusions yourself.

@phallanx, we should not be encouraging any of the loader functions, they are deprecated because they are not intended to be used. I'm of the mind that the \Loader class should go away, it was added simply because the core used \Loader everywhere, and has become more and more useless as we've converted to stronger more explicit loading methods.
Mainio replied on at Permalink Reply
Mainio
@Korvin: OK, thanks for the clarifying the point on services. Although, I'm not sure how to explain that correctly, I could update the post.

My understanding was solely based on what I've heard from @Andrew:
http://andrewembler.com/posts/5-7-preview-developer-changes/...

Dispatcher extends the Laravel IoC Container, allowing for sensible binding of services (which replace helpers) for lazy loading and overriding.


I also probably heard him say something similar in some clip.
Korvin replied on at Permalink Reply
Korvin
Definitely a misnomer, helpers are no different than any other class and shouldn't be seen as such. There is nothing specifically wrong about your post other than alluding that service providers exist to register helpers :)

Service providers are just there to package up common items that need to be registered in runtime.
Mainio replied on at Permalink Reply
Mainio
@Korvin: Thanks for the clarifying vol. 2.

I don't think I explicitly stated that service providers are solely for registering helpers, so I don't think I need to edit the post. Let me know if you feel so and I can do so.
Mainio replied on at Permalink Reply
Mainio
@jtf

Also to add, that not all of that is necessary when defining a helper, just like stated in the original post. You could do the singleton biding straight in your Package controller, as said in the post.

I don't think it's conceptually any harder than the old way. But this gives you a lot more control.
Korvin replied on at Permalink Reply
Korvin
This is wrong. Deprecated methods and classes should be discouraged.
Mainio replied on at Permalink Reply
Mainio
@Korvin
Can you clarify where I have encouraged deprecated methods, so that I can correct the original post?
Korvin replied on at Permalink Reply
Korvin
This was a response to @phallanx
Mainio replied on at Permalink Reply
Mainio
OK, thanks.
mkly replied on at Permalink Reply
mkly
An old classic on some of these concepts linked below. It's Java but a lot of the modern PHP concepts are from Java land so the "when" and "whys" are still applicable.

http://martinfowler.com/articles/injection.html...
Phallanx replied on at Permalink Reply
Phallanx
@mkly

Who was it that was saying PHP is *not* Java?

This is more of a design decision over re-factoring module interfaces (using 3rd party components). A better discussion of the issues and the lessons learnt pertaining to what has happened in 5.7 from the addon developers perspective comes from the Python world which is a far better comparison to PHP than Java.

https://www.youtube.com/watch?v=o9pEzgHorH0#t=567...
Mainio replied on at Permalink Reply
Mainio
@Phallanx

How about keeping these points in their own topic, I've seen these come up in several threads now from multiple people. I think it's better to argue about the decisions in a central location than trying to convince everyone individually to change their minds.

I created one here:
http://www.concrete5.org/community/forums/5-7-discussion/5.7-archit...

And how about keeping the one-on-one debates in some other communication channel. A fight always needs at least two people, so that's not solely towards you.
TheRealSean replied on at Permalink Reply
TheRealSean
Thanks some great information in here,

How would I go about overwriting a core service/helper

In this instance I would like to replace the Sharing service to use custom classes instead of the Font Awesome classes it currently uses.

I've attempted to use the information above to register it as a service and load that from my new block override but currently experiencing a few issues getting it working as I would expect.

As I am attempting to use a custom version of the Sharing service I assume that I can change the path in the bootstrap/app? (or from within my package).
Mainio replied on at Permalink Reply
Mainio
Can you post the code? It might help to see what's wrong.

I've successfully replaced the image helper in core with this call in the /application/bootstrap/app.php (that you also referred to):
$app->bind('html/image', '\Application\Src\Html\Image');


EDIT: In fact, that's not the old image 'helper' but I think that's good for reference. If you want to replace the image helper the key would be 'helper/image'.
TheRealSean replied on at Permalink Reply
TheRealSean
I have attempted a couple of ways, so this might get a little messy with me trying to explain, each method I have attempted separately.

Method 1, add to the bootstrap to try and override the actual call? figured this would be a good start and a safe way if it worked? as I did not need to change the block
//in bootstrap/app
Core::bind('\Concrete\Src\Sharing\SocialNetwork', function($app, $params) {
   return new \Application\Src\Service\Social\Link($params[0]);
});

//No idea if the params are required I don't think so but it would not work either way


Method 2, Try over riding the block and service files
The old structure was
//concrete/src/Sharing/SocialNetwork

I have copied the SocialNetwork folder into the following and renamed it just to try keeping things simple(ish)
//application/src/service/social
and also into my package
//packages/my_package/src/service/social

Then in the block I am overriding I have changed the use from
//old and works
use Concrete\Core\Sharing\SocialNetwork\Link;
//new
use Application\Src\Service\Social\Link;



Method 3,
I also attempted to load in as a new service similar to the method you described further up and in the SocialServiceProvider.php (located in services, in both application/src and the package equivalent)
namespace Concrete\Package\my_packge\Src;
defined('C5_EXECUTE') or die("Access Denied.");
use \Concrete\Core\Foundation\Service\Provider as ServiceProvider;
class SocialServiceProvider extends ServiceProvider
{
    public function register()
    {
        $pkgHandle = 'my_package';
        $singletons = array(
            'helper/social' => '\Concrete\Package\MyPackage\Src\Service\Social\Link',
        );
        foreach($singletons as $key => $value) {
            $this->app->singleton('/packages/' . $pkgHandle . '/' . $key, $value);
            $this->app->singleton($pkgHandle . '/' . $key, $value);
        }


Each method I end up with the following error
Class \Concrete\Package\MyPackage\Src\Service\Social\Link does not exist
or 
Class 'Application\Src\Service\Social\Link' not found


Does that make sense?, to boil it all down I want to change this function in the

// concrete/src/service/social/Service
public function getServiceIconHTML()
    {
        return '<i class="block ' . $this->getIcon() . '"></i>';
    }
//To instead output
public function getServiceIconHTML()
    {
        return '<i class="custom-class ' . $this->getIcon() . '"></i>';
    }


And then also amend the getService function.

Thanks for your help
Mainio replied on at Permalink Reply
Mainio
What is the end goal of that override? I.e. what are you trying to do?
TheRealSean replied on at Permalink Reply
TheRealSean
I would like to be able to use custom icons instead of the default font-awesome ones. The code changes the class applied so I can link this to our current spritemap.

Assuming this worked I also wanted to replicate the functionality and include our own logo library of items that the user can re-order and add/remove items to the list. Similar to the current functionality of the Social Link block (If that makes sense)

I would normally use a number of image blocks within a stack. I would like to be able to do the following using foundation instead of bootstrap, however the assets we have are within a spritemap and I don't really want to add the html to a block or redactor, relying on the content editor to not muck up the layout.

<ul class="small-block-grid-3 large-block-grid-6 logos">
                  <li><span class="logo logo-bmw"></span></li>
                  <li><span class="logo logo-mini"></span></li>
                  <li><span class="logo  logo-vw"></span></li>
</ul>
Mainio replied on at Permalink Reply
Mainio
OK, in this case I would probably use the 2nd one of your solutions, if this is only for your specific block and does not need to apply globally.

I would as well create an own class but I would simply extend the core class. This way you don't have to copy the whole class:
namespace Application\Src\Sharing\SocialNetwork;
defined('C5_EXECUTE') or die("Access Denied.");
class Link extends \Concrete\Core\Sharing\SocialNetwork\Link
{
    public function getServiceIconHTML()
    {
        return '<i class="your-cls your-cls-' . $this->getIcon() . '"></i>';
    }
}



I think this should work OK, or at least I don't see any problem in it. If it's not, the problem might be where you're using it.

I haven't personally looked into the social links block but just guessing that it's using some external class which then uses the core's own Link class and not yours.

There's also one way I've found so far to override whole core classes, but it's really hacky and bad convention (although it's the only option I know), so I would rather not share it here. I think there should be a better solution coming at some point, or at least I've seen some discussions.
Kiesel replied on at Permalink Reply
Okay, there must be an easier way:

5.6.x:
Create class, create methods, include with:
$rh = Loader::helper('rating');


Now THAT's simple. What's the equivalent with no extra mumbo jumbo for 5.7? Like it was in 5.6. Don't tell me it doesn't exist. Steps forward should simplify things, not complicate them, right? Right??
mkly replied on at Permalink Reply
mkly
$rh = \Core::make('helper/rating');
Ricalsin replied on at Permalink Reply
Ricalsin
Is being a software Architect/Engineer that of a pauper of old? Kings and Queens want it 'simpler', while the populace (ie: 'classes') are to aspire to be less like a King and more socialist in their aspirations (Inversion Of Control) for the greater good (Composer) and for posterity (Dependencies). Rather than waitng for the KIng's (procedual) decree to tell them who/what/where, the forest of squatters is to prepare themselves in the best manner in anticipation of the King's request.

@mkly, whilst thou has prepared a fine response it doth take a few more keystrokes to implement! Off with your head! :)

(Sorry, I've been reading too much code.)
Kiesel replied on at Permalink Reply
Not the built in helpers. That's easy as before.
It's about the own helpers.