{
    "href": "/post/2018/05/09/atlas-3-x-cassini-and-phpstorm-completion/",
    "relId": "2018/05/09/atlas-3-x-cassini-and-phpstorm-completion",
    "title": "Atlas 3.x (\"Cassini\") and PHPStorm Completion",
    "author": "pmjones",
    "markup": "html",
    "tags": [
        {
            "href": "/tag/atlas/",
            "relId": "atlas",
            "title": "Atlas",
            "author": null,
            "created": "2020-09-21 14:37:38 UTC",
            "updated": [
                "2020-09-21 14:37:38 UTC",
                "2023-06-22 02:17:41 UTC"
            ],
            "markup": "markdown"
        },
        {
            "href": "/tag/php/",
            "relId": "php",
            "title": "PHP",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        },
        {
            "href": "/tag/programming/",
            "relId": "programming",
            "title": "Programming",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        }
    ],
    "created": "2018-05-09 15:13:09 UTC",
    "updated": [
        "2018-05-09 15:13:09 UTC",
        "2020-10-29 21:57:07 UTC"
    ],
    "html": "<p>I\u2019m proud to announce the release of <a href=\"https://github.com/atlasphp/Atlas.Orm/releases/tag/3.0.0-beta1\">Atlas.Orm 3.0.0-beta1</a>, along with releases of the supporting <a href=\"https://github.com/atlasphp/Atlas.Mapper/releases/tag/1.0.0-beta1\">Mapper</a>, <a href=\"https://github.com/atlasphp/Atlas.Table/releases/tag/1.0.0-beta1\">Table</a>, <a href=\"https://github.com/atlasphp/Atlas.Query/releases/tag/1.0.0-beta2\">Query</a>, <a href=\"https://github.com/atlasphp/Atlas.Cli/releases/tag/2.0.0-beta2\">Cli</a>, and <a href=\"https://github.com/atlasphp/Atlas.Pdo/releases/tag/1.0.0\">Pdo</a> packages. (Atlas is a data-mapper for your persistence model, not your domain model, in PHP.)</p>\n<p>The goal for this release round was \u201cbetter IDE return typehinting support\u201d and I am happy to say that it has been a great success, though it did take some substantial renaming of classes. Unfortunately, this results in a big break from the prior alpha release; if you already have alpha-based data source skeleton classes, you will need to regenerate them with the new class names. Barring the unforeseen, this marks the first, last, and only time that regeneration will be necessary.</p>\n\n\n<a id=\"more\"</a>\n\n<h3 id=\"i\">\n<a name=\"user-content-i\" href=\"#i\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>I.</h3>\n<p>I am not a heavy user of IDEs; I lean more toward text editors like <a href=\"https://www.sublimetext.com/\">Sublime</a>. However, I work with people who use the <a href=\"https://www.jetbrains.com/phpstorm/\">PHPStorm</a> IDE extensively, and I have come to appreciate some of its features.</p>\n<p>One of those features is code autocompletion. For example, if you type <code>$foo = new Foo()</code>, and then refer to <code>$foo</code> later, the IDE will pop up a list of methods and properties on that object.</p>\n<p>This is very convenient, except that the IDE has to know what class is being referenced, in order to figure out what the hinting should be. If you are using a non- or loosely-return-typed factory/locator/container, which is what everything in PHP land does, the IDE cannot know by default how to map the requested name to an actual class. In this example \u2026</p>\n<pre><code class=\"php\">class Factory\n{\n    public static function new($class)\n    {\n        return new $class();\n    }\n}\n\n$foo = Factory::new(Foo::CLASS);\n</code></pre>\n<p>\u2026 the IDE has no idea what the return from the <code>new()</code> method is, or ought to be, so it cannot give you autocompletion hints on <code>$foo</code>.</p>\n<p>That idiom is exactly what Atlas <code>MapperLocator::get()</code> and <code>TableLocator::get()</code> use: the <code>get()</code> param is a class name, and the locator then retains and returns an instance of that class. Likewise, the overarching Atlas class methods all take a mapper class name as their first param, which Atlas uses to figure out which mapper to use for that method call.</p>\n<p>You can typehint those methods to abstract classes or interfaces, but then the IDE will not recognize any custom extensions or overrides on the returned concrete classes. What is needed is a way to determine the return type from the <code>get()</code> param, rather than from the method\u2019s return typehint.</p>\n<h3 id=\"ii\">\n<a name=\"user-content-ii\" href=\"#ii\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>II.</h3>\n<p>Lucky for us, the PHPStorm IDE allows for a <code>.phpstorm.meta.php</code> file (or a collection of files under a <code>.phpstorm.meta.php/</code> directory) where you can map the factory method inputs to their return types. (See <a href=\"https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata\">here</a> for the documentation.)</p>\n<p>On working with it, though, I found it to be a little inflexible. I expected to be able to map a string directly to a literal class name, but that didn\u2019t really work. The IDE never seemed to pick up the return typehinting. Further, with class names the way they are in the 1.x, 2.x, and 3.x-alpha releases, I would need to add a series of param-to-type mappings for every single data source class (i.e., about 10 entries for each data source type). I imagined that would become cumbersome.</p>\n<p>To adapt to this, I decided to modify the Atlas class naming with PHPStorm\u2019s \u2018@\u2019 metadata token in mind. The \u2018@\u2019 token, per the above documentation, gets replaced with the factory method parameter value, making it perfect as a class name prefix. However, you can\u2019t do any string manipulations on it; you cannot, say, call <code>substr('@', 0, -6) . 'Record'</code> and give it \u201cFooMapper\u201d to get back \u201cFooRecord\u201d. It would have to be \u2018@Record\u2019 or nothing.</p>\n<p>This leads to the biggest change in Atlas since its inception: the data source mapper classes are no longer suffixed with \u201cMapper\u201d. Previously, if you had a <code>foo</code> table, you would expect a mapper class name like <code>App\\DataSource\\Foo\\FooMapper</code>. With this naming change, the mapper class name is now <code>App\\DataSource\\Foo\\Foo</code>.  That makes the data source mapper class name a good base as a prefix for all the other data source type classes, which in turn means the \u2018@\u2019 token can be used without need for string manipulation, and it works with only a very few lines of PHPStorm metadata code.</p>\n<p>You can see the resulting <code>.phpstorm.meta.php</code> resource bundled with Atlas 3.x <a href=\"https://github.com/atlasphp/Atlas.Orm/blob/3.x/resources/phpstorm.meta.php\">here</a>. Put that at the root of your project, and PHPStorm will now successfully typehint all the Atlas method returns. (You might have to restart PHPStorm for it to take full effect.)</p>\n<h3 id=\"iii\">\n<a name=\"user-content-iii\" href=\"#iii\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>III.</h3>\n<p>There was still another category of problems, though. While the overarching Atlas class now had IDE completion, the underlying Mapper and Table packages were missing some hints on their classes and methods.</p>\n<p>For example, the base <code>Mapper::fetchRecord()</code> method is typehinted to return a <code>Record</code>, but the child <code>FooMapper::fetchRecord()</code> actually returns a <code>FooRecord</code>. The properties on the <code>FooRecord</code> are specific to the underlying <code>FooRow</code> from the table and any <code>FooRelationships</code> on the mapper, and that needs to be indicated by the IDE.</p>\n<p>Likewise, the base <code>Mapper::select()</code> class is typehinted to return the base MapperSelect class; in turn, the <code>MapperSelect::fetchRecord()</code> method is typehinted to return a <code>Record</code>. But a <code>$fooMapper-&gt;select()-&gt;fetchRecord()</code> call actually returns a <code>FooRecord</code>. Those too need to be indicated by the IDE.</p>\n<p>I ended up solving this in two steps:</p>\n<ol>\n<li>\n<p>Each data source type now gets its own type-specific select class; whereas <code>FooMapper::select()</code> used to return a general-purpose <code>Select</code>, it now returns a <code>FooSelect</code> instance extended from the general-purpose select. This type-specific select class is generated by Atlas.Cli tooling.</p>\n</li>\n<li>\n<p>On the type-specific mapper and select classes, the Atlas.Cli tooling now generates a docblock of <code>@method</code> declarations with overriding type-specific returns. (You can see an example of this in the Atlas.Testing package <a href=\"https://github.com/atlasphp/Atlas.Testing/blob/1.x/src/DataSource/Author/Author.php\">Author</a> and <a href=\"https://github.com/atlasphp/Atlas.Testing/blob/1.x/src/DataSource/Author/AuthorSelect.php\">AuthorSelect</a> classes.) PHPStorm picks up those docblocks and uses them to provide return typehints when those classes are used.</p>\n</li>\n</ol>\n<p>With those two additions to the Atlas.Cli skeleton generator, autocompletion in the PHPStorm IDE appears to be complete.</p>\n<h3 id=\"iv\">\n<a name=\"user-content-iv\" href=\"#iv\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>IV.</h3>\n<p>Of course, \u201cthere are no solutions, there are only tradeoffs.\u201d This situation is no different. Yes, Atlas now has a relatively straightforward IDE completion approach, but at these costs:</p>\n<ul>\n<li>\n<p>If IDE completion is not important to you, these very significant changes from the alpha version to the beta version will feel useless or even counter-productive.</p>\n</li>\n<li>\n<p>There are two more classes per data-source type: one type-specific MapperSelect, and one type-specific TableSelect (to typehint the Row returns).</p>\n</li>\n<li>\n<p>Not having a \u201cMapper\u201d suffix on the actual data-source mapper class may feel wrong or counter-intuitive.</p>\n</li>\n</ul>\n<p>For some, these tradeoffs will not be worth the effort to transition from the alpha release to the beta. However, after working with the beta for a while, I think they end up being a net benefit overall.</p>\n"
}
