Sage 9 PSR-4 - Not loading or instantiating classes


#1

Hi all,

Have searched the net and forums prior to posting this but can’t seem to find an answer.

I see that with PSR-4 autoloading (based on code within Sage/its dependencies) we only need to “use” a namespace to require the file we need.

However, I can’t seem to load my files or even instantiate them.

I can see that in Sage composer.json we have

"psr-4": {
  "App\\": "app/"
}

So, naturally, I have added a file called ‘post-types.php’ inside of ‘app/’ which is a basic class to load other post types. The problem is that nothing in this file is being executed - unless I manually instantiate the class at the end of the file.

app/themes/fabric/app/post-types.php

<?php

namespace App;

var_dump('outside class scope');

//use App\PostTypes\Event;

class PostTypes
{
    public function __construct()
    {
        //    new Event();
        var_dump('inside class constructor');
    }
}

Neither var_dumps work even though the first is outside of the class scope.
I know I could simply add ‘new PostTypes();’ at the bottom of this file, but surely I should have to do that with the autoloading? Seems to be the case when I look at other classes within vendor/
Can anyone point out what I’m doing wrong here? Feel like it must be something very simple that I’m missing.

Any help appreciated.
Thanks


#2

http://www.php-fig.org/psr/psr-4/

PSR-4 autoloading does not check for dashes. also

All class names MUST be referenced in a case-sensitive fashion.


#3

Thanks for pointing that out Kalen.

I’ve updated ‘post-types.php’ to ‘Post_Types.php’ and the class name from ‘PostTypes’ to ‘Post_Types’ based on the examples in the PSR-4 info page - however, the file still doesn’t seem to be loading.

Am I making an incorrect assumption that this file is supposed to be automatically loaded or do I actually have to reference it somewhere like in functions.php alongside the array at the bottom of this snippet?

array_map(function ($file) use ($sage_error) {
    $file = "../app/{$file}.php";
    if (!locate_template($file, true, true)) {
        $sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
    }
}, ['helpers', 'setup', 'filters', 'admin', 'new/files/here.php]);

I am okay with doing that, but was hoping for autoloading.

Thanks again.


#4

I usually add all the files to the import array, only change it to one import per line to make it easier to read.

Have not used autoloading in sage but I don’t believe the files will be automatically loaded, they will be loaded when used/needed. You have to use the class somewhere for it to load. Something like

Post_Types::register()

or

new Post_Types()

in setup.php maybe?

The app folder is pretty full of files in many of my projects so I would probably change it to

"psr-4": {
  "App\\": "app/classes/"
}

Autoload post-types/*.php
#5

@KimH is right that you have to instantiate your class somehow before it actually does anything. Autoloading in PHP basically works like this:

  • Your give your script instructions for how to find things based on their namespacing (in this case, those instructions are PSR-4). i.e. “You can find My\Class in ./src/my/class.php.”
  • This doesn’t actually “do” anything, or cause anything to be executed on its own: It just tells your script where it can find things.
  • When, elsewhere in your script, you write, say, $class = new My\Class($args);, then PHP says “Oh, you told me I can find My\Class in ./src/my/class.php, I’ll go open that up and use the code in there to instantiate your class!”

The end result is similar to doing include('./src/my/class.php') in that it makes your class available for instantiation, but it saves you from having to type all those includes, and only loads files you actually use (in addition to probably have other benefits I’m unaware of). include will execute the entire file immediately; autoloading only executes the file when the class it contains is requested.

I think the reason you’re running into an issue is that autoloading is usually used to load classes, which do exactly nothing until they’re instantiated, and autoloading won’t even open up those files unless you try to instantiate a class. In order to fire the code in your PostTypes class, you’d need to do something like what @KimH suggested. Best practice is not to have functions that fire stuff off in files where you define classes, so I’d recommend something like the following:

// app/PostTypes.php
namespace App;

class PostTypes
{
    public static function register()
    {
         new static();
         // my memory is a little rusty; you might want to use `new self();` instead.
    }

    // Do your other class stuff
}
// app/setup.php

PostTypes::register();

#6

@KimH @alwaysblank thank you for the pointers - I see that my assumptions of how to use this was incorrect. Instantiating the class manually is all I needed and now everything is working as it should :slight_smile:
I’m not sure why I was thinking autoloading would handle this for me, to be honest, but this makes a lot more sense now.

Thanks!


#7

@KimH @alwaysblank I’m having the same problem. I need to use a payment class in a function inside controllers/front-page.php. This payment class need to be instantiate, because of it __construct function.

So I did this

Add payment-pagseguro.php inside App

namespace App;

class PaymentPagSeguro{

    private $environment;

    public function __construct( $environment ){
		...
	}

	...
}

Change Sage required files in functions.php

//line 61
['helpers', 'setup', 'filters', 'admin', 'payment-pagseguro']

And instantiate the class inside a function:

public function foo()
{
   $pagseguro = new PaymentPagSeguro('sandbox');
}

I would like to know if this is the right way.


#8

What errors are you seeing?


#9

No error. I just would like to know if this is the correct way to do.

Before, I tryed this way:

// controllers/front-page.php
    
use App\Pagseguro;

        class FrontPage extends Controller
        {

            public function foo()
            {
                $pagseguro = new App/Pagseguro('sandbox');
            }
        }

But it’s return a fatal error:

Uncaught Error: Call to undefined function App\Pagseguro()


#10

If it works, it’s probably the correct way.

I’d recommend reading up on the PSR-4 loading standard for a deeper understanding of how autoloading works. If you’re using soberwp/controller I’d recommend reading carefully through the documentation, and then closely examining the example controllers in sage. I promise that if you read through those sources carefully you’ll understand why your initial attempt failed.


#11

Fixed! I created a folder called payments inside app/controllers, change the namespace of app/controlers/payments/pagseguro.php to

namespace App\Payments;

And in front-page.php, I called this class using

use Payments\Pagseguro;

And instantiate this like this:

$pagseguro = new Payments\Pagseguro('sandbox');

Thanks again @alwaysblank