:gem: Go! AOP PHP - modern aspect-oriented framework for the new level of software development

Overview

Go! Aspect-Oriented Framework for PHP

Go! AOP is a modern aspect-oriented framework in plain PHP with rich features for the new level of software development. The framework allows cross-cutting issues to be solved in the traditional object-oriented PHP code by providing a highly efficient and transparent hook system for your exisiting code.

GitHub Workflow Status GitHub release Total Downloads Daily Downloads SensioLabs Insight Minimum PHP Version License

Features

  • Provides dynamic hook system for PHP without changes in the original source code.
  • Doesn't require any PECL-extentions (php-aop, runkit, uopz) and DI-containers to work.
  • Object-oriented design of aspects, joinpoints and pointcuts.
  • Intercepting an execution of any public or protected method in a classes.
  • Intercepting an execution of static methods and methods in final classes.
  • Intercepting an execution of methods in the traits.
  • Intercepting an access to the public/protected properties for objects.
  • Hooks for static class initialization (after class is loaded into PHP memory).
  • Hooks for object initialization (intercepting new keywords).
  • Intercepting an invocation of system PHP functions.
  • Ability to change the return value of any methods/functions via Around type of advice.
  • Rich pointcut grammar syntax for defining pointcuts in the source code.
  • Native debugging for AOP with XDebug. The code with weaved aspects is fully readable and native. You can put a breakpoint in the original class or in the aspect and it will work (for debug mode)!
  • Can be integrated with any existing PHP frameworks and libraries (with or without additional configuration).
  • Highly optimized for production use: support of opcode cachers, lazy loading of advices and aspects, joinpoints caching, no runtime checks of pointcuts, no runtime annotations parsing, no evals and __call methods, no slow proxies and call_user_func_array(). Fast bootstraping process (2-20ms) and advice invocation.

What is AOP?

AOP (Aspect-Oriented Programming) is an approach to cross-cutting concerns, where these concerns are designed and implemented in a "modular" way (that is, with appropriate encapsulation, lack of duplication, etc.), then integrated into all the relevant execution points in a succinct and robust way, e.g. through declarative or programmatic means.

In AOP terms, the execution points are called join points. A set of those points is called a pointcut and the new behavior that is executed before, after, or "around" a join point is called advice. You can read more about AOP in Introduction section.

Installation

Go! AOP framework can be installed with composer. Installation is quite easy:

  1. Download the framework using composer
  2. Create an application aspect kernel
  3. Configure the aspect kernel in the front controller
  4. Create an aspect
  5. Register the aspect in the aspect kernel

Step 0 (optional): Try demo examples in the framework

Ask composer to create new project in empty directory:

composer create-project goaop/framework

After that just configure your web server to demos/ folder and open it in your browser. Then you can look at some demo examples before going deeper into installing it in your project.

Step 1: Download the library using composer

Ask composer to download the latest version of Go! AOP framework with its dependencies by running the command:

composer require goaop/framework

Composer will install the framework to your project's vendor/goaop/framework directory.

Step 2: Create an application aspect kernel

The aim of this framework is to provide easy AOP integration for your application. You have to first create the AspectKernel class for your application. This class will manage all aspects of your application in one place.

The framework provides base class to make it easier to create your own kernel. To create your application kernel, extend the abstract class Go\Core\AspectKernel

<?php
// app/ApplicationAspectKernel.php

use Go\Core\AspectKernel;
use Go\Core\AspectContainer;

/**
 * Application Aspect Kernel
 */
class ApplicationAspectKernel extends AspectKernel
{

    /**
     * Configure an AspectContainer with advisors, aspects and pointcuts
     *
     * @param AspectContainer $container
     *
     * @return void
     */
    protected function configureAop(AspectContainer $container)
    {
    }
}

3. Configure the aspect kernel in the front controller

To configure the aspect kernel, call init() method of kernel instance.

// front-controller, for Symfony2 application it's web/app_dev.php

include __DIR__ . '/vendor/autoload.php'; // use composer

// Initialize an application aspect container
$applicationAspectKernel = ApplicationAspectKernel::getInstance();
$applicationAspectKernel->init([
        'debug'        => true, // use 'false' for production mode
        'appDir'       => __DIR__ . '/..', // Application root directory
        'cacheDir'     => __DIR__ . '/path/to/cache/for/aop', // Cache directory
        // Include paths restricts the directories where aspects should be applied, or empty for all source files
        'includePaths' => [
            __DIR__ . '/../src/'
        ]
]);

4. Create an aspect

Aspect is the key element of AOP philosophy. Go! AOP framework just uses simple PHP classes for declaring aspects, which makes it possible to use all features of OOP for aspect classes. As an example let's intercept all the methods and display their names:

// Aspect/MonitorAspect.php

namespace Aspect;

use Go\Aop\Aspect;
use Go\Aop\Intercept\FieldAccess;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\After;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use Go\Lang\Annotation\Pointcut;

/**
 * Monitor aspect
 */
class MonitorAspect implements Aspect
{

    /**
     * Method that will be called before real method
     *
     * @param MethodInvocation $invocation Invocation
     * @Before("execution(public Example->*(*))")
     */
    public function beforeMethodExecution(MethodInvocation $invocation)
    {
        echo 'Calling Before Interceptor for: ',
            $invocation,
            ' with arguments: ',
            json_encode($invocation->getArguments()),
            "<br>\n";
    }
}

Easy, isn't it? We declared here that we want to install a hook before the execution of all dynamic public methods in the class Example. This is done with the help of annotation @Before("execution(public Example->*(*))") Hooks can be of any types, you will see them later. But we don't change any code in the class Example! I can feel your astonishment now.

5. Register the aspect in the aspect kernel

To register the aspect just add an instance of it in the configureAop() method of the kernel:

// app/ApplicationAspectKernel.php

use Aspect\MonitorAspect;

//...

    protected function configureAop(AspectContainer $container)
    {
        $container->registerAspect(new MonitorAspect());
    }

//...

6. Optional configurations

6.1 Custom annotation cache

By default, Go! AOP uses Doctrine\Common\Cache\FilesystemCache for caching annotations. However, if you need to use any other caching engine for annotation, you may configure cache driver via annotationCache configuration option of your application aspect kernel. Only requirement is that cache driver implements Doctrine\Common\Cache\Cache interface.

This can be very useful when deploying to read-only filesystems. In that case, you may use, per example, Doctrine\Common\Cache\ArrayCache or some memory-based cache driver.

6.2 Support for weaving Doctrine entities (experimental, alpha)

Weaving Doctrine entities can not be supported out of the box due to the fact that Go! AOP generates two sets of classes for each weaved entity, a concrete class and proxy with pointcuts. Doctrine will interpret both of those classes as concrete entities and assign for both of them same metadata, which would mess up the database and relations (see https://github.com/goaop/framework/issues/327).

Therefore, a workaround is provided with this library which will sort out mapping issue in Doctrine. Workaround is in form of event subscriber, Go\Bridge\Doctrine\MetadataLoadInterceptor which has to be registered when Doctrine is bootstraped in your project. For details how to do that, see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html.

Event subscriber will modify metadata entity definition for generated Go! Aop proxies as mapped superclass. That would sort out issues on which you may stumble upon when weaving Doctrine entities.

7. Contribution

To contribute changes see the Contribute Readme

Documentation

Documentation about Go! library can be found at official site. If you like this project, you could support it via Flattr this

Comments
  • Annotation use docs

    Annotation use docs

    Hi all,

    We are currently investigating your awesome library for our project, and thank you for very much for this project.

    I was trying to find any documentation about use of annotations except the examples. Is there any docs about how to use all types of annotation (except the examples)?

    Thanks,

    Support 
    opened by ShurikAg 53
  • Possible to use with sub-classes?

    Possible to use with sub-classes?

    Hi,

    Is it possible to somehow add an aspect to a class and all its sub-classes? Say we have parent class A and B as its child. I'd like to add smth like this:

    /**
    * @Before("execution(public A+->*(*))")
    */
    

    and, if B->method1() is executed, then it is also affected.

    I know that there's a way do this with interfaces, but I am looking for this particular solution.

    I am using Go AOP version 0.6.0.

    Support 
    opened by andreyvk 26
  • Cannot declare class because the name is already in use

    Cannot declare class because the name is already in use

    Following the tutorial, as it is, for a project I am working on gave 'class not found exception' in:

    1. configuring the ApplicationAspectKernel in the front controller,
    2. registering aspect in aspect kernel, and
    3. declaring pointcut in an aspect for the methods of some class

    To solve this, I added include statements:

    1. include '../application/ApplicationAspectKernel.php'; for my project's front controller
    2. include 'aspect/MonitorAspect.php'; in ApplicationAspectController.php
    3. include '../application/controller/CallbackController.php'; in MonitorAspect.php (CallbackController is the class whose methods I want to intercept).

    On doing this I get the error PHP Fatal error: Cannot declare class CallbackController, because the name is already in use in /var/www/html/vyom-crm-frontend/application/controller/CallbackController.php on line 0

    [Edit]: I removed the third include above. This resolved the above error. Now, the interceptor didn't work with the following pointcut

    @Before("execution(public CallbackController->*(*))")

    This pointcut works - @Before("execution(public **->*(*))") But I don't want to intercept all methods of all classes. There is a specific method in CallbackController that I want to intercept.

    Please help me solve this. Thanks!

    Support 
    opened by samyak45 24
  • filemtime(): stat failed in Caching Transformer

    filemtime(): stat failed in Caching Transformer

    Getting the error:

    filemtime(): stat failed for php://filter/read=go.source.transforming.loader/resource=/Applications/....

    What is the prefix: php://filter/read=go.source.transforming.loader/resource= and why would that be in the path for filemtime ?

    Support 
    opened by markNZed 22
  • Using annotation always resulting in writing to cache

    Using annotation always resulting in writing to cache

    Given an annotation:

    /**
     * @Annotation
     * @Target("METHOD")
     */
    class MyAnnotation extends Annotation
    {
        /**
         * @var string
         */
        public $name = null;
    
        /**
         * @var string
         */
        public $tag = null;
    }
    

    Given an aspect:

    /**
         *
         * @param MethodInvocation $invocation
         *
         * @Around("@execution(MyAnnotation)")
         */
        public function aroundInvocation(MethodInvocation $invocation)
        {
    
            /** @var MyAnnotation $annotation */
            $annotation = $invocation->getMethod()->getAnnotation(MyAnnotation::class);
            $name = $annotation->name;
        }
    

    The problem is that a call to $invocation->getMethod()->getAnnotation(MyAnnotation::class); results in a call to FileCacheReader::getAnnotations. Which will get the className like so: $class = $method->getDeclaringClass();

    The declaring class in this case is always Class__AopProxied which is not cached. By the way, I'm using Features::PREBUILT_CACHE as I have a read-only file systems.

    Error: The directory "/var/www/html/docroot/bootstrap/cache/aspect/_annotations" is not writable. Both, the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package.

    Is there any workaround for this issue? Thanks

    Bug Support 
    opened by paventuri 20
  • sf2 integration issue

    sf2 integration issue

    AppAspectKernel.php:

    <?php
    
    use Demo\Aspect\SecurityAspect;
    use Go\Core\AspectKernel;
    use Go\Core\AspectContainer;
    
    /**
     * Application Aspect Kernel
     */
    class AppAspectKernel extends AspectKernel
    {
        /**
         * Configure an AspectContainer with advisors, aspects and pointcuts
         *
         * @param AspectContainer $container
         */
        protected function configureAop(AspectContainer $container)
        {
            $container->registerAspect(new SecurityAspect());
        }
    }
    
    

    loading kernel in the web/app.php:

    ...
    require_once __DIR__.'/../app/AppAspectKernel.php';
    $applicationAspectKernel = AppAspectKernel::getInstance();
    $applicationAspectKernel->init([
        'debug' => true,
        'cacheDir'  => __DIR__ . '/../app/cache/aspect/',
        'appDir' => __DIR__ . '/../',
        'includePaths' => [__DIR__ . '/../src/'],
    ]);
    ...
    

    the aspect itself:

    <?php
    
    namespace Demo\Aspect;
    
    use Go\Aop\Aspect;
    use Go\Aop\Intercept\MethodInvocation;
    use Go\Lang\Annotation\Before;
    use Symfony\Component\Security\Core\Exception\AccessDeniedException;
    
    /**
     * Security aspect
     */
    class SecurityAspect implements Aspect
    {
        /**
         * Method that will be called before real method
         *
         * @param MethodInvocation $invocation Invocation
         * @Before("@execution(Demo\Aspect\Annotation\Securable)")
         */
        public function beforeMethodExecution(MethodInvocation $invocation)
        {
            $controller = $invocation->getThis();
            $user = $controller->getUser();
            $role = $invocation->getMethod()->getAnnotation('Demo\Aspect\Annotation\Securable')->role;
            if (!$user->hasAccessToPage($role)) {
                throw new AccessDeniedException();
            }
        }
    }
    
    

    getting error:

    2015/04/03 15:07:59 [error] 11226#0: *6454 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Class 'TokenReflection\Exception\RuntimeException' not found in /var/www/vendor/andrewsville/php-token-reflection/TokenReflection/ReflectionNamespace.php on line 156
    PHP message: PHP Stack trace:
    PHP message: PHP   1. {main}() /var/www/web/app.php:0
    PHP message: PHP   2. Symfony\Component\HttpKernel\Kernel->handle() /var/www/web/app.php:48
    PHP message: PHP   3. Symfony\Component\HttpKernel\Kernel->boot() /var/www/app/bootstrap.php.cache:2375
    PHP message: PHP   4. Symfony\Component\HttpKernel\Kernel->initializeBundles() /var/www/app/bootstrap.php.cache:2343
    PHP message: PHP   5. AppKernel->registerBundles() /var/www/app/bootstrap.php.cache:2513
    PHP message: PHP   6. spl_autoload_call() /var/www/app/bootstrap.php.cache:11
    PHP message: PHP   7. Symfony\Component\Debug\DebugClassLoader->loadClass() /var/www/app/bootstrap.php.cache:11
    PHP message: PHP   8. Go\Instrument\ClassLoading\SourceTransformingLoader->filter() /var/www/app/bootstrap.php.cache:151
    PHP message: PHP   9. Go\Instrument\ClassLoading\SourceTransformingLoader->transformCode() /var/www/vendor/lisachenko/go-aop-php/src/Instrument/ClassLoading/SourceTransformingLoader.php:102
    PHP message: PHP  10. Go\Instrument\Transformer\CachingTransformer->transform() /var/www/vendor/lisachenko/go-aop-php/src/Instrument/ClassLoading/SourceTransformingLoader.php:146
    PHP message: PHP  11. Go\Instrument\Transformer\CachingTransformer->processTransformers() /var/www/vendor/lisachenko/go-aop-php/src/Instrument/Transformer/CachingTransformer.php:117
    PHP message: PHP  12. Go\Instrument\Transformer\WeavingTransformer->transform() /var/www/vendor/lisachenko/go-aop-php/src/Instrument/Transformer/CachingTransformer.php:141
    PHP message: PHP  13. TokenReflection\ReflectionClass->getParentClassNameList() /var/www/vendor/lisachenko/go-aop-php/src/Instrument/Transformer/WeavingTransformer.php:109
    
    

    i figured out that it can't find that class only in the closure here https://github.com/lisachenko/go-aop-php/blob/master/src/Core/AspectKernel.php#L243-L260. Outside the closure it is able to find that class. Any ideas? (PHP 5.5.20)

    Known Issue Support Compatibility 
    opened by AlexKovalevych 20
  • AOP is not working with ZendOptimizer+ in the debug mode

    AOP is not working with ZendOptimizer+ in the debug mode

    I'm trying to do a integration in a framework I'm bulding (Im using symfony HttpFoundation)

    here is what I got so far

    <?php
    
    include __DIR__ . '/../vendor/lisachenko/go-aop-php/src/Go/Core/AspectKernel.php';
    
    use Go\Core\AspectKernel;
    use Go\Core\AspectContainer;
    
    /**
     * Application Aspect Kernel
     */
    class ApplicationAspectKernel extends AspectKernel
    {
    
        /**
         * Configure an AspectContainer with advisors, aspects and pointcuts
         *
         * @param AspectContainer $container
         *
         * @return void
         */
        protected function configureAop(AspectContainer $container)
        {
            $container->registerAspect(new Nucleus\Cache\Caching());
        }
    }
    
    ApplicationAspectKernel::getInstance()->init(array(
        'appLoader' => __DIR__ . '/../vendor/autoload.php',
        // Configuration for autoload namespaces
        'autoloadPaths' => array(
            'Go' => __DIR__ . '/../vendor/lisachenko/go-aop-php/src/',
            'TokenReflection' => __DIR__ . '/../vendor/andrewsville/php-token-reflection/',
            'Doctrine\\Common' => __DIR__ . '/../vendor/doctrine/common/lib/',
            'Dissect' => __DIR__ . '/../vendor/jakubledl/dissect/src/'
        ),
        'includePaths' => array(
            __DIR__ . '/../vendor',
            __DIR__ . '/../src'
        )
    ));
    
    $request = Symfony\Component\HttpFoundation\Request::createFromGlobals();
    

    And a Aspect for testing

    <?php 
    
    namespace Nucleus\Cache;
    
    use Go\Aop\Aspect;
    use Go\Aop\Intercept\FieldAccess;
    use Go\Aop\Intercept\MethodInvocation;
    use Go\Lang\Annotation\After;
    use Go\Lang\Annotation\Before;
    use Go\Lang\Annotation\Around;
    use Go\Lang\Annotation\Pointcut;
    
    /**
     * Monitor aspect
     */
    class Caching implements Aspect
    {
    
        /**
         * Method that will be called before real method
         *
         * @param MethodInvocation $invocation Invocation
         * @Before("execution(public *->*(*))")
         */
        public function beforeMethodExecution(MethodInvocation $invocation)
        {
            $obj = $invocation->getThis();
            echo 'Calling Before Interceptor for method: ',
                 is_object($obj) ? get_class($obj) : $obj,
                 $invocation->getMethod()->isStatic() ? '::' : '->',
                 $invocation->getMethod()->getName(),
                 '()',
                 ' with arguments: ',
                 json_encode($invocation->getArguments()),
                 "<br>\n";
        }
    }
    

    nothing is happening...

    I check that the filter is properly registered using the stream_get_filters function;

    array (size=13)
      0 => string 'convert.iconv.*' (length=15)
      1 => string 'string.rot13' (length=12)
      2 => string 'string.toupper' (length=14)
      3 => string 'string.tolower' (length=14)
      4 => string 'string.strip_tags' (length=17)
      5 => string 'convert.*' (length=9)
      6 => string 'consumed' (length=8)
      7 => string 'dechunk' (length=7)
      8 => string 'zlib.*' (length=6)
      9 => string 'bzip2.*' (length=7)
      10 => string 'mcrypt.*' (length=8)
      11 => string 'mdecrypt.*' (length=10)
      12 => string 'go.source.transforming.loader' (length=29)
    

    And the path return by FilterInjectorTransformer::rewrite

    php://filter/read=go.source.transforming.loader/resource=D:\nucleus\nucleus\web/../vendor/autoload.php
    

    The filter function of SourceTransformingLoader is never call, probably whe it doesn't work...

    Is there something I don't do correctly ? Or maybe a comflict with other filter and it never reach th filter that is registered ?

    Known Issue 
    opened by mpoiriert 17
  • Error with class/file that have specific instruction at the end (ex: Propel1 model classes)

    Error with class/file that have specific instruction at the end (ex: Propel1 model classes)

    I'm using propel 1 in a project and I get a issue with the generated file.

    At the end of the file propel call a static method on the current declared class. Since the class with the good name is appended to the class it cannot be found.

    Here is a exemple of before and after:

    //Before
    class BaseFilePeer
    {
    }
    
    BaseFilePeer::buildTableMap();
    
    
    //After
    class BaseFilePeer__AopProxied
    {
    }
    
    BaseFilePeer::buildTableMap();
    
    abstract class BaseFilePeer extends BaseItemPeer__AopProxied implements \Go\Aop\Proxy
    {
    }
    

    I've tried modifying the WeavingTransformer to append the child class just after the first class but the ReflectionElement just give access to the last line of the class and not the 'char' position in the file (it's probably why you don't support 2 namespaces in files).

    Do you have any suggestion because this is a big issue for my project...

    Known Issue 
    opened by mpoiriert 15
  • [Feature] Implementation of AST+token modification instead of direct source transforming

    [Feature] Implementation of AST+token modification instead of direct source transforming

    This PR changes the logic of transformers, they can use AST now and operate directly on token stream using token positions from AST to filter interesting places.

    Improvement 
    opened by lisachenko 14
  • Execution + multiple !within in pointcut expression is not working

    Execution + multiple !within in pointcut expression is not working

    Hi,

    I have the following expression:

    /**
     * @Before("execution(public **\controllers\*Controller->handle_*(*)) && !within(**\base\controllers\*Controller) && !within(**\common\controllers\LoginController)")
     */
    

    It seems that only the first within is considered and the latter isnt working. Is that expected behavior?

    Pointcut Bug 
    opened by andreyvk 14
  • Allow private property access

    Allow private property access

    It's a slight inconvenience that an AOP framework imposes an implementation on me by forcing my properties to be at least protected.

    I'd like to initialise values of annotated properties:

    class FindByAspect implements Aspect
    {
        /**
         * @Before("@access(Zalas\Annotation\FindBy)")
         */
        public function beforeFieldAccess(FieldAccess $fieldAccess): void
        {
            if (null === $fieldAccess->getValue() && FieldAccess::READ === $fieldAccess->getAccessType()) {
                $field = $fieldAccess->getField()->name;
                $setter = \Closure::bind(function ($object, $value) use ($field) {
                    $object->$field = $value;
                }, null, $fieldAccess->getThis());
                $setter($fieldAccess->getThis(), $this->getValue($fieldAccess));
            }
        }
    
        // ...
    }
    
    class Foo
    {
        /**
         * @FindBy(xpath="//div")
         */
        private $bar;
    
        public function getBar(): string
        {
            return $this->bar;
        }
    }
    

    The above code works only if the property is protected or public.

    To Be Discussed Support 
    opened by jakzal 12
  • PHP 8 Support

    PHP 8 Support

    I have added PHP 8 Support by replacing the @Annotation with the new #[Attribute] syntax

    I have refactored goaop/parser-reflection to also support PHP 8. I might have refactored too much of it, but all the tests are running :)

    The tests from the framework must be edited, but most of them work as well. I also ran some custom tests on the Laravel framework and it seemed to work

    opened by WalterWoshid 2
  • [FIX] Library generates non-nullable return type for nullable return …

    [FIX] Library generates non-nullable return type for nullable return …

    Library generates non-nullable return type for nullable return methods #482 Library version 3.0.0

    For below method proxy is generating non-null return

    /**
         * @AopAnnotation()
         * @return null|int
         */
        public function getSomeRandomValueAllowOnNull(): ?int
        {
            return random_int(0, 666);
        }
    

    result:

    /**
         * @AopAnnotation()
         * @return null|int
         */
        public function getSomeRandomValueAllowOnNull() : int
        {
            return self::$__joinPoints['method:getSomeRandomValueAllowOnNull']->__invoke($this);
        }
    
    opened by jakublabno 1
  • Library generates non-nullable return type for nullable return methods

    Library generates non-nullable return type for nullable return methods

    Library version 3.0.0

    For below method proxy is generating non-null return

    /**
         * @AopAnnotation()
         * @return null|int
         */
        public function getSomeRandomValueAllowOnNull(): ?int
        {
            return random_int(0, 666);
        }
    

    result:

    /**
         * @AopAnnotation()
         * @return null|int
         */
        public function getSomeRandomValueAllowOnNull() : int
        {
            return self::$__joinPoints['method:getSomeRandomValueAllowOnNull']->__invoke($this);
        }
    
    opened by jakublabno 0
  • Uncaught Error: Typed property Go\Aop\Framework\AbstractInterceptor::$adviceOrder must not be accessed before initialization in /var/www/html/app/vendor/goaop/framework/src/Aop/Framework/AbstractInterceptor.php

    Uncaught Error: Typed property Go\Aop\Framework\AbstractInterceptor::$adviceOrder must not be accessed before initialization in /var/www/html/app/vendor/goaop/framework/src/Aop/Framework/AbstractInterceptor.php

    Hi recently I updated the library from v2.3.5 to v3.0.0, while taking note of the breaking changes in the ChangeLog, I came to experience an error that doesn't seem to trace to any of the breaking changes mentioned. Here's the exact error which comes up: PHP message: PHP Fatal error: Uncaught Error: Typed property Go\Aop\Framework\AbstractInterceptor::$adviceOrder must not be accessed before initialization in /var/www/html/app/vendor/goaop/framework/src/Aop/Framework/AbstractInterceptor.php:119 Stack trace: #0 /var/www/html/app/vendor/goaop/framework/src/Aop/Framework/AbstractJoinpoint.php(95): Go\Aop\Framework\AbstractInterceptor->getAdviceOrder() #1 [internal function]: Go\Aop\Framework\AbstractJoinpoint::Go\Aop\Framework\{closure}() #2 /var/www/html/app/vendor/goaop/framework/src/Aop/Framework/AbstractJoinpoint.php(100): uasort() #3 /var/www/html/app/vendor/goaop/framework/src/Aop/Framework/AbstractJoinpoint.php(116): Go\Aop\Framework\AbstractJoinpoint::sortAdvices() #4 /var/www/html/app/vendor/goaop/framework/src/Instrument/Transformer/WeavingTransformer.php(142): Go\Aop\Framework\AbstractJoinpoint::flatAndSortAdvices() #5 /var/www/html/app/vendor/goaop/framework/src/Instrument/Transformer/WeavingTransformer.php(106): Go\Instrument\Transforme

    Any clue if there is another breaking change that needs to be looked out for?

    opened by gibrankasif 0
  • Is this library alive?

    Is this library alive?

    Hey @lisachenko,

    I am just checking to confirm that this library alive and kicking.

    I just saw that you deprecated https://github.com/goaop/parser-reflection, but this library still depends on it. This issue https://github.com/goaop/framework/issues/476 which has been open for half year has not gotten any traction even though it probably concerns a lot of people. This PR also addresses this issue (kinda): https://github.com/goaop/framework/pull/477

    I do not want to sound unappreciative about your awesome work for the open source community in this very critical and difficult area of PHP. On the other hand I have to consider the implications of using a library this complicated without having the proper support of the maintainers.

    Could you please elaborate on the current status of the library and maybe its future? Maybe you could get a co-maintainer on board?

    Thank you for your efforts :) I hope to hear from you soon.

    Support 
    opened by func0der 6
  • Update phpstan/phpstan requirement from ^0.12.64 to ^0.12.64 || ^1.0.0

    Update phpstan/phpstan requirement from ^0.12.64 to ^0.12.64 || ^1.0.0

    Updates the requirements on phpstan/phpstan to permit the latest version.

    Release notes

    Sourced from phpstan/phpstan's releases.

    1.0.0

    PHPStan 1.0 is here and I'm really excited about it! Read the accompanying article on PHPStan's blog and also check out the merchandise we're selling for limited time (until November 22nd). It includes white and blue PHPStan t-shirts, PHPStan stickers, and also something very special: Rule level badges that you can pin to your clothes to show off that you care about code quality 😊

    Major new features 🚀

    New rules

    ... (truncated)

    Commits
    • 0d13a99 PHPStan 1.0.0
    • 4453ce4 Updated PHPStan to commit 3738fcd98a98f5a8ef61b40a4c0c5384b7704714
    • b4442f2 Merch test
    • 90f09b0 Updated PHPStan to commit 5ddca4279e348a3e9e8ce1b1dbac70459b7ab20c
    • c3f75a5 Updated PHPStan to commit c836a423d34faa818127e34efb89ef4b3a7b66b6
    • f4538fd Updated PHPStan to commit fc2b308fd14fc989be72922531e64a6231368ae3
    • d6ec0d1 Updated PHPStan to commit afef6dc7f42ac00945dbed3f52f8700de9593656
    • d557793 Updated PHPStan to commit f8885db4593ce7678ef95fd5b732e13f7db15e5e
    • 69f6ffc Updated PHPStan to commit dbbbd41447de82234e28465c3caa3af147f8787e
    • 7633c8a Updated PHPStan to commit 50a7141a6eaa8df169933116164ad12d63f8ebb4
    • Additional commits viewable in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies 
    opened by dependabot[bot] 1
Releases(3.0.0)
  • 3.0.0(Apr 5, 2021)

    Better late than never! This is next major release of Go! AOP that contains some improvements and uses new code features for better performance and stability.

    Notable changes are:

    • [BC BREAK] Switched to the PHP7.4 and upper, strict types, property and return type hints and new syntax
    • [BC BREAK] Removed the Joinpoint->getThis() method, as not all joinpoints belongs to classes (eg. FunctionInvocation)
    • [BC BREAK] Removed the Joinpoint->getStaticPart() method as it can return anything, better to use explicit methods
    • [Feature] Introduced the new ClassJoinpoint interface with getScope(), getThis() and isDynamic() methods
    • [Feature] Implemented parameter widening feature for generated code #380
    • [Feature] AnnotatedReflectionProperty provides simple access to property annotations #388 by @TheCelavi
    • [Feature] Switched to the laminas/laminas-code package to generate code for proxies
    • [Feature] Add private properties interception #412
    • [Feature] Static code analysis with PhpStan was enabled
    • [Feature] Migration from TravisCI to GitHub Actions

    Notice: This version still doesn't support PHP8, see #466, mostly because of absence of PHP8-compatible parser reflection libraries. If you want to contribute this to one of existing projects, this will be very helpful.

    Source code(tar.gz)
    Source code(zip)
  • 2.3.4(Aug 12, 2020)

  • 3.0.0-RC1(Dec 4, 2019)

    This is next major release of Go! AOP that contains some improvements and uses new code features for better performance and stability.

    Notable changes are:

    • [BC BREAK] Switched to the PHP7.2 and upper, strict types, return type hints and new syntax
    • [BC BREAK] Removed the Joinpoint->getThis() method, as not all joinpoints belongs to classes (eg. FunctionInvocation)
    • [BC BREAK] Removed the Joinpoint->getStaticPart() method as it can return anything, better to use explicit methods
    • [Feature] Introduced the new ClassJoinpoint interface with getScope(), getThis() and isDynamic() methods
    • [Feature] Implemented parameter widening feature for generated code #380
    • [Feature] AnnotatedReflectionProperty provides simple access to property annotations #388 by @TheCelavi
    • [Feature] Switched to the zendframework/zend-code package to generate code for proxies
    • [Feature] Add private properties interception #412
    Source code(tar.gz)
    Source code(zip)
  • 2.3.3(Dec 4, 2019)

  • 2.3.2(Aug 1, 2019)

    Patch release that contains several small fixes

    • Fixed call to undefined method with new PhpParser for 2.x branch #419
    • Finder glob pattern #421
    • Fix memory leaks and late object disposal #423
    Source code(tar.gz)
    Source code(zip)
  • 2.3.0(Jan 13, 2019)

    This minor release introduces some fixes:

    • Fix error with "self" references in top level namespace #397
    • Use Symfony Finder component for faster enumeration #402
    • [BC Break] Update goaop/parser-reflection dependency to 2.0+ #403

    Please, be aware that #403 bumps the PHP minimum version for 2.x branch to be >=7.0.0. As minimum supported PHP version now is 7.2, I decided to drop 5.6 for 2.x branch too.

    In emergency cases 2.3.1 patch can be released.

    Source code(tar.gz)
    Source code(zip)
  • 2.2.0(Feb 9, 2018)

    This version introduces latest minor release in 2.x branch. Only security fixes and bug patches will be applied to the 2.x since current release.

    Fixes:

    • [x] AdviceMatcher should reject abstract method as fix for #335, #337
    • [x] Fix wrong checking of member modifiers for static members, resolves #293
    • [x] Feature/detecting inconsistent weaving #346
    • [x] Fix incorrect optimization logic for transformers, resolves #355
    • [x] Introduce an option to configure cached reader for annotations, resolves #136, #358 and #245

    Improvements:

    • [x] Replace unsupported HHVM with supported PHP7.2 in Travis matrix
    • [x] Inline definition of advices into classes and beautify formatting
    • [x] Improve PhpUnit integration and prepare reusable constraints, resolves #340 #350
    • [x] Add support for class initializations asserts. #354
    • [x] Apply short array syntax for files in 2.x branch
    • [x] Performance tuning

    Features:

    • [x] Implementation of AST+token modification instead of direct source transforming #362. Be aware, that this feature can reduce the speed of proxy generation because of switching to the AST analysis.
    • [x] Implement return-type pointcuts for the framework, #370 #371. Now you can match methods by return type.
    • [x] Interception of final methods, resolves #372 #373. Yes, now you can intercept final methods in your classes with Go! AOP.

    BC breaks on code level:

    • [x] d0c11d06aba419a4ce2bbedd89c7e4957352ac46 Simplify interface of IntroductionAdvisor, also allow only one interface/trait per introduction.
    Source code(tar.gz)
    Source code(zip)
  • 2.1.2(Jul 18, 2017)

    This small patch introduces following fixes, thanks to @TheCelavi:

    • [x] Implement workaround for Doctrine entities with metadata listener #327
    • [x] CLI commands can be owerwritten for transparent integration with frameworks #330
    Source code(tar.gz)
    Source code(zip)
  • 2.1.1(Apr 25, 2017)

    Small patch version with fixes

    • [x] Fix fnmatch issue on Windows machine #326
    • [x] Fix incorrect usage of CachedAspectLoader when cache is not configured #319
    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(Feb 4, 2017)

    Version 2.1.0 enables support for PHP7.1. As well it provides some useful features for you applications:

    • [x] Add support for the nullable types in PHP7.1, see #309, #314
    • [x] Add support for the void return types for PHP7.1, see #307
    • [x] Update the requirement for the goaop/parser-reflection library to use the latest version with PHP7.1 support.
    • [x] Add a fix for non-absolute paths returned from the composer since PHP5.6 #295
    • [x] Add an ability to update invocation arguments via Invocation->setArguments() method #297
    • [x] Enable wildcard paths for the Enumerator, e.g /*Bundle/*/Tests, see #300
    • [x] Allow to pass an exception as a second argument to the invocation for the AfterThrowing type of adivce, see #302
    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Feb 4, 2017)

    Version 1.2.0 is a minor release with one new feature and several fixes:

    • [x] Add an ability to update invocation arguments in advices, see #297
    • [x] Restrict the 1.x branch only to the PHP5.5, because andrewsville/php-token-reflection can work only with PHP=<5.5 and is not maintained anymore, see #305
    • [x] Fix broken logic of pattern matching for includePath and excludePath options, see #300, #311

    Please, note, that the branch 1.x will not receive any new features and you should plan your time to upgrade to the 2.x versions.

    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(May 14, 2016)

    Here it is! Shiny 2.0.0 version!

    This version contains a lot of fixes and improvements:

    • [x] Added a support for the PHP5.6 and 7.0 features: variadic arguments for methods, scalar type hints, return type hints
    • [x] Migrated from the Andrewswille/Token-Reflection to the goaop/parser-reflection library for PHP5.6 and PHP7.0 support
    • [x] Command-line tools for debugging aspects and advisors
    • [x] Dropped support for PHP<5.6, cleaned all old code
    • [x] [BC BREAK] Removed ability to rebind closures because of PHP restrictions, see #247
    • [x] [BC BREAK] Removed getDefaultFeatures() method from the AspectKernel, no need in it since PHP5.6

    Changes from the 1.x branch:

    • [x] Fixed cache files permission, now all cache files will be created with given cache mode, resolves #275
    • [x] Added context-sensitive matching that allows new checks #274 and resolves #272 by providing a new pointcut expression !matchInherited() to exclude all inherited methods from matching
    • [x] Fixed logic for complex pointcut expressions, combined with OR logic, resolves #217
    • [x] Fixed broken property interception generated source code for the constructor, resolves #271
    • [x] Property interceptor now use trait for common logic as well returns values by reference, see #54 and #232

    Pay attention, that some internal parts are changed, so be careful when updated. Also property interceptors logic is changed now and to modify values in advices, you should use returning by reference methods like following:

        /**
         * Advice that controls an access to the properties
         *
         * @param FieldAccess $fieldAccess Joinpoint
         *
         * @Around("access(public|protected Demo\Example\PropertyDemo->*)")
         * @return mixed
         */
        public function aroundFieldAccess(FieldAccess $fieldAccess)
        {
            $isRead = $fieldAccess->getAccessType() == FieldAccess::READ;
            // proceed all internal advices
            $fieldAccess->proceed();
    
            if ($isRead) {
                // if you want to change original property value, then return it by reference
                $value = /* & */$fieldAccess->getValue();
            } else {
                // if you want to change value to set, then return it by reference
                $value = /* & */$fieldAccess->getValueToSet();
            }
    
            echo $fieldAccess, ", value: ", json_encode($value), PHP_EOL;
        }
    
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(May 14, 2016)

    This version contains several important fixes and improvements:

    • [x] Fixed cache files permission, now all cache files will be created with given cache mode, resolves #275
    • [x] Added context-sensitive matching that allows new checks #274 and resolves #272 by providing a new pointcut expression !matchInherited() to exclude all inherited methods from matching
    • [x] Fixed logic for complex pointcut expressions, combined with OR logic, resolves #217
    • [x] Fixed broken property interception generated source code for the constructor, resolves #271
    • [x] Property interceptor now use trait for common logic as well returns values by reference, see #54 and #232

    Pay attention, that some internal parts are changed, so be careful, when updated. Also property interceptors logic is changed now and to modify values in advices, you should use returning by reference methods like following:

        /**
         * Advice that controls an access to the properties
         *
         * @param FieldAccess $fieldAccess Joinpoint
         *
         * @Around("access(public|protected Demo\Example\PropertyDemo->*)")
         * @return mixed
         */
        public function aroundFieldAccess(FieldAccess $fieldAccess)
        {
            $isRead = $fieldAccess->getAccessType() == FieldAccess::READ;
            // proceed all internal advices
            $fieldAccess->proceed();
    
            if ($isRead) {
                // if you want to change original property value, then return it by reference
                $value = /* & */$fieldAccess->getValue();
            } else {
                // if you want to change value to set, then return it by reference
                $value = /* & */$fieldAccess->getValueToSet();
            }
    
            echo $fieldAccess, ", value: ", json_encode($value), PHP_EOL;
        }
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.2(Mar 8, 2016)

  • 1.0.0(Feb 13, 2016)

    1.0.0 (Feb 13, 2016)

    • Dropped support for PHP<5.5, clean all old code
    • Tagged public methods and interfaces with @api tag. No more changes for them in future.
    • Refactored core code to use general interceptors for everything instead of separate classes
    • New static initialization pointcut to intercept the moment of class loading
    • New feature to intercept object initializations, requires INTERCEPT_INITIALIZATIONS to be enabled
    • [BC BREAK] remove class() pointcut from the grammar #189
    • [BC BREAK] make within() and @within() match all joinpoints #189
    • [BC BREAK] drop @annotation syntax. Add @execution pointcut
    • Pointcuts can be build now directly from closures via PointcutBuilder class
    • Do not create files in the cache, if no aspects were applied to them, respects includePath option now
    • FilterInjector is now disabled by default, this job for composer integration now
    • Automatic opcache invalidation for cache state file
    Source code(tar.gz)
    Source code(zip)
  • 0.6.1(Jul 28, 2015)

  • 0.6.0(Feb 1, 2015)

    Version 0.6.0 is ready! This is probably the last 0.x release and now we are going to 1.0.0.

    However, this version also includes some improvements and changes:

    • Interceptor for magic methods via "dynamic" pointcut. This feature also gives an access for dynamic pointcuts with different checks and conditions.
    • PSR-4 standard for the codebase, thanks to @cordoval
    • Added a support for splat (...) operator for more efficient advice invocation (requires PHP5.6)
    • New feature system. All tunings of kernel are configured with feature-set. This breaks old configuration option interceptFunctions=>true use 'features' => $defaultFeatures | Features::INTERCEPT_FUNCTIONS now
    • Proxy can generate more effective invocation call with static::class for PHP>=5.5
    • Bug-fixes with empty cache path and PSR4 code, thanks to @andy-shea
    Source code(tar.gz)
    Source code(zip)
  • 0.5.0(May 24, 2014)

    Version 0.5.0 of framework is ready!

    • Proxies are now stored in the separate files to allow more transparent debugging
    • Cache warmer command added
    • Extended pointcut syntax for or-ed methods: ClassName->method1|method2(*)
    • Access to the annotations from MethodInvocation instance
    • Support for read-only file systems (phar, GAE, etc)
    • Direct access to advisors (no more serialize/unserialize)
    • New @within pointcut to match classes by annotation class
    • Nice demo GUI
    • Deprecate the usage of submodules for framework
    • Inheritance support during class-loading and weaving
    • List of small fixes and imrovements
    Source code(tar.gz)
    Source code(zip)
  • 0.4.3(Dec 19, 2013)

    1. Add minor changes for supporting CLI-mode
    2. Optimization: sort list of advices only during the cache creation phase
    3. Optimization: do not split a source into array of lines if there is only one class per file
    Source code(tar.gz)
    Source code(zip)
  • 0.4.2(Dec 19, 2013)

    1. Switched to doctrine/annotations package instead of doctrine/common
    2. Removed the Demo namespace from the composer and use it only for testing.
    3. Optimized injection of advices and caching of callables
    4. Some fixes for supporting Symfony2 and SensioDistributionBundle
    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Aug 27, 2013)

    • Better parsing of complex "include" expressions for Yii (by @zvirusz)
    • Support for dynamic arguments count for methods by checking for func_get_args() inside method body
    • Fixed a bug with autoloaders reodering (by @zvirusz)
    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Aug 4, 2013)

    This release contains a lot of new features. Here are the short changelog for them:

    • Privileged advices for aspect: allows to access private and protected properties and methods of objects inside advice
    • Full integration with composer that allows for easy configuration and workflow with AOP
    • Fix some bugs with caching on Windows
    • "True" pointcut references that gives the ability to compose a complex pointcut from a simple pointcuts.
    • Pointcut now accept "$this" in references to point to the current aspect instance (Allows for abstract aspects and abstract pointcuts)
    • AspectContainer interface was extracted. This gives the way to integrate with another DIC. Look at Warlock framework.
    • Intercepting system functions such as fopen(), file_get_contents(), etc
    • Annotation property pointcut was added
    • Ability to declare multiple interfaces and/or traits with single DeclareParent introduction
    • DeclareError interceptor was added. This can be used for generating an runtime error for methods that should not be executed in such a way.
    Source code(tar.gz)
    Source code(zip)
Owner
Go! Aspect-Oriented Framework
Go! Aspect-Oriented Framework
Hamtaro - the new web framework for front-end / back-end development using Php and Javascript.

Hamtaro framework About Technologies Controllers Components Commands Front-end development Getting Started About Hamtaro is the new web framework for

Phil'dy Jocelyn Belcou 3 May 14, 2022
A resource-oriented micro PHP framework

Bullet Bullet is a resource-oriented micro PHP framework built around HTTP URIs. Bullet takes a unique functional-style approach to URL routing by par

Vance Lucas 414 Nov 15, 2022
A resource-oriented application framework

BEAR.Sunday A resource-oriented application framework What's BEAR.Sunday This resource orientated framework has both externally and internally a REST

BEAR.Sunday 237 Oct 17, 2022
A resource-oriented application framework

BEAR.Sunday A resource-oriented application framework What's BEAR.Sunday This resource orientated framework has both externally and internally a REST

Akihito Koriyama 6 May 11, 2022
Zephir is a compiled high level language aimed to the creation of C-extensions for PHP.

Zephir - is a high level programming language that eases the creation and maintainability of extensions for PHP. Zephir extensions are exported to C c

Zephir Language 3.2k Dec 3, 2022
🚀 Developing Rocketseat's Next Level Week (NLW#05) Application using PHP/Swoole + Hyperf

Inmana PHP ?? Developing Rocketseat 's Next Level Week (NLW#05) Application using Swoole + Hyperf. This is the app of the Elixir track. I know PHP/Swo

Leo Cavalcante 18 Jun 1, 2022
Sunhill Framework is a simple, fast, and powerful PHP App Development Framework

Sunhill Framework is a simple, fast, and powerful PHP App Development Framework that enables you to develop more modern applications by using MVC (Model - View - Controller) pattern.

Mehmet Selcuk Batal 2 Aug 6, 2022
Framework for building extensible server-side progressive applications for modern PHP.

Chevere ?? Subscribe to the newsletter to don't miss any update regarding Chevere. Framework for building extensible server-side progressive applicati

Chevere 61 Nov 2, 2022
CakePHP: The Rapid Development Framework for PHP - Official Repository

CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Associative Data Mapping, Front Controller, and MVC. O

CakePHP 8.6k Dec 1, 2022
QPM, the process management framework in PHP, the efficient toolkit for CLI development. QPM provides basic daemon functions and supervision mechanisms to simplify multi-process app dev.

QPM QPM全名是 Quick(or Q's) Process Management Framework for PHP. PHP 是强大的web开发语言,以至于大家常常忘记PHP 可以用来开发健壮的命令行(CLI)程序以至于daemon程序。 而编写daemon程序免不了与各种进程管理打交道。Q

Comos 75 Dec 21, 2021
PHPLucidFrame (a.k.a LucidFrame) is an application development framework for PHP developers

PHPLucidFrame (a.k.a LucidFrame) is an application development framework for PHP developers. It provides logical structure and several helper utilities for web application development. It uses a functional architecture to simplify complex application development. It is especially designed for PHP, MySQL and Apache. It is simple, fast, lightweight and easy to install.

PHPLucidFrame 19 Apr 25, 2022
li₃ is the fast, flexible and most RAD development framework for PHP

li₃ You asked for a better framework. Here it is. li₃ is the fast, flexible and the most RAD development framework for PHP. A framework of firsts li₃

Union of RAD 1.2k Nov 27, 2022
☄️ PHP CLI mode development framework, supports Swoole, WorkerMan, FPM, CLI-Serve

☄️ PHP CLI mode development framework, supports Swoole, WorkerMan, FPM, CLI-Server / PHP 命令行模式开发框架,支持 Swoole、WorkerMan、FPM、CLI-Server

Mix PHP 1.8k Dec 5, 2022
Pods is a development framework for creating, extending, managing, and deploying customized content types in WordPress.

Pods Framework Pods is a development framework for creating, extending, managing, and deploying customized content types in WordPress. Description Che

Pods Foundation, Inc 979 Nov 22, 2022
Elgg is an open source rapid development framework for socially aware web applications.

Elgg Elgg is an open source rapid development framework for socially aware web applications. Features Well-documented core API that allows developers

Elgg 1.6k Nov 19, 2022
Motan - a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services

Motan-PHP Overview Motan is a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services.

Weibo R&D Open Source Projects 81 Nov 19, 2022
PPM is a process manager, supercharger and load balancer for modern PHP applications.

PPM - PHP Process Manager PHP-PM is a process manager, supercharger and load balancer for PHP applications. It's based on ReactPHP and works best with

PPM - PHP Process Manager 6.5k Nov 29, 2022
FrankenPHP is a modern application server for PHP built on top of the Caddy web server

FrankenPHP: Modern App Server for PHP FrankenPHP is a modern application server for PHP built on top of the Caddy web server. FrankenPHP gives superpo

Kévin Dunglas 2.8k Dec 7, 2022