phparkitect / arkitect

Put your architectural rules under test!
MIT License
704 stars 36 forks source link

Is it possible not to depend on classes? #318

Closed nmeri17 closed 1 year ago

nmeri17 commented 1 year ago
Q A
Library version x.y.z
PHP version x.y.z

Support Question

I've gone through the documentation but can't find what I'm looking for. My intention is

  1. Prevent class X from depending on a specific list of classes (from different namespaces, not one)
  2. In fact, if it's possible to tailor permitted dependencies to a given list, that will be better
  3. Set a list of classes allowed to depend on class Y
pfazzi commented 1 year ago

Hello! You could write a rule like this:

Rule::allClasses()
  ->that(new ResideInOneOfTheseNamespaces(
      "App\Service",
      "App\Entity",
  ))->should(
      new NotDependsOnTheseNamespaces(
          "App\OldService",
          "App\OldEntity",
      )
  )->because("Those namespaces have been deprecated in favor of the new modular architecture");

Does this address your issue?

nmeri17 commented 1 year ago

Thanks for the prompt response. It looks like it'll solve point (1). However, like I said, it'll be more preferable to solve (2) . I wasn't aware the methods apply to classes (instead of just *namespaces). If I use this expression, I believe DependsOnlyOnTheseNamespaces will help enforce presence of given classes by all who extend X. NotHaveDependencyOutsideNamespace would solves 3 ie restricting usage of Y to a given list

I'm assuming "dependence" here refers to constructors and methods. Two more questions:

  1. Is there a way to distinguish between constructors and methods? I only want request objects on controller methods
  2. Are there programmatic handlers? I'd appreciate if I can retain the tests currently confirming this behaviour but connecting it to your library. I also want to call it from code (during server build), but if unavailable, it's not a big problem
pfazzi commented 1 year ago

Is there a way to distinguish between constructors and methods? I only want request objects on controller methods

ATM we don't distinguish between constructor dependencies and method dependencies, but it could be an nice new feature

Are there programmatic handlers?

Can you explain to me what you mean by programmatic handlers? What are those handlers intended to do?

nmeri17 commented 1 year ago

Can you explain to me what you mean by programmatic handlers

That is calling the library from code rather than from the CLI

pfazzi commented 1 year ago

Actually, we already have an integration with PHPUnit. Can this solve your problem?

nmeri17 commented 1 year ago

Are there programmatic handlers? I'd appreciate if I can retain the tests currently confirming this behaviour but connecting it to your library. I also want to call it from code

By this, here's what I mean: my framework has a build step; it uses roadrunner under the hood. My first question is asking whether it's possible to invoke arkitect during this build process, so it can terminate if developer's code violates preset rules. Something like:


try {
    Arkitect::processConfig($somePath);

    MyFramework::buildApp();
}
catch (ArkitectException)

I suppose I can equally test this from my end to confirm violations are duly reported. I wrote a component covering my dependency needs. The tests for it can hint at what I'm talking about. Please see https://github.com/nmeri17/suphle/blob/466378d36ba0b829707ea8d41fa2cd8dc5b974d4/tests/Integration/Services/Proxies/OnlyLoadedByTest.php#L16 . Now, I want to replace it with Arkitect since I can run this at build /compile phase as opposed to at runtime for each request. Do you understand

AlessandroMinoccheri commented 1 year ago

Hi @nmeri17 so if I understood well, you would like to use arkitect directly into the PHP code instead of using CLI, right? At the moment I can see two ways:

nmeri17 commented 1 year ago

@AlessandroMinoccheri the problem with using exec is that I can't catch errors or violation of the rules, and possibly prevent build of my own app OR report that test failed. I guess it can suffice to combine symfony Process with your Check command and the path to my rules configuration. If command returns Command::FAILURE, I can work with that status

Just as a suggestion, it would've been more ideal if the code in Check was extracted into a service that allows usage outside the CLI. That way, I'll work with that directly

Thanks for pitching in. If that's fine with you, it's OK to close the topic. I will write the config today or tomorrow and report here if it doesn't behave according to these discussions

nmeri17 commented 1 year ago

Alright, I'm writing the config now. There's a rule I can't seem to locate. Suppose I want to limit classes that can depend on X, what combination of rules do I use? For instance, I only want Mail builders to be used by Task classes and not synchronous ones like the controller. I'm asking this so I don't assume and incorrectly test

AlessandroMinoccheri commented 1 year ago

I think that you can use DependsOnlyOnTheseNamespaces for your Mail builders

nmeri17 commented 1 year ago

@AlessandroMinoccheri I used that rule to try and limit dependencies receivable by a class i.e.

protected function restrictCoordinator () {

            return Rule::allClasses()

            ->that(new Extend(ServiceCoordinator::class))

            ->should(
                new DependsOnlyOnTheseNamespaces(...[
                    ConditionalFactory::class, // We're treating it as a type of service in itself
                    ControllerModule::class, // These are a service already. There's no need accessing them through another local proxy

                    PayloadStorage::class, // there may be items we don't want to pass to the builder but to a service?

                    BaseHttpRequest::class, UpdatefulService::class,

                    UpdatelessService::class
                ], // constructor arguments
                ...[

                    ModelfulPayload::class, ModellessPayload::class
                ]) // action method arguments
            );
        }

I don't expect any descendant of ServiceCoordinator to import any of those classes. I've not tested it yet but that's the intended behavior. It's a different behavior to this:

I only want Mail builders to be used by Task classes

If you can make sense of this, I had it working here https://github.com/nmeri17/suphle/blob/466378d36ba0b829707ea8d41fa2cd8dc5b974d4/src/Services/DecoratorHandlers/OnlyLoadedByHandler.php#L19

AlessandroMinoccheri commented 1 year ago

ok, so it seems ok that rule is for your purpose right?

nmeri17 commented 1 year ago

ok, so it seems ok that rule is for your purpose right?

Sorry, is this a typo? I don't understand your message

AlessandroMinoccheri commented 1 year ago

with the rule DependsOnlyOnTheseNamespaces you can achieve what you want I think, so what do you expect instead? What behavior do you want in your OnlyLoadedByHandler class?