@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1@title Adding New Classes
2@group developer
3
4Guide to adding new classes to extend Phorge.
5
6Overview
7========
8
9Phorge is highly modular, and many parts of it can be extended by adding
10new classes. This document explains how to write new classes to change or
11expand the behavior of Phorge.
12
13NOTE: The upstream offers limited support with extension development.
14
15Fundamentals
16============
17
18Phorge primarily discovers functionality by looking at concrete subclasses
19of some base class. For example, Phorge determines which applications are
20available by looking at all of the subclasses of
21@{class:PhabricatorApplication}. It
22discovers available workflows in `arc` by looking at all of the subclasses of
23@{class@arcanist:ArcanistWorkflow}. It discovers available locales
24by looking at all of the subclasses of @{class@arcanist:PhutilLocale}.
25
26This pattern holds in many cases, so you can often add functionality by adding
27new classes with no other work. Phorge will automatically discover and
28integrate the new capabilities or features at runtime.
29
30There are two main ways to add classes:
31
32 - **Extensions Directory**: This is a simple way to add new code. It is
33 less powerful, but takes a lot less work. This is good for quick changes,
34 testing and development, or getting started on a larger project.
35 - **Creating Libraries**: This is a more advanced and powerful way to
36 organize extension code. This is better for larger or longer-lived
37 projects, or any code which you plan to distribute.
38
39The next sections walk through these approaches in greater detail.
40
41
42Extensions Directory
43====================
44
45The easiest way to extend Phorge by adding new classes is to drop them
46into the extensions directory, at `phorge/src/extensions/`.
47
48This is intended as a quick way to add small pieces of functionality, test new
49features, or get started on a larger project. Extending Phorge like this
50imposes a small performance penalty compared to using a library.
51
52This directory also exists for Arcanist, so you can find a similar
53directory in `arcanist/src/extensions/`.
54
55For example, to add a new application, create a file like this one and add it
56to `phorge/src/extensions/`.
57
58```name=phorge/src/extensions/ExampleApplication.php, lang=php
59<?php
60
61final class ExampleApplication extends PhabricatorApplication {
62
63 public function getName() {
64 return pht('Example');
65 }
66
67}
68```
69
70If you load {nav Applications} in the web UI, you should now see your new
71application in the list. It won't do anything yet since you haven't defined
72any interesting behavior, but this is the basic building block of Phorge
73extensions.
74
75
76Creating Libraries
77==================
78
79A more powerful (but more complicated) way to extend Phorge is to create
80a library. Libraries can organize a larger amount of code, are easier
81to work with and distribute, and have slightly better performance than loose
82source files in the extensions directory.
83
84In general, you'll perform these one-time setup steps to create a library:
85
86 - Create a new directory.
87 - Use `arc liberate` to initialize and name the library.
88 - Configure Phorge or Arcanist to load the library.
89
90Then, to add new code, you do this:
91
92 - Write or update classes.
93 - Update the library metadata by running `arc liberate` again.
94
95Initializing a Library
96======================
97
98To create a new library, create a directory for it and run
99`arc liberate` on the directory. This documentation will use a conventional
100directory layout, which is recommended, but you are free to deviate from this.
101
102```
103$ mkdir libcustom/
104$ cd libcustom/
105libcustom/ $ arc liberate src/
106```
107
108Now you'll get a prompt like this:
109
110```lang=txt
111No library currently exists at that path...
112The directory '/some/path/libcustom/src' does not exist.
113
114 Do you want to create it? [y/N] y
115Creating new library in '/some/path/libcustom/src'.
116Choose a name for the new library.
117
118 What do you want to name this library?
119```
120
121Choose a library name (in this case, "libcustom" would be appropriate) and it
122you should get some details about the library initialization:
123
124```lang=txt
125Writing '__phutil_library_init__.php' to
126 '/some/path/libcustom/src/__phutil_library_init__.php'...
127Using library root at 'src'...
128Mapping library...
129Verifying library...
130Finalizing library map...
131 OKAY Library updated.
132```
133
134This will write three files:
135
136 - `src/.phutil_module_cache` This is a cache which makes "arc liberate"
137 faster when you run it to update the library. You can safely remove it at
138 any time. If you check your library into version control, you can add this
139 file to ignore rules (like `.gitignore`).
140 - `src/__phutil_library_init__.php` This records the name of the library and
141 tells Arcanist that a library exists here.
142 - `src/__phutil_library_map__.php` This is a map of all the symbols
143 (functions and classes) in the library, which allows them to be autoloaded
144 at runtime and dependencies to be statically managed by `arc liberate`.
145
146Linking with Phorge
147===================
148
149(NOTE) If you aren't using this library with Phorge (e.g., you are only using it
150with Arcanist or are building something else) you can skip this
151step.
152
153But, if you intend to use this library with Phorge, you need to define its
154dependency on Phorge by creating a `.arcconfig` file which points at
155Phorge. For example, you might write this file to
156`libcustom/.arcconfig`:
157
158```lang=json
159{
160 "load": [
161 "phorge/src/"
162 ]
163}
164```
165
166For details on creating a `.arcconfig`, see
167@{article:Arcanist User Guide: Configuring a New Project}. In general, this
168tells `arc liberate` that it should look for symbols in Phorge when
169performing static analysis.
170
171NOTE: If Phorge isn't located next to your custom library, specify a
172path which actually points to the `phorge/` directory.
173
174You do not need to declare dependencies on `arcanist`, since `arc liberate`
175automatically loads them.
176
177Finally, edit your Phorge config to tell it to load your library at
178runtime, by adding it to `load-libraries`:
179
180```lang=json
181...
182'load-libraries' => array(
183 'libcustom' => 'libcustom/src/',
184),
185...
186```
187
188Now, Phorge will be able to load classes from your custom library.
189
190
191Writing Classes
192===============
193
194To actually write classes, create a new module and put code in it:
195
196 libcustom/ $ mkdir src/example/
197 libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
198
199Now, run `arc liberate` to regenerate the static resource map:
200
201 libcustom/ $ arc liberate src/
202
203This will automatically regenerate the static map of the library.
204
205
206What You Can Extend And Invoke
207==============================
208
209Arcanist and Phorge are strict about extensibility of classes and
210visibility of methods and properties. Most classes are marked `final`, and
211methods have the minimum required visibility (protected or private). The goal
212of this strictness is to make it clear what you can safely extend, access, and
213invoke, so your code will keep working as the upstream changes.
214
215IMPORTANT: We'll still break APIs frequently. The upstream offers limited
216 support for extension development, and none of these APIs are stable.
217
218When developing libraries to work with Arcanist and Phorge, you should
219respect method and property visibility.
220
221If you want to add features but can't figure out how to do it without changing
222Phorge code, here are some approaches you may be able to take:
223
224 - {icon check, color=green} **Use Composition**: If possible, use composition
225 rather than extension to build your feature.
226 - {icon check, color=green} **Find Another Approach**: Check the
227 documentation for a better way to accomplish what you're trying to do.
228 - {icon check, color=green} **File a Feature Request**: Let us know what your
229 use case is so we can make the class tree more flexible or configurable, or
230 point you at the right way to do whatever you're trying to do, or explain
231 why we don't let you do it.
232
233These approaches are **discouraged**, but also possible:
234
235 - {icon times, color=red} **Fork**: Create an ad-hoc local fork and remove
236 `final` in your copy of the code. This will make it more difficult for you
237 to upgrade in the future, although it may be the only real way forward
238 depending on what you're trying to do.
239 - {icon times, color=red} **Use Reflection**: You can use
240 [[ http://php.net/manual/en/book.reflection.php | Reflection ]] to remove
241 modifiers at runtime. This is fragile and discouraged, but technically
242 possible.
243 - {icon times, color=red} **Remove Modifiers**: Send us a patch removing
244 `final` (or turning `protected` or `private` into `public`). We will almost
245 never accept these patches unless there's a very good reason that the
246 current behavior is wrong.
247
248
249Next Steps
250==========
251
252Continue by:
253
254 - visiting the [[ https://we.phorge.it/w/community_resources/ |
255 Community Resources ]] page to find or share extensions and libraries.