Changeset View
Changeset View
Standalone View
Standalone View
src/docs/contributor/adding_new_classes.diviner
@title Adding New Classes | @title Adding New Classes | ||||
@group developer | @group developer | ||||
Guide to adding new classes to extend Phabricator. | Guide to adding new classes to extend Phorge. | ||||
Overview | Overview | ||||
======== | ======== | ||||
Phabricator is highly modular, and many parts of it can be extended by adding | Phorge is highly modular, and many parts of it can be extended by adding | ||||
new classes. This document explains how to write new classes to change or | new classes. This document explains how to write new classes to change or | ||||
expand the behavior of Phabricator. | expand the behavior of Phorge. | ||||
IMPORTANT: The upstream does not offer support with extension development. | NOTE: The upstream offers limited support with extension development. | ||||
Fundamentals | Fundamentals | ||||
============ | ============ | ||||
Phabricator primarily discovers functionality by looking at concrete subclasses | Phorge primarily discovers functionality by looking at concrete subclasses | ||||
of some base class. For example, Phabricator determines which applications are | of some base class. For example, Phorge determines which applications are | ||||
available by looking at all of the subclasses of | available by looking at all of the subclasses of | ||||
@{class@phabricator:PhabricatorApplication}. It | @{class@phabricator:PhabricatorApplication}. It | ||||
discovers available workflows in `arc` by looking at all of the subclasses of | discovers available workflows in `arc` by looking at all of the subclasses of | ||||
@{class@arcanist:ArcanistWorkflow}. It discovers available locales | @{class@arcanist:ArcanistWorkflow}. It discovers available locales | ||||
by looking at all of the subclasses of @{class@arcanist:PhutilLocale}. | by looking at all of the subclasses of @{class@arcanist:PhutilLocale}. | ||||
This pattern holds in many cases, so you can often add functionality by adding | This pattern holds in many cases, so you can often add functionality by adding | ||||
new classes with no other work. Phabricator will automatically discover and | new classes with no other work. Phorge will automatically discover and | ||||
integrate the new capabilities or features at runtime. | integrate the new capabilities or features at runtime. | ||||
There are two main ways to add classes: | There are two main ways to add classes: | ||||
- **Extensions Directory**: This is a simple way to add new code. It is | - **Extensions Directory**: This is a simple way to add new code. It is | ||||
less powerful, but takes a lot less work. This is good for quick changes, | less powerful, but takes a lot less work. This is good for quick changes, | ||||
testing and development, or getting started on a larger project. | testing and development, or getting started on a larger project. | ||||
- **Creating Libraries**: This is a more advanced and powerful way to | - **Creating Libraries**: This is a more advanced and powerful way to | ||||
organize extension code. This is better for larger or longer-lived | organize extension code. This is better for larger or longer-lived | ||||
projects, or any code which you plan to distribute. | projects, or any code which you plan to distribute. | ||||
The next sections walk through these approaches in greater detail. | The next sections walk through these approaches in greater detail. | ||||
Extensions Directory | Extensions Directory | ||||
==================== | ==================== | ||||
The easiest way to extend Phabricator by adding new classes is to drop them | The easiest way to extend Phorge by adding new classes is to drop them | ||||
into the extensions directory, at `phabricator/src/extensions/`. | into the extensions directory, at `pohrge/src/extensions/`. | ||||
This is intended as a quick way to add small pieces of functionality, test new | This is intended as a quick way to add small pieces of functionality, test new | ||||
features, or get started on a larger project. Extending Phabricator like this | features, or get started on a larger project. Extending Phorge like this | ||||
imposes a small performance penalty compared to using a library. | imposes a small performance penalty compared to using a library. | ||||
This directory exists in all libphutil libraries, so you can find a similar | This directory also exists for Arcanist, so you can find a similar | ||||
directory in `arcanist/src/extensions/`. | directory in `arcanist/src/extensions/`. | ||||
For example, to add a new application, create a file like this one and add it | For example, to add a new application, create a file like this one and add it | ||||
to `phabricator/src/extensions/`. | to `phorge/src/extensions/`. | ||||
```name=phabricator/src/extensions/ExampleApplication.php, lang=php | ```name=phorge/src/extensions/ExampleApplication.php, lang=php | ||||
<?php | <?php | ||||
final class ExampleApplication extends PhabricatorApplication { | final class ExampleApplication extends PhabricatorApplication { | ||||
public function getName() { | public function getName() { | ||||
return pht('Example'); | return pht('Example'); | ||||
} | } | ||||
} | } | ||||
``` | ``` | ||||
If you load {nav Applications} in the web UI, you should now see your new | If you load {nav Applications} in the web UI, you should now see your new | ||||
application in the list. It won't do anything yet since you haven't defined | application in the list. It won't do anything yet since you haven't defined | ||||
any interesting behavior, but this is the basic building block of Phabricator | any interesting behavior, but this is the basic building block of Phorge | ||||
extensions. | extensions. | ||||
Creating Libraries | Creating Libraries | ||||
================== | ================== | ||||
A more powerful (but more complicated) way to extend Phabricator is to create | A more powerful (but more complicated) way to extend Phorge is to create | ||||
a libphutil library. Libraries can organize a larger amount of code, are easier | a library. Libraries can organize a larger amount of code, are easier | ||||
to work with and distribute, and have slightly better performance than loose | to work with and distribute, and have slightly better performance than loose | ||||
source files in the extensions directory. | source files in the extensions directory. | ||||
In general, you'll perform these one-time setup steps to create a library: | In general, you'll perform these one-time setup steps to create a library: | ||||
- Create a new directory. | - Create a new directory. | ||||
- Use `arc liberate` to initialize and name the library. | - Use `arc liberate` to initialize and name the library. | ||||
- Configure Phabricator or Arcanist to load the library. | - Configure Phorge or Arcanist to load the library. | ||||
Then, to add new code, you do this: | Then, to add new code, you do this: | ||||
- Write or update classes. | - Write or update classes. | ||||
- Update the library metadata by running `arc liberate` again. | - Update the library metadata by running `arc liberate` again. | ||||
Initializing a Library | Initializing a Library | ||||
====================== | ====================== | ||||
To create a new libphutil library, create a directory for it and run | To create a new library, create a directory for it and run | ||||
`arc liberate` on the directory. This documentation will use a conventional | `arc liberate` on the directory. This documentation will use a conventional | ||||
directory layout, which is recommended, but you are free to deviate from this. | directory layout, which is recommended, but you are free to deviate from this. | ||||
``` | ``` | ||||
$ mkdir libcustom/ | $ mkdir libcustom/ | ||||
$ cd libcustom/ | $ cd libcustom/ | ||||
libcustom/ $ arc liberate src/ | libcustom/ $ arc liberate src/ | ||||
``` | ``` | ||||
Show All 31 Lines | - `src/.phutil_module_cache` This is a cache which makes "arc liberate" | ||||
any time. If you check your library into version control, you can add this | any time. If you check your library into version control, you can add this | ||||
file to ignore rules (like `.gitignore`). | file to ignore rules (like `.gitignore`). | ||||
- `src/__phutil_library_init__.php` This records the name of the library and | - `src/__phutil_library_init__.php` This records the name of the library and | ||||
tells libphutil that a library exists here. | tells libphutil that a library exists here. | ||||
- `src/__phutil_library_map__.php` This is a map of all the symbols | - `src/__phutil_library_map__.php` This is a map of all the symbols | ||||
(functions and classes) in the library, which allows them to be autoloaded | (functions and classes) in the library, which allows them to be autoloaded | ||||
at runtime and dependencies to be statically managed by `arc liberate`. | at runtime and dependencies to be statically managed by `arc liberate`. | ||||
Linking with Phabricator | Linking with Phorge | ||||
======================== | =================== | ||||
If you aren't using this library with Phabricator (e.g., you are only using it | (NOTE) If you aren't using this library with Phorge (e.g., you are only using it | ||||
with Arcanist or are building something else on libphutil) you can skip this | with Arcanist or are building something else) you can skip this | ||||
chris: Remove the `libphutil` reference while we're in here? | |||||
step. | step. | ||||
But, if you intend to use this library with Phabricator, you need to define its | But, if you intend to use this library with Phorge, you need to define its | ||||
dependency on Phabricator by creating a `.arcconfig` file which points at | dependency on Phorge by creating a `.arcconfig` file which points at | ||||
Phabricator. For example, you might write this file to | Phorge. For example, you might write this file to | ||||
`libcustom/.arcconfig`: | `libcustom/.arcconfig`: | ||||
```lang=json | ```lang=json | ||||
{ | { | ||||
"load": [ | "load": [ | ||||
"phabricator/src/" | "phorge/src/" | ||||
] | ] | ||||
} | } | ||||
``` | ``` | ||||
For details on creating a `.arcconfig`, see | For details on creating a `.arcconfig`, see | ||||
@{article:Arcanist User Guide: Configuring a New Project}. In general, this | @{article:Arcanist User Guide: Configuring a New Project}. In general, this | ||||
tells `arc liberate` that it should look for symbols in Phabricator when | tells `arc liberate` that it should look for symbols in Phorge when | ||||
performing static analysis. | performing static analysis. | ||||
NOTE: If Phabricator isn't located next to your custom library, specify a | NOTE: If Phorge isn't located next to your custom library, specify a | ||||
path which actually points to the `phabricator/` directory. | path which actually points to the `phorge/` directory. | ||||
You do not need to declare dependencies on `arcanist`, since `arc liberate` | You do not need to declare dependencies on `arcanist`, since `arc liberate` | ||||
automatically loads them. | automatically loads them. | ||||
Finally, edit your Phabricator config to tell it to load your library at | Finally, edit your Phorge config to tell it to load your library at | ||||
runtime, by adding it to `load-libraries`: | runtime, by adding it to `load-libraries`: | ||||
```lang=json | ```lang=json | ||||
... | ... | ||||
'load-libraries' => array( | 'load-libraries' => array( | ||||
'libcustom' => 'libcustom/src/', | 'libcustom' => 'libcustom/src/', | ||||
), | ), | ||||
... | ... | ||||
``` | ``` | ||||
Now, Phabricator will be able to load classes from your custom library. | Now, Phorge will be able to load classes from your custom library. | ||||
Writing Classes | Writing Classes | ||||
=============== | =============== | ||||
To actually write classes, create a new module and put code in it: | To actually write classes, create a new module and put code in it: | ||||
libcustom/ $ mkdir src/example/ | libcustom/ $ mkdir src/example/ | ||||
libcustom/ $ nano src/example/ExampleClass.php # Edit some code. | libcustom/ $ nano src/example/ExampleClass.php # Edit some code. | ||||
Now, run `arc liberate` to regenerate the static resource map: | Now, run `arc liberate` to regenerate the static resource map: | ||||
libcustom/ $ arc liberate src/ | libcustom/ $ arc liberate src/ | ||||
This will automatically regenerate the static map of the library. | This will automatically regenerate the static map of the library. | ||||
What You Can Extend And Invoke | What You Can Extend And Invoke | ||||
============================== | ============================== | ||||
Arcanist and Phabricator are strict about extensibility of classes and | Arcanist and Pohrge are strict about extensibility of classes and | ||||
visibility of methods and properties. Most classes are marked `final`, and | visibility of methods and properties. Most classes are marked `final`, and | ||||
methods have the minimum required visibility (protected or private). The goal | methods have the minimum required visibility (protected or private). The goal | ||||
of this strictness is to make it clear what you can safely extend, access, and | of this strictness is to make it clear what you can safely extend, access, and | ||||
invoke, so your code will keep working as the upstream changes. | invoke, so your code will keep working as the upstream changes. | ||||
IMPORTANT: We'll still break APIs frequently. The upstream does not support | IMPORTANT: We'll still break APIs frequently. The upstream offers limited | ||||
extension development, and none of these APIs are stable. | support for extension development, and none of these APIs are stable. | ||||
When developing libraries to work with Arcanist and Phabricator, you should | When developing libraries to work with Arcanist and Phorge, you should | ||||
respect method and property visibility. | respect method and property visibility. | ||||
If you want to add features but can't figure out how to do it without changing | If you want to add features but can't figure out how to do it without changing | ||||
Phabricator code, here are some approaches you may be able to take: | Phorge code, here are some approaches you may be able to take: | ||||
- {icon check, color=green} **Use Composition**: If possible, use composition | - {icon check, color=green} **Use Composition**: If possible, use composition | ||||
rather than extension to build your feature. | rather than extension to build your feature. | ||||
- {icon check, color=green} **Find Another Approach**: Check the | - {icon check, color=green} **Find Another Approach**: Check the | ||||
documentation for a better way to accomplish what you're trying to do. | documentation for a better way to accomplish what you're trying to do. | ||||
- {icon check, color=green} **File a Feature Request**: Let us know what your | - {icon check, color=green} **File a Feature Request**: Let us know what your | ||||
use case is so we can make the class tree more flexible or configurable, or | use case is so we can make the class tree more flexible or configurable, or | ||||
point you at the right way to do whatever you're trying to do, or explain | point you at the right way to do whatever you're trying to do, or explain | ||||
why we don't let you do it. Note that we **do not support** extension | why we don't let you do it. | ||||
development so you may have mixed luck with this one. | |||||
These approaches are **discouraged**, but also possible: | These approaches are **discouraged**, but also possible: | ||||
- {icon times, color=red} **Fork**: Create an ad-hoc local fork and remove | - {icon times, color=red} **Fork**: Create an ad-hoc local fork and remove | ||||
`final` in your copy of the code. This will make it more difficult for you | `final` in your copy of the code. This will make it more difficult for you | ||||
to upgrade in the future, although it may be the only real way forward | to upgrade in the future, although it may be the only real way forward | ||||
depending on what you're trying to do. | depending on what you're trying to do. | ||||
- {icon times, color=red} **Use Reflection**: You can use | - {icon times, color=red} **Use Reflection**: You can use | ||||
[[ http://php.net/manual/en/book.reflection.php | Reflection ]] to remove | [[ http://php.net/manual/en/book.reflection.php | Reflection ]] to remove | ||||
modifiers at runtime. This is fragile and discouraged, but technically | modifiers at runtime. This is fragile and discouraged, but technically | ||||
possible. | possible. | ||||
- {icon times, color=red} **Remove Modifiers**: Send us a patch removing | - {icon times, color=red} **Remove Modifiers**: Send us a patch removing | ||||
`final` (or turning `protected` or `private` into `public`). We will almost | `final` (or turning `protected` or `private` into `public`). We will almost | ||||
never accept these patches unless there's a very good reason that the | never accept these patches unless there's a very good reason that the | ||||
current behavior is wrong. | current behavior is wrong. | ||||
Next Steps | Next Steps | ||||
========== | ========== | ||||
Continue by: | Continue by: | ||||
- visiting the [[ https://secure.phabricator.com/w/community_resources/ | | - visiting the [[ https://we.phorge.it/w/community_resources/ | | ||||
Community Resources ]] page to find or share extensions and libraries. | Community Resources ]] page to find or share extensions and libraries. |
Content licensed under Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA) unless otherwise noted; code licensed under Apache 2.0 or other open source licenses. · CC BY-SA 4.0 · Apache 2.0
Remove the libphutil reference while we're in here?