-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcomponents.xml
8400 lines (7005 loc) · 377 KB
/
components.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright © 2022-2024 Nikolaos Dionysopoulos
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free
Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with
no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included
in the section entitled "GNU Free Documentation License".
-->
<chapter version="5.1" xml:id="com" xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xila="http://www.w3.org/2001/XInclude/local-attributes"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:trans="http://docbook.org/ns/transclusion"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:m="http://www.w3.org/1998/Math/MathML"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:db="http://docbook.org/ns/docbook">
<title>Components</title>
<para>The Joomla component is arguably the most important extension type.
It's not just the sheer fact that it is an application inside an
application, letting us create custom experiences otherwise nearly
impossible with just core code, it is also that Joomla embraces the need for
this kind of custom experiences and has heavily invested in making component
development relatively easy. This is in stark contrast with WordPress where
your experience is far and foremost defined by its core code, custom
experiences outside custom content types are actively discouraged and there
is no sensible API for creating an equivalent extension type without a lot
of reinventing the wheel, bending over backwards and judicious application
of the Dark Arts.</para>
<section xml:id="com-mvc">
<title>The Joomla MVC: an introduction</title>
<para>Joomla came with a strong legacy of modular architecture in version
1.0, when it was a little more than a fork of and improvement upon Mambo
4, its predecessor. Joomla 1.5 introduced extension developers to the
concept of <link
xlink:href="https://en.wikipedia.org/wiki/Model–view–controller">MVC</link>
(Model-View-Controller) where each component has defined and logical
structure, separating business logic from presentation. Joomla 4 improves
upon this legacy, by further polishing the MVC and embracing concepts
introduced to the PHP developer's arsenal over the last decade and a
half.</para>
<para>Before delving into improvements and changes in Joomla 4 I think a
little Joomla MVC refresher is in order. This is especially useful if you
are used to the MVC definition from a college Computer Science class or
have used real MVC in other programming languages or even PHP frameworks
(e.g. in Laravel the typical Model is Eloquent which works completely
differently, the View is your Blade files and the Controller is more of a
collection of middleware rather than a single class — it's still MVC but a
different dialect in the same way Spanish, Italian and French are all
Romance languages).</para>
<para>Joomla follows the MVC model typically called “skinny controller -
fat model”. This puts most of the business logic into the Model and keeps
the Controller a relatively nimble affair. Further to that, Joomla also
uses another class called a Table to abstract our interaction with the
persistence layer objects (that's a fancy, pretentious way of saying
“database table”). So, it's really an MVCT approach.</para>
<para>But what are these Controllers, Models and Views anyway?</para>
<para>The Controller consists of one or more <emphasis>tasks</emphasis>
implemented as public methods. Each one of them tells the controller to
<emphasis>do</emphasis> something. For example display an article, publish
or unpublish an article, delete an article, log in the user, check the
Multi-factor Authentication provided by the user, create a user data
deletion request and so on and so forth. Controllers are orchestrators;
they know what work needs to be done but they do not do it themselves. The
Controller handles requests: it reads the user input and decides what
needs to happen next which is invariably one of two things. For simple
tasks with no output like publishing an article, deleting an article,
logging in a user etc it will get the Model, tell it what to do and then
issue a redirection to a different page, possibly setting a (very short!)
message to show to the user. Most of the time it will need to create a
document, e.g. an HTML page, a JSON document, an RSS feed etc. In this
case it will get the appropriate View, push the Model to it and ask it to
render itself. It then takes the rendered document and echoes it; Joomla
will intercept that and decide what to do with the document effectively
returned by the Controller. If an error occurred the Controller will catch
it and decide what to do with it: swallow it or push it up the stack so
that it ends up becoming an error page for the user.</para>
<para><emphasis>Note that unlike the normative Controller of the MVC
pattern the Joomla component's Controller DOES NOT normally push data to
the Model. Yes, this is a violation of the separation of concerns.
However, addressing that would be a massive backwards compatibility break
which would necessitate the rethinking and rewriting of all Joomla
components, core and third party. As a result we are unlikely to ever see
this changing.</emphasis></para>
<para>The Model is the workhorse of the component. It has the
<emphasis>business logic</emphasis>, i.e. it knows how to get things done.
In most cases it is a data-aware model which means that it knows how to
get stuff from the database, put stuff back to the database and perform
other auxiliary functions with the database data. It does NOT handle any
presentation logic, i.e. it will NOT output any HTML. It returns raw data
whenever it is asked to. Therefore you can use a Model in any context:
inside the component, in a CLI script, in the API application (which only
handles and returns JSON data), a Scheduled Task, a module, a plugin, even
a template (though that would be a bit of an architectural violation; I
won't judge you harshly if you do that because you're on a deadline and/or
a shoestring budget).</para>
<para><emphasis>Note that unlike the normative Model of the MVC pattern
the Joomla component's Model will seek data from the user session and if
it's not found there it will try to get it from the request. This makes it
a pain in the posterior to use outside the frontend, backend and API
applications, e.g. in a CLI application. Yes, there's a trick to that (and
probably a section I will have to write at some point): set its state
manually and it will no longer try to get data from the
request.</emphasis></para>
<para>As I said, Joomla also has a Table class which, architecturally
speaking, is somewhere between a Model and a Persistence Layer. But we're
not CS majors, we're Joomla extension developers. What we need to know is
that the Table class is an abstraction which represents exactly one record
of a database table. Table classes only exist in the backend of your
component but can be used anywhere. They are used either inside a Model or
directly on their own.</para>
<para><emphasis>Tables are used by Models but not when returning multiple
rows. When you run getItems on a ListModel you get an array of stdClass
objects. This sounds odd at first but it makes sense; Models can join
multiple tables and return embellished data in lists. For example, you may
not just get a user ID but also the user's name and email address as
separate fields. In most (but not all!) cases you could instantiate a
Table object and call its bind() method with one of the aforementioned
stdClass objects as its argument to get a Table object representation of
your data. Just remember in this case that instantiating a Table object is
computationally expensive as it needs to parse the database table's column
definitions. Create an instance of the Table object. Then clone it for
each row you are processing and bind the data to the Table object's clone.
When processing a few dozen or more rows it can save you hundreds of
milliseconds of page load time.</emphasis></para>
<para>Then, we have Views. Now, this is the biggest departure of the
normative MVC pattern. In Computer Science the View only renders
information in a suitable way for the user to understand. A Joomla
component's View is actually part controller and part ViewModel. Which
means that Joomla's MVC is neither MVC nor MVVM, it's its own thing. But,
as we said before, we are not CS majors, we are Joomla extension
developers. So let's see what our View does.</para>
<para>Our View gets data from the Model. It does a bit of error checking
to make sure nothing is amiss — if it is, it will show an appropriate
error state be it the new in Joomla 4 Empty State (guiding the user to
please create some data when we have nothing to show them) or an error
page if something has gone so bad we don't know what to do. It is also
responsible for setting up the page metadata (frontend) or the page's
Title and Toolbar (backend) which is View-related stuff in the same way
that making coffee is related to hosting an in-person meeting: not really
your job but people would be really upset if nobody did it.</para>
<para>In the end, the View renders the information in an appropriate
format for the user — which is what a View is really supposed to do if it
wants to be called a View! For HTML pages it calls one or more View
Template files to render some HTML. For other view types such as JSON,
Raw, XML, Feed and so on a view template file may be used but it's neither
necessary nor does it always make sense. If you're returning a JSON
document or a CSV it makes far more sense to construct it directly in your
View code and return it than going through an unnecessary round-trip
through a view template file.</para>
<para>Finally, we need to talk about the View Template files. These are
the real View of the MVC pattern: they convert the raw data we got from
our Model to the HTML which will be displayed to the user. Why not have
the View return the HTML directly? Well, we <emphasis>could</emphasis> but
we'd have two problems. First, the View itself is also part Controller and
part ViewModel therefore we'd have an issue with the separation of
concerns, mixing business logic with presentation logic (bad idea; we are
using MVC to avoid this bad practice). Second, Joomla's raw, unadulterated
power comes from the fact that our end users can
<emphasis>override</emphasis> View Templates in their sites, thereby
changing the presentation of our component in ways we can neither think of
nor support in any meaningful way across our entire user base.</para>
<para>This is the overview of Joomla's MVC and you'll be happy to know
that it has not actually changed ever since Joomla 1.5. Some
implementation details have changed but the core concepts — the good, the
bad and the ugly parts alike — have remained unchanged in Joomla 4. In
other words, if you already have a perfectly serviceable component written
for Joomla 3 you <emphasis>can</emphasis> migrate it to Joomla 4. It's not
a different language (like Italian to Spanish), it's a different
<emphasis>dialect</emphasis>. Yes, you'll need to learn the vernacular of
the new dialect but it's much, much easier than learning a new language. I
am saying that as someone who had to learn three foreign (human) languages
and more programming languages than I care to list.</para>
</section>
<section xml:id="com-j3-vs-j4-mvc">
<title>Joomla 3 MVC vs Joomla 4 MVC</title>
<para>As we already said, the basic functionality of Joomla's MVC has
remained the same throughout Joomla 3 and 4. If you know how to make a
Joomla 3 component you know most of what you need to make a Joomla 4
component. The differences are a few minor, but salient, points.</para>
<para><link linkend="com-namespaces"><emphasis
role="bold">Namespaces</emphasis></link>. All native Joomla 4 components
use <link
xlink:href="https://www.php.net/manual/en/language.namespaces.php">PHP
namespaces</link> for their PHP code. This is a simple change in naming
classes for models, views and controllers. For example, instead of your
frontend model being called <code>ExampleModelItem</code> it will now be
the class <code>ItemModel</code> in the namespace
<classname>\Acme\Component\Example\Site\Model</classname>. The first part
of the namespace in this example, <code>\Acme\Component\Example</code>, is
the same for the whole component. This component namespace prefix consists
of 3 parts:<programlisting>\<replaceable>My</replaceable>\Component\<replaceable>ComponentName</replaceable></programlisting></para>
<para>for instance (as often used throughout this book):<programlisting>\Acme\Component\Example</programlisting>The
3 parts of a component namespace prefix:</para>
<itemizedlist>
<listitem>
<para><code><emphasis
role="bold">\<replaceable>My</replaceable></emphasis></code> vendor
namespace prefix: a valid PHP namespace, unique for you, your company
or your project. A valid vendor namespace prefix can for instance be
<classname>\Acme</classname>. A vendor namespace prefix can also have
sub-namespaces, for instance:
<classname>\Acme\Support\Tools</classname>. You are free to choose
your own vendor namespace, however:</para>
<itemizedlist>
<listitem>
<para>The vendor namespace should not start with
<classname>\Joomla</classname>, because component namespaces
starting withIn <classname>\Joomla</classname> are reserved for
core components.</para>
</listitem>
<listitem>
<para>The vendor namespace should not contain a sub-namespace
starting with <classname>\Component</classname>. It is a reserved
sub-namespace, that should only be used immediately AFTER the
vendor namespace prefix in a component namespace.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><code><emphasis role="bold">\Component</emphasis></code>
sub-namespace: literally "\Component", with a capital C. You should
use the <code>\Component</code> sub-namespace in the component
namespace only once: AFTER the vendor namespace and BEFORE the
sub-namespace with the component name.</para>
</listitem>
<listitem>
<para><code><emphasis
role="bold">\<replaceable>ComponentName</replaceable></emphasis></code>
sub-namespace: a valid component name for Joomla as is used in
com_componentname. In the namespace-part you can use camel case; in
com_componentname only lowercase is used. A valid component name would
for instance be <classname>Example</classname> in the namespace
<classname>Acme\Component\Example</classname> for component
<code>com_example</code>.</para>
</listitem>
</itemizedlist>
<para>Namespaces are originally meant to avoid name collisions for
classes, functions and constants, while conveniently using shorter names
to identify those classes, functions and constants. In Joomla 3 many
frontend and backend classes had the same name, like for instance
<classname>ContentModelArticle</classname>, and those frontend and backend
classes couldn't be used together. Namespaces to the rescue: the frontend
class, including its namespace, is now called
<classname>\Joomla\Component\Content\Site\Model\ArticleModel</classname>
and the backend class
<classname>\Joomla\Component\Content\Administrator\Model\ArticleModel</classname>.</para>
<para>In the PHP-world there is a standard for autoloading, which uses
namespaces: <link
xlink:href="https://www.php-fig.org/psr/psr-4/">PSR4</link>. A namespace
prefix is mapped to a path in the filesystem. Any sub-namespaces that are
under the mapped namespace are one-on-one mapped to subdirectories with
the same names. For instance the namespace
<classname>\Joomla\Component\Content\Administrator</classname> is mapped
to <filename>/administrator/components/com_content/src</filename>. The
class
<classname>\Joomla\Component\Content\Administrator\Model\ArticleModel</classname>
is then found in the subdirectory <filename>/Model</filename> of
<filename>/administrator/components/com_content/src</filename>. Any code
with a namespace that adheres to PSR4 can automatically be autoloaded. In
Joomla the mapping is cached in the
<filename>administrator/cache/autoload_psr4.php</filename> file.</para>
<para>Because of the PSR4 autoloading, the namespace of your custom
component can be used to locate any file in your component from elsewhere
in Joomla. For example, to find the code files for custom form fields you
may need in your component. The way to tell Joomla which namespace to use
for custom form fields is with the <code>addfieldprefix</code> attribute
in an XML form. This attribute of a fieldset (and method of
<classname>Joomla\CMS\Form\FormHelper</classname>) is new since Joomla 3.8
and can be used to add a namespace for your custom fields, so they can be
found with PSR4 autoloading, instead of directly providing a path as was
done with the <code>addfieldpath</code> attribute.</para>
<para><link linkend="com-services"><emphasis role="bold">Service
provider</emphasis></link>. All Joomla components are passed a Dependency
Injection Container (DIC) which is, in fact, a Service Locator. This even
applies to components written with the Joomla 3 MVC as we'll see in <link
linkend="com-lifetime">the lifetime of a component</link>. You can
register services specific to your component in your service locator. At
the very least, Joomla expects you to set up the <link
linkend="com-extension">extension class</link> of your component.
Unfortunately, even simple components need to have a service provider as
there is no convention-over-configuration in Joomla 4's component
DIC.</para>
<para><link linkend="com-extension"><emphasis role="bold">Extension
class</emphasis></link>. The extension class <emphasis>replaces</emphasis>
the entry point file (e.g.
<filename>administrator/components/com_example/example.php</filename>) you
had in Joomla 3 MVC. All components written with the Joomla 4 MVC have an
extension class which extends from
<code>Joomla\CMS\Extension\MVCComponent</code>. It is used to register
special services used when working with your component such as the
<code>MVCFactory</code> service (responsible for creating model, view and
controller object instances), an <link linkend="com-html">HTML helper
service</link> which extends Joomla's HTMLHelper, a <link
linkend="com-router">Router service</link> for frontend search engine
friendly (SEF) URL routing, and a <link linkend="com-categories">Category
service</link> if your component uses core Categories. It also returns
your component's <link linkend="com-dispatcher">Dispatcher</link> which is
where the execution of your component starts. For very simple components
you can even use the core MVCComponent class as your extension class,
without sub-classing. See, for example, what the core
<code>com_actionlogs</code> component does.</para>
<para><emphasis role="bold"><link
linkend="com-dispatcher">Dispatcher</link> instead of a front
controller</emphasis>. In the Joomla 3 MVC paradigm you had a
<filename>controller.php</filename> file in the root of your component's
directory structure. This was a front-controller, supposed to figure out
which controller to use to handle the specified view and task in the
request. This was a bit gimmicky and error-prone, especially if you had
components capable of displaying more than one discrete types of
information, e.g. a downloads repository would be able to display
categories, releases in a category and files in a release which made for
three discrete content types which had to be handled by the same front
controller. The new Dispatcher does the same job, it works without
sub-classing it for most simple extensions and benefits greatly the more
complex extensions which need more involved request processing.</para>
<para><emphasis role="bold"><link linkend="com-html">HTML helper</link> is
a service</emphasis>. If you had ever registered an HTMLHelper class with
static calls this is no longer the recommended method. If you need an HTML
helper for your extension you need to register it through the extension
class and its methods are no longer static. Since it's a real object
instantiated by the extension object it has access to the component's DIC,
therefore to any service you registered in your service provider or you
were given by Joomla in the DIC. This makes helpers testable and means
that they can access custom services (e.g. a shipping cost calculation
service if you are writing an e-commerce component) without trying to
contort your code.</para>
<para><emphasis role="bold">New format for </emphasis><link
linkend="com-router"><emphasis role="bold">Router</emphasis></link>.
Joomla 4 comes with a new Router which is instantiated through a service.
The new component router superclass allows you to write efficient routers
without a lot of boilerplate, duplicated and convoluted code.</para>
</section>
<section xml:id="com-lifetime">
<title>The lifetime of a component</title>
<para>In the previous section we talked about the new concepts in the
Joomla 4 MVC but it's really hard to understand how they all work
together. It's best if we talk about the lifetime of a component, i.e.
what happens when you try to access a Joomla URL in the form
<uri>index.php?option=com_example&view=foo</uri>.</para>
<para>The lifetime of our component starts in the application object
(<code>SiteApplication</code>, <code>AdministratorApplication</code> or
<code>ApiApplication</code>), namely its <code>dispatch()</code> method.
All these methods do some initialisation and call
<code>Joomla\CMS\Component::renderComponent()</code>. This method runs
this line where things get interesting for our component:</para>
<programlisting language="php">$app->bootComponent($option)->getDispatcher($app)->dispatch();</programlisting>
<para>Let's rewrite it so it's more readable:</para>
<para><programlisting language="php">$componentExtension = $app->bootComponent($option)
$componentDispatcher = $componentExtension->getDispatcher($app)
$componentDispatcher->dispatch();</programlisting>What Joomla
does is ask the application object to <emphasis>somehow</emphasis> get a
component extension object — even if it is a legacy component using the
Joomla 3 MVC. Then it asks the component extension object to get it the
component's Dispatcher object. Finally, it tells the component's
Dispatcher to dispatch (execute) the component.</para>
<para>These three steps are crucial to understanding the difference
between legacy Joomla 3 MVC components, modern Joomla 4 MVC components and
how Joomla 4 makes them all work.</para>
<section xml:id="com-lifetime-booting">
<title>Booting the component</title>
<para>The <code>bootComponent</code> method is implemented by
<classname>Joomla\CMS\Extension\ExtensionManagerTrait</classname>. The
first thing it does, by calling its <code>loadExtension</code> method,
is to figure out if this is a Joomla 4 MVC or Joomla 3 MVC component. It
does that by looking for the <filename>services/provider.php</filename>
file under the component's root. As you may have guessed this is our
component's <link linkend="com-services">service provider</link> and the
reason why all Joomla 4 MVC components, even the simplest ones for which
we could infer their service provider, really do need to have a service
provider file.</para>
<para>If the file exists it's loaded and its services are registered
into the copy of the Joomla DIC which is used as our component's
DIC.</para>
<note>
<para>This means that our component only sees a frozen in time copy of
the global application services. You cannot register any services in
the component's DIC and expect them to be available in the global
application.</para>
</note>
<para>If the file does not exist, Joomla creates an instance of the
<classname>Joomla\CMS\Extension\LegacyComponent</classname>. As you can
see, this extension object uses a legacy MVC factory object which
"speaks" the Joomla 3 non-namespaced MVC class names and a legacy
component dispatcher which looks for the
<filename>componentName.php</filename> (in our example:
<filename>example.php</filename>) component entry point file and loads
it. This is exactly how Joomla 4 can render components using Joomla 3
MVC.</para>
<para>The rest of this section will talk about a Joomla 4 MVC
component.</para>
<para>Finally, Joomla returns <link linkend="com-extension">the
component's extension object</link> as the result of the
<code>bootComponent</code> method.</para>
<tip>
<para>You can call
<code>\Joomla\CMS\Factory::getApplication()->bootComponent('com_example')</code>
to get the <code>com_example</code> component's extension object
anytime, anywhere — <emphasis role="bold">including in modules and
plugins</emphasis>. If you subclass the
<code>Joomla\CMS\Extension\MVCExtension</code> class in your own
component you can create your own methods to return <emphasis>ANY
OBJECT KNOWN TO THE COMPONENT AND ITS CONTAINER</emphasis>.</para>
<para>This is of enormous importance.</para>
<para>You can instantiate any MVC class of the component. You have
access to your HTML helper, categories service and router service. You
can get an instance of any of the custom services you created in your
component. All of that WITHOUT any static helpers in your extension,
WITHOUT worrying that something might break if you call your code
outside your component. Your Models can provide a public API for third
party developers communicating with your component.</para>
<para>Joomla core components already do that. For example, you can get
the backend <classname>ArticleModel</classname> of
<code>com_content</code> in a frontend component or plugin to create a
new Joomla article <emphasis>the right way</emphasis>:</para>
<programlisting language="php">$articleModel = \Joomla\CMS\Factory::getApplication()
->bootComponent('com_content')
->getMVCFactory()
->createModel('Article', 'Administrator');</programlisting>
</tip>
</section>
<section xml:id="com-lifetime-dispatcher">
<title>Getting the Dispatcher</title>
<para>The extension object is responsible for returning a number of
interesting objects through its methods. These objects are meant to be
used for interacting with the component. One of these is
<code>getDispatcher</code> which lets us retrieve the component's <link
linkend="com-dispatcher">Dispatcher</link> object.</para>
<para>Your extension can have a custom dispatcher class,
<classname>\<replaceable>My</replaceable>\Component\<replaceable>Example</replaceable>\Dispatcher\Dispatcher</classname>,
where
<classname>\<replaceable>My</replaceable>\Component\<replaceable>Example</replaceable></classname>
is your component's prefix. If this class exists, an object instance of
it is returned.</para>
<para>If your extension does not have a dispatcher an instance of the
default <classname>Joomla\CMS\Dispatcher\ComponentDispatcher</classname>
(site and administrator applications) or
<classname>Joomla\CMS\Dispatcher\ApiDispatcher</classname> (API
application) will be created and returned instead.</para>
</section>
<section xml:id="com-lifetime-dispatching">
<title>Dispatching the component</title>
<para>The dispatcher object has a <code>dispatch()</code> method. This
method looks in the request data to figure out which controller it needs
to instantiate and which task it should execute on it. The controller
object is instantiated through your component's
<classname>MVCFactory</classname> service and its <code>execute</code>
and <code>redirect</code> methods are called on it. So, yes, this is
basically what your old Joomla 3 MVC <filename>controller.php</filename>
file was doing.</para>
<tip>
<para>Having a custom dispatcher means that you can manipulate the
<emphasis>controller and task</emphasis> based on other request
variables.</para>
<para>This is very important when your component is accessed
<emphasis>outside of a Joomla menu item (no Itemid in the request
variables)</emphasis>. In this case Joomla always uses the default
(Home) menu item for the current language. This means that you may be
getting a <uri>controller</uri>, <uri>view</uri> or <uri>task</uri>
request variable — as well as other variables which might mess with
your component, like <uri>id</uri> — from the Home menu item.</para>
<para>Your Dispatcher is responsible for figuring out if this is the
case and fix the input variables to prevent weird bugs / showing the
wrong page of your component when it's accessed without a menu item
id, e.g. a URL like <uri>/component/example?view=foobar</uri>. This is
something which has happened a lot when I converted my own extensions
to Joomla 4, including LoginGuard (the extension contributed as
Joomla's Multi-factor Authentication feature in Joomla 4.2).</para>
</tip>
</section>
</section>
<section xml:id="com-dirs">
<title>Directory structure</title>
<para>The directory structure of a typical Joomla 4 MVC component is as
follows, assuming it's called <code>com_example</code> and has the
namespace prefix <code>\Acme\Component\Example</code>. Directories are
suffixed with <filename>/</filename>. Please note that capitalisation
matters (directory and file names are case-sensitive).</para>
<itemizedlist>
<listitem>
<para><filename>administrator/components/com_example/</filename> The
component's backend directory</para>
<itemizedlist>
<listitem>
<para><filename>forms/</filename> Optional. List view forms and
search tools forms (formerly in models/forms)</para>
<itemizedlist>
<listitem>
<para><filename>items.xml</filename> An example form for the
Items list view.</para>
</listitem>
<listitem>
<para><filename>filter_items.xml</filename> An example search
tools form for the Items list view.</para>
</listitem>
<listitem>
<para><filename>item.xml</filename> An example form for the
Item edit view.</para>
</listitem>
<listitem>
<para>…</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>language/</filename> Not required but you should
always somehow provide language files with your extension. The
language files of your component (typically installed in
<filename>administrator/languages/en-GB</filename>)</para>
<itemizedlist>
<listitem>
<para><filename>en-GB/</filename> The English (Great Britain)
language files. That's the default Joomla language.</para>
<itemizedlist>
<listitem>
<para><filename>com_example.ini</filename> The main
language file for your component's views. Note that there
is no longer an <filename>en-GB.</filename> prefix; it's
implied by the directory the language file is in.</para>
</listitem>
<listitem>
<para><filename>com_example.sys.ini</filename> The
language file used by Joomla to display backend menu
items, permissions, the component's Options page, core
categories used by your component, select menu item types
in the menu manager, and render each menu item type's
configuration parameters.</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>layouts/</filename> Optional. Any Joomla Layouts
you want to ship with your extension for use in its
backend.</para>
</listitem>
<listitem>
<para><filename>services/</filename> Required. Here lives the
<link linkend="com-services">Service Provider</link> of your
component.</para>
<itemizedlist>
<listitem>
<para><filename>provider.php</filename> The service provider
file.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>sql/</filename> Optional, unless your extension
makes changes to database structure or content.</para>
<itemizedlist>
<listitem>
<para><filename>updates/</filename> Required. Your component's
schema (database) files applied on each update.</para>
<itemizedlist>
<listitem>
<para><filename>mysql/</filename> Optional. Indicates
updates for sites running on the MySQL database.</para>
<itemizedlist>
<listitem>
<para><filename>1.0.0-20220815-0000.sql</filename> A
database update file. You should name them
<filename>version-date-time.sql</filename>. The
version part is mandatory.</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>install.mysql.utf8.sql</filename> The SQL
executed on your component's first installation. Update files
are not executed in this case.</para>
</listitem>
<listitem>
<para><filename>uninstall.mysql.utf8.sql</filename> The SQL
executed on your component's uninstallation. Remove any tables
or database data you created.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>src/ Required. The root of your extension's PHP files and
the root of the PSR-4
<code>\Acme\Component\Example\Administrator</code> namespace
prefix.</para>
<itemizedlist>
<listitem>
<para><filename>Controller/</filename> Required. Your
component's controllers</para>
<itemizedlist>
<listitem>
<para><filename>ItemsController.php</filename> An example
controller for a ListView</para>
</listitem>
<listitem>
<para><filename>ItemController.php</filename> An example
controller for an AdminView</para>
</listitem>
<listitem>
<para>…</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Dispatcher/</filename> Optional. Holds your
custom component dispatcher.</para>
<itemizedlist>
<listitem>
<para><filename>Dispatcher.php</filename> Your custom
component dispatcher.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Extension/</filename> Optional. Holds your
custom extension class.</para>
<itemizedlist>
<listitem>
<para><filename>ExampleExtension.php</filename> Your
custom extension class.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Field/</filename> Optional. Custom form
fields. Use them in your XML forms using the attribute
<code>addfieldprefix="Acme\Component\Example\Administrator\Field"</code>.</para>
</listitem>
<listitem>
<para><filename>Model/</filename> Required. Your component's
Models.</para>
<itemizedlist>
<listitem>
<para><filename>ItemsModel.php</filename> An example model
for the Items list view.</para>
</listitem>
<listitem>
<para><filename>ItemModel.php</filename> An example model
for the Item view. Handles editing and adding
records.</para>
</listitem>
<listitem>
<para>…</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Provider/</filename> Optional. Custom service
providers.</para>
<itemizedlist>
<listitem>
<para><filename>RouterFactory.php</filename> Example of a
RouterFactory provider, if you need a frontend SEF URL
router.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Router/</filename> Optional. The recommended
place to put your custom RouterFactory, if you need a frontend
SEF URL router.</para>
</listitem>
<listitem>
<para><filename>Service/</filename> Optional. Custom
services.</para>
<itemizedlist>
<listitem>
<para><filename>Html/</filename> Optional. Where your
custom HTML services (which are made available through
Joomla's HTMLHelper) live.</para>
<itemizedlist>
<listitem>
<para><filename>Example.php</filename> An example HTML
service.</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Table/</filename> Required. Your Table
classes.</para>
<itemizedlist>
<listitem>
<para><filename>ItemTable.php</filename> An example table
class for the items of this component (which should
ideally live in the <database>#__example_items</database>
table if you want to follow Joomla best practices).</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>View/</filename> Required. Your view classes.
All subdirectories must be in Uppercasefirst format and match
the controller's name before the <code>Controller</code>
word.</para>
<itemizedlist>
<listitem>
<para><filename>Items/</filename> The directory for the
Items list view.</para>
<itemizedlist>
<listitem>
<para><filename>HtmlView.php</filename> The HTML view
class for rendering this backend view.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Item/</filename> The directory for the
Item add/edit view.</para>
<itemizedlist>
<listitem>
<para><filename>HtmlView.php</filename> The HTML view
class for rendering this backend view.</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>tmpl/</filename> Required. The view templates for
your backend views.</para>
<important>
<para>All the sub-directories are in <emphasis role="bold">all
lowercase</emphasis>. This is VERY important!</para>
<para>Be careful if you are developing on Windows or macOS
(which use case-insensitive filesystems). You might accidentally
use MixedCase or Uppercasefirst folder names. While this will
work fine on Windows and macOS it will <emphasis>fail</emphasis>
on the Linux servers most sites run on. That's because Linux
primarily uses case-sensitive filesystems.</para>
</important>
<itemizedlist>
<listitem>
<para><filename>items/</filename></para>
<itemizedlist>
<listitem>
<para><filename>default.php</filename> The view template
for the Items view.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>item/</filename></para>
<itemizedlist>
<listitem>
<para><filename>edit.php</filename> The view template for
the Item view.</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>access.xml</filename> The component's permissions
configuration file</para>
</listitem>
<listitem>
<para><filename>config.xml</filename> The component;s Options page
form file</para>
</listitem>
<listitem>
<para><filename>example.xml</filename> The component's XML
manifest</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>components/com_example/</filename> The component's
frontend directory</para>
<itemizedlist>
<listitem>
<para><filename>forms/</filename> Optional. Any forms for frontend
views (formerly in models/forms).</para>
</listitem>
<listitem>
<para><filename>language/</filename> Not required but you should
always somehow provide language files with your extension. The
language files of your component (typically installed in
<filename>languages/en-GB</filename>)</para>
<itemizedlist>
<listitem>
<para><filename>en-GB/</filename> The English (Great Britain)
language files. That's the default Joomla language.</para>
<itemizedlist>
<listitem>
<para><filename>com_example.ini</filename> The main
language file for your component's frontend views.</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>layouts/</filename> Optional. Any Joomla Layouts
you want to ship with your extension for use in its
frontend.</para>
</listitem>
<listitem>
<para>src/ Required. The root of your extension's PHP files and
the root of the PSR-4 <code>\Acme\Component\Example\Site</code>
namespace prefix.</para>
<itemizedlist>
<listitem>
<para><filename>Controller/</filename> Required. Your
component's controllers</para>
</listitem>
<listitem>
<para><filename>Dispatcher/</filename> Optional. Holds your
custom component dispatcher for the frontend only.</para>
<itemizedlist>
<listitem>
<para><filename>Dispatcher.php</filename> Your custom
component frontend dispatcher.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Model/</filename> Required. Your component's
Models.</para>
<itemizedlist>
<listitem>
<para><filename>ItemsModel.php</filename> An example model
for the Items list view.</para>
</listitem>
<listitem>
<para><filename>ItemModel.php</filename> An example model
for the Item view. Handles editing and adding
records.</para>
</listitem>
<listitem>
<para>…</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>Service/</filename> Optional. Custom
services.</para>
<itemizedlist>
<listitem>
<para><filename>Category.php</filename> Optional. Your
site's category service.</para>
</listitem>
<listitem>
<para><filename>Router.php</filename> Optional. Your
site's SEF router.</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><filename>View/</filename> Required. Your frontend view
classes. All subdirectories must be in Uppercasefirst format
and match the controller's name before the
<code>Controller</code> word.</para>
</listitem>
</itemizedlist>
</listitem>