Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use with Laravel? #25

Closed
inmanturbo opened this issue Jul 23, 2023 · 14 comments
Closed

How to use with Laravel? #25

inmanturbo opened this issue Jul 23, 2023 · 14 comments
Assignees
Labels
good first issue 🥇 Good for newcomers question ❔ Further information is requested

Comments

@inmanturbo
Copy link

Hello, I see you've updated this wonderful idea to work with php 8.2

How do I use it with laravel?

@WalterWoshid
Copy link
Contributor

WalterWoshid commented Jul 23, 2023

Hi @inmanturbo,

to use it with Laravel, you can just install it like you would usually do (more instructions in the README.md file and in the Implicit Aspects Section or the Explicit Aspects Section if you want to add custom Aspects to your own classes)

  1. Install the package: composer require okapi/aop

  2. Create the Kernel.php file in your new app/Aop folder with following contents:

<?php

namespace App\Aop;

use App\Aop\Aspects\DiscountAspect;
use Okapi\Aop\AopKernel;

class MyKernel extends AopKernel
{
    // Define a list of your custom aspects
    protected array $aspects = [
        DiscountAspect::class,
    ];   
}
  1. Create your advices in the app/Aop/Aspects folder, e.g. DiscountAspect.php:
// Discount Aspect

<?php

namespace App\Aop\Aspects;

use Okapi\Aop\Attributes\Aspect;
use Okapi\Aop\Attributes\After;
use Okapi\Aop\Invocation\AfterMethodInvocation;

// Aspects must be annotated with the "Aspect" attribute
#[Aspect]
class DiscountAspect
{
    // Annotate the methods that you want to intercept with
    // "Before", "Around" or "After" attributes
    #[After(
        // Use named arguments
        // You can also use Wildcards (see Okapi/Wildcards package)
        class: Product::class . '|' . Order::class,
        method: 'get(Price|Total)',
    )]
    public function applyDiscount(AfterMethodInvocation $invocation): void
    {
        // Get the subject of the invocation
        // The subject is the object class that contains the method
        // that is being intercepted
        $subject = $invocation->getSubject();
        
        $productDiscount = 0.1;
        $orderDiscount   = 0.2;
        
        if ($subject instanceof Product) {
            // Get the result of the original method
            $oldPrice = $invocation->proceed();
            $newPrice = $oldPrice - ($oldPrice * $productDiscount);
            
            // Set the new result
            $invocation->setResult($newPrice);
        }
        
        if ($subject instanceof Order) {
            $oldTotal = $invocation->proceed();
            $newTotal = $oldTotal - ($oldTotal * $orderDiscount);
            
            $invocation->setResult($newTotal);
        }
    }
}
  1. Register your kernel in the bootstrap/app.php file after the <?php opening tag:
<?php

use App\Aop\MyKernel;

MyKernel::init();

Let me know if this helped you or if you have any errors while using it :)

@WalterWoshid WalterWoshid changed the title How to use with laravel? How to use with Laravel? Jul 23, 2023
@WalterWoshid WalterWoshid pinned this issue Jul 23, 2023
@inmanturbo
Copy link
Author

But where to register or initialize the Kernel? If I add it to a service provider or bootstrap/app.php I just get an error:

  The singleton instance of App\Aop\Kernel has already been initialized.
// Initialize the AOP Kernel
$kernel = App\Aop\Kernel::init();

Otherwise, if I do not add it, the kernel is just ignored and I get no errors and no applied advice

@inmanturbo
Copy link
Author

I should say, I see it working now in the browser, with the kernel initialized in bootstrap/app.php, but I am only getting the error in the terminal, when trying to run tests.

@WalterWoshid
Copy link
Contributor

WalterWoshid commented Jul 24, 2023

You're right, I missed that, sorry.

bootstrap/app.php is a good location, best to place it is after the include "../vendor/autoload.php" statement of the application life cycle (Before the code you want to intercept).

If you receive the error only when you run the tests, make sure your code doesn't call Kernel::init() twice. If you initialized the kernel in your tests as well, I think you don't actually have to do that, because bootstrap/app.php will be run for the tests. I will test it tomorrow with Laravel and see if there's an error somewhere!

@inmanturbo
Copy link
Author

inmanturbo commented Jul 24, 2023

Strangely, if I put it directly in public/index.php, right after require __DIR__.'/../vendor/autoload.php'; it continues to work in the browser but doesn't cause any errors in the test. No code is intercepted in the tests either though. It could have something to do with how orchestra builds the application.

In any case, the code doesn't call Kernel::init() more than the one time: there are no calls to it during tests or the test setup.

Here are my changes to a fresh jetstream project with livewire stack:

public/index.php:

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/

require __DIR__.'/../vendor/autoload.php';

+$aopKernel = App\Aop\Kernel::init();

app/Aop/Kernel.php:

<?php

namespace App\Aop;

use App\JetstreamAspect;
use Okapi\Aop\AopKernel;

// Extend from the "AopKernel" class
class Kernel extends AopKernel
{
    // Define a list of aspects
    protected array $aspects = [
        JetstreamAspect::class,
    ];   
}

app/JetstreamAspect.php:

<?php

namespace App;

use App\Actions\Fortify\CreateNewUser;
use Okapi\Aop\Attributes\Aspect;
use Okapi\Aop\Attributes\After;
use Okapi\Aop\Invocation\AfterMethodInvocation;

// Aspects must be annotated with the "Aspect" attribute
#[Aspect]
class JetstreamAspect
{
    // This method will be executed after the "create" method of the "App\Actions\Fortify\CreateNewUser" class
    #[After(
        class: CreateNewUser::class,
        method: 'create',
    )]
    public function afterCreate(AfterMethodInvocation $invocation)
    {
        // testing various intercepts here
       // register as new user in the browser to see this code run
       // I expect this to die and dump after this method is called during the RegistrationTest
        dd('executed after the "create" method of the "App\Actions\Fortify\CreateNewUser" class');
    }
}

@akelalix
Copy link

hi @inmanturbo

have you successfully setup it on laravel application?

when I add the kernel::init on the bootstrap/app.php just before return the $app, it was working but got some error Cannot declare class App\Aop\MyKernel, because the name is already in use when I tried to run the php artisan config:cache

thank you

@inmanturbo
Copy link
Author

This error is often from a mismatch from file name to class name. Is your file called 'Kernel.php' or 'MyKernel.php' ?

@akelalix
Copy link

Hi @inmanturbo, my class name is both MyKernel..

did you put it on the boostrap/app.php like this ?

$path = realpath(DIR.'/..') . '/app/Aop/MyKernel.php';
require($path);
$kernel = App\Aop\MyKernel::init();

Thank you

@WalterWoshid WalterWoshid self-assigned this Jul 31, 2023
@WalterWoshid WalterWoshid added the question ❔ Further information is requested label Jul 31, 2023
@WalterWoshid
Copy link
Contributor

Hi @inmanturbo, sorry for the late response, I will test it now with a fresh Laravel Jetstream installation.

@WalterWoshid
Copy link
Contributor

@inmanturbo I have found the problem. bootstrap/app.php should be the file where you add your kernel, also it does seem like the test framework bootstraps the application multiple times. In that case you can add an if check to the bootstrap/app.php file like this:

if (!\App\Aop\Kernel::isInitialized()) {
    \App\Aop\Kernel::init();
}

I will add an option in the future to ignore an already initialized kernel and make it the default behaviour!

@WalterWoshid
Copy link
Contributor

Hi @akelalix,

you don't have to require the class

$path = realpath(DIR.'/..') . '/app/Aop/MyKernel.php';
require($path);

because bootstrap/app.php will be executed right after require __DIR__.'/../vendor/autoload.php'; and it should automatically autoload all your classes.

In case PHP still doesn't know about your MyKernel.php file, try to run the composer dump-autoload command in your terminal.

@WalterWoshid WalterWoshid mentioned this issue Jul 31, 2023
23 tasks
@akelalix
Copy link

akelalix commented Aug 1, 2023

if (!\App\Aop\Kernel::isInitialized()) {
\App\Aop\Kernel::init();
}



    
      
    

      
    

    
  

Thank you @WalterWoshid .
I-remove the require then use the suggested

if (!\App\Aop\MyKernel::isInitialized()) { \App\Aop\MyKernel::init(); }

Thanks again

@WalterWoshid
Copy link
Contributor

WalterWoshid commented Sep 3, 2023

I have added the $throwExceptionOnDoubleInitialization option to the CodeTransformerKernel (which is extended by the AopKernel) and the default value is set to false which makes it ignore subsequent init() calls. By running composer update in your project, you should be able to use it without any changes and can also replace this code:

if (!\App\Aop\Kernel::isInitialized()) {
    \App\Aop\Kernel::init();
}

with this:

\App\Aop\Kernel::init();

@Cyrille37
Copy link
Contributor

Hello

It works fine with php 8.2, laravel 10.18 and php-aop 1.2.3 :-)

With first lines of bootstrap/app.php as:

<?php

use App\Aop\Kernel as AopKernel;
AopKernel::init();
...

@WalterWoshid WalterWoshid added the good first issue 🥇 Good for newcomers label Sep 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue 🥇 Good for newcomers question ❔ Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants