-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Split ORMException
into more fine-grained, better documented and typed exception classes
#6743
Conversation
2169905
to
7898d3d
Compare
@greg0ire the split is indeed needed (see doctrine/common#681), but splitting just per component is a bit silly. These are (almost) all separate exceptions, implementing a component-specific exception interface (for example, For example: class InvalidRepositoryMagicMethodCall extends ORMException implements RepositoryException
{
public static function fromMethodCallAndArguments(string $method, array $arguments) : self {
...
}
} In an ideal world, this should be: class InvalidRepositoryMagicMethodCall extends \InvalidArgumentException implements RepositoryException
{
public static function fromMethodCallAndArguments(string $method, array $arguments) : self {
...
}
} Moving away from |
@Ocramius My plan was to make So to sum up, would that be ok with you: class InvalidRepositoryMagicMethodCall extends \InvalidArgumentException implements RepositoryException, ORMException
{
public static function fromMethodCallAndArguments(string $method, array $arguments) : self {
...
}
} |
@greg0ire given that the constructors would move around anyway, having an |
Ok so if I understand correctly, I should move every method to its separate exception class, and use the split I proposed to create interfaces instead of classes. I'll try to push an example soon based on the "ideal world" snippet you gave + |
@greg0ire the namespace Doctrine\ORM\Repository;
interface RepositoryException extends \Throwable, \Doctrine\ORM\ORMException
{
} |
Even better! |
ORMException being an interface was exactly my idea. namespace Doctrine\ORM;
interface ORMException extends \Throwable {} Then specific exceptions would implement it (or have its own interface, as shown above): namespace Doctrine\ORM\Mapping;
use Doctrine\ORM\ORMException;
interface MappingException extends ORMException {}
class BlahException extends \RuntimeException implements MappingException {} |
lib/Doctrine/ORM/NewORMException.php
Outdated
/** | ||
* This interface should be implemented by all exceptions in this package. | ||
*/ | ||
interface NewORMException extends \Throwable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will of course be refactored to ORMException
|
||
use Doctrine\ORM\RepositoryException; | ||
|
||
final class InvalidMagicMethodCall extends \InvalidArgumentException implements RepositoryException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the exception final
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the Repository
bit from the name since it already is in the namespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was InvalidArgumentException
chosen instead of BadMethodCallException
because of BC or because magic methods are getting the method name as an argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know, I copy pasted a snippet from @Ocramius but BadMethodCallException
might be better indeed: it's not about the arguments being invalid, it's about the field not existing.
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM\Repository; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put the exception in the namespace of the relevant component.
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put that at the top level namespace to avoid the repetition. An alternative would be to name the class Doctrine\ORM\Repository\Exception
, tell me what you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a PersisterException
in Doctrine\ORM\Persisters
namespace. Also a CacheException
in Doctrine\ORM\Cache
. If we keep that structure, it suggests that RepositoryException
will be in Doctrine\ORM\Repository
.
I would like to see a structure as you suggested with Doctrine\ORM\Repository\Exception
, even with the already existing exception classes, but I don't think that this would be an anticipated change.
|
||
final class InvalidMagicMethodCall extends \InvalidArgumentException implements RepositoryException | ||
{ | ||
public static function fromEntityNameFieldNameAndMethod( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method name is a bit long, I'm open to proposals
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fieldNameNotFound
or fieldNotFound
because it's the reason for not being able to use a magic method in an entity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So no from
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The static name may lack fluent reading because of a missing method prefix like "from". Sounds funny with "because" InvalidMagicMethodCall::becauseFieldNotFoundIn
, but I like "In" as suffix. Maybe the suffix can make it more readable without having needed arguments in the method name.
public static function fromEntityNameFieldNameAndMethod( | ||
string $entityName, | ||
string $fieldName, | ||
$method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woops I forgot the typehint here
7b6c3eb
to
897ca26
Compare
|
||
use Doctrine\ORM\RepositoryException; | ||
|
||
final class InvalidFindByInverseAssociation extends \BadMethodCallException implements RepositoryException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of this class name. I know I created it myself, but it's derived from the former method name. What I don't like is that it kind of implies there could be valid find by inverse association method calls. Should I drop the Invalid
part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the name can be discussed in combination with the exception methods. I suggest InvalidAssociation
or InvalidAssociationSide
. I've read the exception message in the tests and used the mentioned names for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InvalidAssociationSide
sounds great
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the other one leaves more room for other named constructors.
|
||
final class InvalidFindByInverseAssociation extends \BadMethodCallException implements RepositoryException | ||
{ | ||
public static function becauseIsInverseAssociation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ocramius @SenseException can you please agree on a "because" vs "from" naming recommendation before I migrate all the exceptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like I previously wrote, "because" sounds too funny, so I can agree on a different prefix. If we go with my exception name suggestions of a previous comment about this class name, we can create a method like InvalidAssociationSide::fromInverseSide()
or InvalidAssociation::fromInverseSideUsage()
. We can recombine method name and class name between those two method-suggestions, if you like class name or method name, but not the other one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is a point to using named constructors if we don't have several of them (or at least, leave room for more of them). I'll therefore go with your second proposition: classname = issue, and method name = cause of the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or rather, InvalidFindByCall::fromInverseSideUsage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm gonna cram findByRequiresParameter
in there, Ocramius said those should be almost all separate exceptions, I think using the "almost" card here is right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fromMissingParameter
sounds weird though, maybe because
is not that bad 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about onMissingParameter
, onInverseSideUsage
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm gonna cram findByRequiresParameter in there, Ocramius said those should be almost all separate exceptions, I think using the "almost" card here is right.
as @ostrolucky pointed out, this one is not about findBy
, but about a magic call, so I guess I'm gonna put it in InvalidMagicCall
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EvilMagicCall
😆
It sounds right that it belongs into an exception for magic calls.
About just one needed named constructor: They become or already are a standard in Doctrine, so it would be weird if we have one case where an instance was created with new
. Especially if that exception class might need a second exception case one day, we have to look that the one with new
will get an own named constructor too.
@@ -683,7 +684,7 @@ public function testCanRetrieveRepositoryFromClassNameWithLeadingBackslash() | |||
/** | |||
* @group DDC-1376 | |||
* | |||
* @expectedException Doctrine\ORM\ORMException | |||
* @expectedException Doctrine\ORM\Repository\InvalidFindByInverseAssociation | |||
* @expectedExceptionMessage You cannot search for the association field 'Doctrine\Tests\Models\CMS\CmsUser#address', because it is the inverse side of an association. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized it now, that one test is using $this->expectException()
and $this->expectExceptionMessage()
while we have annotations here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think annotations should be completely dropped in favor of those method calls :P
Wait why is |
Looking at the unit tests, it seems this exception is indeed supposed to be thrown when using |
678811a
to
1f70547
Compare
50d23b5
to
94e566c
Compare
9f5fd37
to
e7d61c2
Compare
Needs a rebase. |
efc918a
to
9040e93
Compare
9044cda
to
10aac31
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This goes in as-is 👍
The BC breaks to be documented are to be added to the UPGRADE.md
, but I'm raising a separate issue for that.
ORMException
into more fine-grained, better documented and typed exception types
ORMException
into more fine-grained, better documented and typed exception typesORMException
into more fine-grained, better documented and typed exception classes
Thanks a lot for merging! Will list the BC-break this week-end. |
@greg0ire Any update on documentating these changes? |
Also what do we do with all the exceptions left in root namespace?
Current state is highly inconsistent. |
This class is too big, and some exceptions could inherit from
doctrine/common
exceptions instead of this one.TODO
entityMissingAssignedIdForField
entityMissingForeignAssignedId
invalidFindByCall
invalidFlushMode
notSupported
namedNativeQueryNotFound
namedQueryNotFound
proxyClassesAlwaysRegenerating
invalidEntityRepository
unknownEntityNamespace
entityManagerClosed
invalidHydrationMode
mismatchedEventManager
missingIdentifierField
missingMappingDriverImpl
unrecognizedIdentifierFields
cantUseInOperatorOnCompositeKeys
invalidOrientation
unexpectedAssociationValue
unrecognizedField
findByRequiresParameter
invalidMagicCall
invalidFindByInverseAssociation
invalidResultCacheDriver
queryCacheNotConfigured
metadataCacheNotConfigured
queryCacheUsesNonPersistentCache
metadataCacheUsesNonPersistentCache
Proposal
notSupported
Doctrine\ORM\Tools\SchemaTool
SchemaToolException
namedNativeQueryNotFound
Doctrine\ORM\Configuration
ConfigurationException
namedQueryNotFound
Doctrine\ORM\Configuration
ConfigurationException
proxyClassesAlwaysRegenerating
Doctrine\ORM\Configuration
ConfigurationException
invalidEntityRepository
Doctrine\ORM\Configuration
ConfigurationException
unknownEntityNamespace
Doctrine\ORM\Configuration
ConfigurationException
entityManagerClosed
Doctrine\ORM\EntityManager
ManagerException
entityMissingAssignedIdForField
entityMissingForeignAssignedId
invalidHydrationMode
Doctrine\ORM\EntityManager
ManagerException
mismatchedEventManager
Doctrine\ORM\EntityManager
ManagerException
missingIdentifierField
Doctrine\ORM\EntityManager
ManagerException
missingMappingDriverImpl
Doctrine\ORM\EntityManager
ManagerException
unrecognizedIdentifierFields
Doctrine\ORM\EntityManager
ManagerException
cantUseInOperatorOnCompositeKeys
Doctrine\ORM\Persisters\Entity\BasicEntityPersister
PersisterException
invalidFlushMode
invalidOrientation
Doctrine\ORM\Persisters\Entity\BasicEntityPersister
PersisterException
unexpectedAssociationValue
Doctrine\ORM\UnitOfWork
unrecognizedField
Doctrine\ORM\Persisters\Entity\BasicEntityPersister
PersisterException
findByRequiresParameter
Doctrine\ORM\EntityRepository
RepositoryException
invalidFindByCall
invalidMagicCall
Doctrine\ORM\EntityRepository
RepositoryException
invalidFindByInverseAssociation
Doctrine\ORM\Persisters\Entity\BasicEntityPersister
RepositoryException
invalidResultCacheDriver
Doctrine\ORM\AbstractQuery
CacheException
queryCacheNotConfigured
Doctrine\ORM\Configuration
CacheException
metadataCacheNotConfigured
Doctrine\ORM\Configuration
CacheException
queryCacheUsesNonPersistentCache
Doctrine\ORM\Configuration
CacheException
metadataCacheUsesNonPersistentCache
Doctrine\ORM\Configuration
CacheException