Should i throw exceptions in the service layer or repository, and catch it on the controller? I read some answers here on stack exchange, but none really &quot;hits the nail on the head&quot; about where to throw exceptions, where I shouldn't and where to catch them.
the idea that an exception should be thrown in exceptional cases is very vague, for example, if I can't find a user in my data source (repository) I can't continue the flow of my code, can this be considered exceptional?
I also understand that there are caveats about flow control with exceptions, for me it does not seem right to use try/catch within the service, to catch all the exceptions that a repository/adapter/whatever may throw to control what to do next.
based on this, a code example with a &quot;service&quot; and a controller calling the service and catching all of the service exceptions:
Service:
class CheckUserUltraSecretInformation
{

    public function __construct(
        private CachedUserDataRepository $cachedDataRepository,
    )
    {
    }

    public function handle(string $document)
    {
        $cachedUserData = $this-&gt;cachedDataRepository-&gt;findByDocument($document);

        if (empty($cachedUserData)) {
            throw new CachedUserDataNotFoundException('User Cached information not found, cannot proceed');
        }

        if ($cachedUserData-&gt;hasExpired()) {
            throw new CachedUserDataHasExpiredException('Cached user data is expired');
        }
        // return ultra secret information
    }
}

Controller:
class UserSecretInformationController
{

    public function __construct(
        private CheckUserUltraSecretInformation $checkUserUltraSecretInformation)
    {
    }

    public function checkUserInformation(Request $request)
    {
        $document = $request-&gt;input('document');

        try {
            $userUltraSecretInformation = $this-&gt;checkUserUltraSecretInformation-&gt;handle($document);
        } catch (CachedUserDataNotFoundException $exception) {

            // return json response with http code 400

        } catch (CachedUserDataExpiredException $exception) {
            //returns json response with http code 500
        }
    }
}


Is it &quot;correct&quot; that my repository does not throw exceptions, and only returns null/false when it cannot return an entity? if not, what is the problem?

if my repository throws exceptions, should my service capture them and re-launch them as service exceptions? is this considered flow control?

Are the outer layers the &quot;most correct&quot; to capture and handle exceptions? in my example the controller is used, can It know the name of the services/use-cases exceptions?
the idea that an exception should be thrown in exceptional cases is very vague, for example, if I can't find a user in my data source (repository) I can't continue the flow of my code, can this be considered exceptional?

My personal approach is I raise an exception if encounter a scenario where during code execution, I make a truthy assumption but that assumption turns out to be falsey. So in your example, if you can't continue the expected code execution path without a valid user object returned from the repository, throw an exception at the point where your having a user assumption turns out to be false.
Where to initially throw and exception and where to catch depends on the use case. The rule of thumb I tend to follow is thrown an exception as soon as I can't continue or recover the desired code execution path, then allow that exception to propagate back up the stack to a place that understands how to handle that exception. Don't catch an exception if all you're going to do is re-throw.

Is it &quot;correct&quot; that my repository does not throw exceptions, and only returns null/false when it cannot return an entity? if not, what is the problem?

I would say this is acceptable. There might be occasions where a falsey response from your repository is not exceptional and is perfectly acceptable. I would also argue it's not the responsibility of the repository to decide if a falsey response is acceptable or not (for example when checking the existence of a record).

if my repository throws exceptions, should my service capture them and re-launch them as service exceptions? is this considered flow control?

Catch an exception where you intend to handle that exception. Sometimes it does make sense to catch and re-throw an exception, such as in cases where you want to add information to the exception or cast it to a more meaningful exception for the caller above.

Are the outer layers the &quot;most correct&quot; to capture and handle exceptions? in my example the controller is used, can It know the name of the services/use-cases exceptions?

Catch and handle where it makes sense. In your case it makes sense as the controller is the last caller before returning a response to the client and it's not the responsibility of your service to format an error response for the caller.