Repository navigation
Conversation
Resolve a class-level Source through the configured annotations adapter before falling back to the uncamelized model name. Support positional and named table arguments while preserving explicit sources and table prefixes. Add regression coverage for both readers, optional DI services, per-class caching, column metadata, and SQLite persistence and queries. Assisted-by: Codex
Assisted-by: Codex
niden
left a comment
There was a problem hiding this comment.
Very nice fix and thank you for this. The logic i s correct but you missed the point where this starts running for every single model.
Looking forward to your fixes!
| let source = null, | ||
| container = this->container; | ||
|
|
||
| if typeof container === "object" && container->has("annotations") { |
There was a problem hiding this comment.
This check is true for almost every application. FactoryDefault always registers the annotations service, with the docblock Reader. Thus, each model that does not call setSource() now has its annotations parsed on the first getSource() call. This also occurs with the default Introspection metadata strategy, which did not read annotations before.
With the default Memory adapter, this parse occurs on each request, for each model class.
Can we make this opt-in? One option is to read Source only when the reader is AttributesReader. Then setReader(new AttributesReader()) is the opt-in. That reader does not parse docblocks, so it cannot throw a parser exception:
let annotations = <AnnotationsAdapterInterface> container->getShared("annotations");
if annotations->getReader() instanceof AttributesReader {
// read the Source attribute
}An even better implementation to the approach above is to extract this whole logic to its own method, which will be responsible for checking the annotations reader in the DI and the reader and return the scanned source or the default one.
|
|
||
| if typeof container === "object" && container->has("annotations") { | ||
| let annotations = <AnnotationsAdapterInterface> container->getShared("annotations"), | ||
| classAnnotations = annotations->get(get_class(model))->getClassAnnotations(); |
There was a problem hiding this comment.
This call parses all the docblocks of the model: the class, the properties and the methods. Before this change, a model with a docblock that the annotations parser rejects worked with the Introspection strategy. Now getSource() throws Phalcon\Annotations\Exception for that model. The PR description says that these exceptions are not suppressed. For these applications, this is a breaking change in a bug-fix release i.e. v7
|
|
||
| /** | ||
| * Names the table of a model. No framework code reads it today. | ||
| * Names the table of a model when the annotations service uses AttributesReader. |
There was a problem hiding this comment.
This text says that the attribute works only with AttributesReader. But the code and the tests also accept the docblock @Source. Please make the docblock and the code agree. The decision on the Manager.zep comments sets which one is correct.
| * @Source(table="named_docblock_records") | ||
| */ | ||
| #[Source(table: 'named_attribute_records')] | ||
| class SourceNamedArgument extends SourceModel |
There was a problem hiding this comment.
This class declares its own Source. What occurs for a subclass that does not? PHP does not inherit attributes or doc comments. Thus, a subclass of SourceModel with no Source gets its own inferred name. But when the parent calls setSource() in initialize(), the subclass gets the table of the parent. Please add a test for a subclass without Source, and tell us which result you expect.
|
|
||
| ### Fixed | ||
|
|
||
| - `Phalcon\Mvc\Model\Manager::getModelSource()` ignoring class-level `Source` attributes and annotations. Explicit sources take precedence, and both positional and named `table` arguments are supported. [#17741](https://github.com/phalcon/cphalcon/issues/17741) |
There was a problem hiding this comment.
Can we shorten this a bit? The changelog is supposed to be short and sweet and any additional information goes in the documentation. So the "Explicit sources...." should end up in the docs in a new PR.
| ]; | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
Almost all the tests in tests/database/Mvc/Model/Manager/ use the testMvcModelManager<Method><Case>() name format, with a docblock that has @author and @since. Please use the same format for the new tests, for example testMvcModelManagerGetModelSourcePrefix(). Your name (if you wish) and the since have to be there for accreditation purposes
Only consult class attributes when the annotations service uses AttributesReader. Extract fallback resolution to a private method so the default docblock reader and Introspection models retain their behavior. Cover malformed docblocks, unannotated subclasses, and inherited explicit sources. Align test names and author/since tags with the repository style, clarify the Source API documentation, and shorten the changelog entry. Assisted-by: Codex
Exercise attribute-based persistence and PHQL with Introspection as well as Annotations, and assert that source resolution retains the configured metadata strategy. Cover the opt-in behavior of an AttributesReader subclass. Assisted-by: Codex
|
Thanks for the review, @niden! All six comments have been addressed:
Additional tests cover both metadata strategies and an Ready for another review. Thank you! |
Hello!
Fixes #17741.
In raising this pull request, I confirm the following:
#[Source(table: 'co_customers')]is read byAttributesReader, but the ORM ignores it and usescustomersfor aCustomersmodel. This causes queries to target the wrong table unless the model also callssetSource().Resolve the class-level
Sourceattribute only when the container's annotations service explicitly usesAttributesReader(or a subclass). A privategetDefaultModelSource()method checks that opt-in and supplies the attribute value or the conventional table name. Both positional and namedtablearguments are supported. ExplicitsetSource()/setModelSource()values retain priority, the existing per-class source cache stores the result, and the manager applies its current table prefix on each lookup.The default PHPDoc reader is not invoked for source resolution, so existing
Introspectionmodels keep working even when their unrelated docblocks are rejected by the annotation parser. PHPDoc@Sourceremains ignored. Missing DI/annotations services and classes withoutSourceretain the inferred-name fallback; foreign attributes namedSourcedo not override it.Attributes are not inherited: a subclass without its own
Sourcegets the table inferred from the subclass name. An inheritedinitialize()that callssetSource()still explicitly sets the child's source. Unit and SQLite tests cover both cases, anAttributesReadersubclass, attribute-based persistence with both metadata strategies, and an Introspection model with malformed docblocks. Table resolution retains the configured metadata strategy. The Source API docblock includes a usage example; applications still configureAttributesReaderon their annotations adapter.Validation on PHP 8.5.10, Zephir 1.7.0 and PHPUnit 10.5.66:
git diff --checkpasses.The full suites requiring Redis/Memcached services, MySQL/PostgreSQL, and the remaining PHP-version matrix were not run locally.
Thanks!