1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 09:18:48 +02:00

Update the translations document

Summary:
Fixes T8616. The rules for contributors have come up a few times recently, so update this document to give more complete advice.

Also try to do a better job with "adding new classes" (previously: libphutil libraries blah blah no one cares).

Test Plan: Read documents.

Reviewers: btrahan, joshuaspence

Reviewed By: btrahan, joshuaspence

Subscribers: joshuaspence, epriestley

Maniphest Tasks: T8616

Differential Revision: https://secure.phabricator.com/D13358
This commit is contained in:
epriestley 2015-06-20 05:25:08 -07:00
parent 9921cbc41a
commit 85083c88c1
10 changed files with 609 additions and 195 deletions

View file

@ -2,7 +2,7 @@
"name": "phabcontrib",
"title": "Phabricator Contributor Documentation",
"short": "Phabricator Contributor Docs",
"preface": "Information for Phabricator contributors.",
"preface": "Information for Phabricator contributors and developers.",
"root": "../../../",
"uri.source":
"https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",

View file

@ -2,7 +2,7 @@
"name": "phabdev",
"title": "Phabricator Technical Documentation",
"short": "Phabricator Tech Docs",
"preface": "Technical documentation intended for Phabricator developers.",
"preface": "Technical reference material for Phabricator developers.",
"root": "../../../",
"uri.source":
"https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",

View file

@ -0,0 +1,256 @@
@title Adding New Classes
@group developer
Guide to adding new classes to extend Phabricator.
Overview
========
Phabricator 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
expand the behavior of Phabricator.
IMPORTANT: The upstream does not offer support with extension development.
Fundamentals
============
Phabricator primarily discovers functionality by looking at concrete subclasses
of some base class. For example, Phabricator determines which applications are
available by looking at all of the subclasses of
@{class@phabricator:PhabricatorApplication}. It
discovers available workflows in `arc` by looking at all of the subclasses of
@{class@arcanist:ArcanistWorkflow}. It discovers available locales
by looking at all of the subclasses of @{class@libphutil:PhutilLocale}.
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
integrate the new capabilities or features at runtime.
There are two main ways to add classes:
- **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,
testing and development, or getting started on a larger project.
- **Creating Libraries**: This is a more advanced and powerful way to
organize extension code. This is better for larger or longer-lived
projects, or any code which you plan to distribute.
The next sections walk through these approaches in greater detail.
Extensions Directory
====================
The easiest way to extend Phabricator by adding new classes is to drop them
into the extensions directory, at `phabricator/src/extensions/`.
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
imposes a small performance penalty compared to using a library.
This directory exists in all libphutil libraries, so you can find similar
directories in `arcanist/src/extensions/` and `libphutil/src/extensions/`.
For example, to add a new application, create a file like this one and add it
to `phabricator/src/extensions/`.
```name=phabricator/src/extensions/ExampleApplication.php, lang=php
<?php
final class ExampleApplication extends PhabricatorApplication {
public function getName() {
return pht('Example');
}
}
```
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
any interesting behavior, but this is the basic building block of Phabricator
extensions.
Creating Libraries
==================
A more powerful (but more complicated) way to extend Phabricator is to create
a libphutil library. Libraries can organize a larger amount of code, are easier
to work with and distribute, and have slightly better performance than loose
source files in the extensions directory.
In general, you'll perform these one-time setup steps to create a library:
- Create a new directory.
- Use `arc liberate` to initialize and name the library.
- Configure Phabricator or Arcanist to load the library.
Then, to add new code, you do this:
- Write or update classes.
- Update the library metadata by running `arc liberate` again.
Initializing a Library
======================
To create a new libphutil library, create a directory for it and run
`arc liberate` on the directory. This documentation will use a conventional
directory layout, which is recommended, but you are free to deviate from this.
```
$ mkdir libcustom/
$ cd libcustom/
libcustom/ $ arc liberate src/
```
Now you'll get a prompt like this:
```lang=txt
No library currently exists at that path...
The directory '/some/path/libcustom/src' does not exist.
Do you want to create it? [y/N] y
Creating new libphutil library in '/some/path/libcustom/src'.
Choose a name for the new library.
What do you want to name this library?
```
Choose a library name (in this case, "libcustom" would be appropriate) and it
you should get some details about the library initialization:
```lang=txt
Writing '__phutil_library_init__.php' to
'/some/path/libcustom/src/__phutil_library_init__.php'...
Using library root at 'src'...
Mapping library...
Verifying library...
Finalizing library map...
OKAY Library updated.
```
This will write three files:
- `src/.phutil_module_cache` This is a cache which makes "arc liberate"
faster when you run it to update the library. You can safely remove it at
any time. If you check your library into version control, you can add this
file to ignore rules (like `.gitignore`).
- `src/__phutil_library_init__.php` This records the name of the library and
tells libphutil that a library exists here.
- `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
at runtime and dependencies to be statically managed by `arc liberate`.
Linking with Phabricator
========================
If you aren't using this library with Phabricator (e.g., you are only using it
with Arcanist or are building something else on libphutil) you can skip this
step.
But, if you intend to use this library with Phabricator, you need to define its
dependency on Phabricator by creating a `.arcconfig` file which points at
Phabricator. For example, you might write this file to
`libcustom/.arcconfig`:
```lang=json
{
"load": [
"phabricator/src/"
]
}
```
For details on creating a `.arcconfig`, see
@{article:Arcanist User Guide: Configuring a New Project}. In general, this
tells `arc liberate` that it should look for symbols in Phabricator when
performing static analysis.
NOTE: If Phabricator isn't located next to your custom library, specify a
path which actually points to the `phabricator/` directory.
You do not need to declare dependencies on `arcanist` or `libphutil`,
since `arc liberate` automatically loads them.
Finally, edit your Phabricator config to tell it to load your library at
runtime, by adding it to `load-libraries`:
```lang=json
...
'load-libraries' => array(
'libcustom' => 'libcustom/src/',
),
...
```
Now, Phabricator will be able to load classes from your custom library.
Writing Classes
===============
To actually write classes, create a new module and put code in it:
libcustom/ $ mkdir src/example/
libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
Now, run `arc liberate` to regenerate the static resource map:
libcustom/ $ arc liberate src/
This will automatically regenerate the static map of the library.
What You Can Extend And Invoke
==============================
libphutil, Arcanist and Phabricator are strict about extensibility of classes
and visibility of methods and properties. Most classes are marked `final`, and
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
invoke, so your code will keep working as the upstream changes.
IMPORTANT: We'll still break APIs frequently. The upstream does not support
extension development, and none of these APIs are stable.
When developing libraries to work with libphutil, Arcanist and Phabricator, you
should respect method and property visibility.
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:
- {icon check, color=green} **Use Composition**: If possible, use composition
rather than extension to build your feature.
- {icon check, color=green} **Find Another Approach**: Check the
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
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
why we don't let you do it. Note that we **do not support** extension
development so you may have mixed luck with this one.
These approaches are **discouraged**, but also possible:
- {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
to upgrade in the future, although it may be the only real way forward
depending on what you're trying to do.
- {icon times, color=red} **Use Reflection**: You can use
[[ http://php.net/manual/en/book.reflection.php | Reflection ]] to remove
modifiers at runtime. This is fragile and discouraged, but technically
possible.
- {icon times, color=red} **Remove Modifiers**: Send us a patch removing
`final` (or turning `protected` or `private` into `public`). We will almost
never accept these patches unless there's a very good reason that the
current behavior is wrong.
Next Steps
==========
Continue by:
- visiting the [[ https://secure.phabricator.com/w/community_resources/ |
Community Resources ]] page to find or share extensions and libraries.

View file

@ -9,57 +9,370 @@ Overview
Phabricator partially supports internationalization, but many of the tools
are missing or in a prototype state.
This document very briefly summarizes some of what exists today.
This document describes what tools exist today, how to add new translations,
and how to use the translation tools to make a codebase translatable.
Adding a New Locale
===================
To add a new locale, subclass @{class:PhutilLocale}. This allows you to
introduce a new locale, like "German" or "Klingon".
Once you've created a locale, applications can add translations for that
locale.
For instructions on adding new classes, see @{article:Adding New Classes}.
Adding Translations to Locale
=============================
To translate strings, subclass @{class:PhutilTranslation}. Translations need
to belong to a locale: the locale defines an available language, and each
translation subclass provides strings for it.
Translations are separated from locales so that third-party applications can
provide translations into different locales without needing to define those
locales themselves.
For instructions on adding new classes, see @{article:Adding New Classes}.
Writing Translatable Code
========
=========================
Strings are marked for translation with @{function@libphutil:pht}.
Adding a New Locale
=========
The `pht()` function takes a string (and possibly some parameters) and returns
the translated version of that string in the current viewer's locale, if a
translation is available.
To add a new locale, subclass @{class:PhutilLocale}.
If text strings will ultimately be read by humans, they should essentially
always be wrapped in `pht()`. For example:
Translating Strings
========
```lang=php
$dialog->appendParagraph(pht('This is an example.'));
```
This allows the code to return the correct Spanish or German or Russian
version of the text, if the viewer is using Phabricator in one of those
languages and a translation is available.
Using `pht()` properly so that strings are translatable can be tricky. Briefly,
the major rules are:
- Only pass static strings as the first parameter to `pht()`.
- Use parameters to create strings containing user names, object names, etc.
- Translate full sentences, not sentence fragments.
- Let the translation framework handle plural rules.
- Use @{class@libphutil:PhutilNumber} for numbers.
- Let the translation framework handle subject gender rules.
- Translate all human-readable text, even exceptions and error messages.
See the next few sections for details on these rules.
Use Static Strings
==================
The first parameter to `pht()` must always be a static string. Broadly, this
means it should not contain variables or function or method calls (it's OK to
split it across multiple lines and concatenate the parts together).
These are good:
```lang=php
pht('The night is dark.');
pht(
'Two roads diverged in a yellow wood, '.
'and sorry I could not travel both '.
'and be one traveler, long I stood.');
```
These won't work (they might appear to work, but are wrong):
```lang=php, counterexample
pht(some_function());
pht('The duck says, '.$quack);
pht($string);
```
The first argument must be a static string so it can be extracted by static
analysis tools and dumped in a big file for translators. If it contains
functions or variables, it can't be extracted, so translators won't be able to
translate it.
Lint will warn you about problems with use of static strings in calls to
`pht()`.
Parameters
==========
You can provide parameters to a translation string by using `sprintf()`-style
patterns in the input string. For example:
```lang=php
pht('%s earned an award.', $actor);
pht('%s closed %s.', $actor, $task);
```
This is primarily appropriate for usernames, object names, counts, and
untranslatable strings like URIs or instructions to run commands from the CLI.
Parameters normally should not be used to combine two pieces of translated
text: see the next section for guidance.
Sentence Fragments
==================
You should almost always pass the largest block of text to `pht()` that you
can. Particularly, it's important to pass complete sentences, not try to build
a translation by stringing together sentence fragments.
There are several reasons for this:
- It gives translators more context, so they can be more confident they are
producing a satisfying, natural-sounding translation which will make sense
and sound good to native speakers.
- In some languages, one fragment may need to translate differently depending
on what the other fragment says.
- In some languages, the most natural-sounding translation may change the
order of words in the sentence.
For example, suppose we want to translate these sentence to give the user some
instructions about how to use an interface:
> Turn the switch to the right.
> Turn the switch to the left.
> Turn the dial to the right.
> Turn the dial to the left.
Maybe we have a function like this:
```
function get_string($is_switch, $is_right) {
// ...
}
```
One way to write the function body would be like this:
```lang=php, counterexample
$what = $is_switch ? pht('switch') : pht('dial');
$dir = $is_right ? pht('right') : pht('left');
return pht('Turn the ').$what.pht(' to the ').$dir.pht('.');
```
This will work fine in English, but won't work well in other languages.
One problem with doing this is handling gendered nouns. Languages like Spanish
have gendered nouns, where some nouns are "masculine" and others are
"feminine". The gender of a noun affects which article (in English, the word
"the" is an article) should be used with it.
In English, we say "**the** knob" and "**the** switch", but a Spanish speaker
would say "**la** perilla" and "**el** interruptor", because the noun for
"knob" in Spanish is feminine (so it is used with the article "la") while the
noun for "switch" is masculine (so it is used with the article "el").
A Spanish speaker can not translate the string "Turn the" correctly without
knowing which gender the noun has. Spanish has //two// translations for this
string ("Gira el", "Gira la"), and the form depends on which noun is being
used.
Another problem is that this reduces flexibility. Translating fragments like
this locks translators into a specific word order, when rearranging the words
might make the sentence sound much more natural to a native speaker.
For example, if the string read "The knob, to the right, turn it.", it
would technically be English and most English readers would understand the
meaning, but no native English speaker would speak or write like this.
However, some languages have different subject-verb order rules or
colloquisalisms, and a word order which transliterates like this may sound more
natural to a native speaker. By translating fragments instead of complete
sentences, you lock translators into English word order.
Finally, the last fragment is just a period. If a translator is presented with
this string in an interface without much context, they have no hope of guessing
how it is used in the software (it could be an end-of-sentence marker, or a
decimal point, or a date separator, or a currency separator, all of which have
very different translations in many locales). It will also conflict with all
other translations of the same string in the codebase, so even if they are
given context they can't translate it without technical problems.
To avoid these issues, provide complete sentences for translation. This almost
always takes the form of writing out alternatives in full. This is a good way
to implement the example function:
```lang=php
if ($is_switch) {
if ($is_right) {
return pht('Turn the switch to the right.');
} else {
return pht('Turn the switch to the left.');
}
} else {
if ($is_right) {
return pht('Turn the dial to the right.');
} else {
return pht('Turn the dial to the left.');
}
}
```
Although this is more verbose, translators can now get genders correct,
rearrange word order, and have far more context when translating. This enables
better, natural-sounding translations which are more satisfying to native
speakers.
To translate strings, subclass @{class:PhutilTranslation}.
Singular and Plural
========
===================
Different languages have various rules for using singular and plural. All you
need to do is to call @{function@libphutil:pht} with a text that is suitable for
both forms. Example:
Different languages have various rules for plural nouns.
pht('%d beer(s)', $count);
In English there are usually two plural noun forms: for one thing, and any
other number of things. For example, we say that one chair is a "chair" and any
other number of chairs are "chairs": "0 chairs", "1 chair", "2 chairs", etc.
Translators will translate this text for all different forms the language uses:
In other languages, there are different (and, in some cases, more) plural
forms. For example, in Czech, there are separate forms for "one", "several",
and "many".
// English translation
array('%d beer', '%d beers');
Because plural noun rules depend on the language, you should not write code
which hard-codes English rules. For example, this won't translate well:
// Czech translation
array('%d pivo', '%d piva', '%d piv');
```lang=php, counterexample
if ($count == 1) {
return pht('This will take an hour.');
} else {
return pht('This will take hours.');
}
```
The ugly identifier passed to @{function@libphutil:pht} will remain in the text
only if the translation doesn't exist.
This code is hard-coding the English rule for plural nouns. In languages like
Czech, the correct word for "hours" may be different if the count is 2 or 15,
but a translator won't be able to provide the correct translation if the string
is written like this.
Instead, pass a generic string to the translation engine which //includes// the
number of objects, and let it handle plural nouns. This is the correct way to
write the translation:
```lang=php
return pht('This will take %s hour(s).', new PhutilNumber($count));
```
If you now load the web UI, you'll see "hour(s)" literally in the UI. To fix
this so the translation sounds better in English, provide translations for this
string in the @{class@phabricator:PhabricatorUSEnglishTranslation} file:
```lang=php
'This will take %s hour(s).' => array(
'This will take an hour.',
'This will take hours.',
),
```
The string will then sound natural in English, but non-English translators will
also be able to produce a natural translation.
Note that the translations don't actually include the number in this case. The
number is being passed from the code, but that just lets the translation engine
get the rules right: the number does not need to appear in the final
translations shown to the user.
Using PhutilNumber
==================
When translating numbers, you should almost always use `%s` and wrap the count
or number in `new PhutilNumber($count)`. For example:
```lang=php
pht('You have %s experience point(s).', new PhutilNumber($xp));
```
This will let the translation engine handle plural noun rules correctly, and
also format large numbers correctly in a locale-aware way with proper unit and
decimal separators (for example, `1000000` may be printed as "1,000,000",
with commas for readability).
The exception to this rule is IDs which should not be written with unit
separators. For example, this is correct for an object ID:
```lang=php
pht('This diff has ID %d.', $diff->getID());
```
Male and Female
========
===============
Different languages use different words for talking about males, females and
unknown genders. Callsites have to call @{function@libphutil:pht} passing
@{class:PhabricatorUser} (or other implementation of
@{interface@libphutil:PhutilPerson}) if talking about the user. Example:
Different languages also use different words for talking about subjects who are
male, female or have an unknown gender. In English this is mostly just
pronouns (like "he" and "she") but there are more complex rules in other
languages, and languages like Czech also require verb agreement.
pht('%s wrote', $actor);
When a parameter refers to a gendered person, pass an object which implements
@{interface@libphutil:PhutilPerson} to `pht()` so translators can provide
gendered translation variants.
Translators will create this translations:
```lang=php
pht('%s wrote', $actor);
```
// English translation
'%s wrote';
Translators will create these translations:
// Czech translation
array('%s napsal', '%s napsala');
```lang=php
// English translation
'%s wrote';
// Czech translation
array('%s napsal', '%s napsala');
```
(You usually don't need to worry very much about this rule, it is difficult to
get wrong in standard code.)
Exceptions and Errors
=====================
You should translate all human-readable text, even exceptions and error
messages. This is primarily a rule of convenience which is straightforward
and easy to follow, not a technical rule.
Some exceptions and error messages don't //technically// need to be translated,
as they will never be shown to a user, but many exceptions and error messages
are (or will become) user-facing on some way. When writing a message, there is
often no clear and objective way to determine which type of message you are
writing. Rather than try to distinguish which are which, we simply translate
all human-readable text. This rule is unambiguous and easy to follow.
In cases where similar error or exception text is often repeated, it is
probably appropriate to define an exception for that category of error rather
than write the text out repeatedly, anyway. Two examples are
@{class@libphutil:PhutilInvalidStateException} and
@{class@libphutil:PhutilMethodNotImplementedException}, which mostly exist to
produce a consistent message about a common error state in a convenient way.
There are a handful of error strings in the codebase which may be used before
the translation framework is loaded, or may be used during handling other
errors, possibly rasised from within the translation framework. This handful
of special cases are left untranslated to prevent fatals and cycles in the
error handler.
Next Steps
==========
Continue by:
- adding a new locale or translation file with @{article:Adding New Classes}.

View file

@ -207,5 +207,5 @@ integrations, see the base class for your application and
Continue by:
- learning more about extending Phabricator with custom code in
@{article:libphutil Libraries User Guide};
@{article@contributor:Adding New Classes};
- or returning to the @{article: Configuration Guide}.

View file

@ -109,7 +109,7 @@ This daemon will daemonize and run normally.
just those started with `phd start`. If you're writing a restart script,
have it launch any custom daemons explicitly after `phd restart`.
- You can write your own daemons and manage them with `phd` by extending
@{class:PhabricatorDaemon}. See @{article:libphutil Libraries User Guide}.
@{class:PhabricatorDaemon}. See {article@contributor:Adding New Classes}.
- See @{article:Diffusion User Guide} for details about tuning the repository
daemon.
@ -137,4 +137,4 @@ Continue by:
- learning about the repository daemon with @{article:Diffusion User Guide};
or
- writing your own daemons with @{article:libphutil Libraries User Guide}.
- writing your own daemons with {article@contributor:Adding New Classes}.

View file

@ -38,7 +38,7 @@ make this work, you need to do three things:
If you haven't created a library for the class to live in yet, you need to do
that first. Follow the instructions in
@{article:libphutil Libraries User Guide}, then make the library loadable by
@{article@contributor:Adding New Classes}, then make the library loadable by
adding it to your `.arcconfig` like this:
{

View file

@ -47,7 +47,7 @@ Other options include:
- **load**: list of additional Phutil libraries to load at startup.
See below for details about path resolution, or see
@{article:libphutil Libraries User Guide} for a general introduction to
@{article@contributor:Adding New Classes} for a general introduction to
libphutil libraries.
- **https.cabundle**: specifies the path to an alternate certificate bundle
for use when making HTTPS connections.

View file

@ -21,7 +21,7 @@ To install event listeners in Phabricator, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions,
see @{article:libphutil Libraries User Guide}.
see @{article@contributor:Adding New Classes}.
- Configure Phabricator to load the library by adding it to `load-libraries`
in the Phabricator config.
- Configure Phabricator to install the event listener by adding the class
@ -38,7 +38,7 @@ To install event listeners in Arcanist, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions,
see @{article:libphutil Libraries User Guide}.
see @{article@contributor:Adding New Classes}.
- Configure Phabricator to load the library by adding it to `load`
in the Arcanist config (e.g., `.arcconfig`, or user/global config).
- Configure Arcanist to install the event listener by adding the class

View file

@ -1,155 +0,0 @@
@title libphutil Libraries User Guide
@group userguide
Guide to creating and managing libphutil libraries.
= Overview =
libphutil includes a library system which organizes PHP classes and functions
into modules. Some extensions and customizations of Arcanist and Phabricator
require you to make code available to Phabricator by providing it in a libphutil
library.
For example, if you want to store files in some kind of custom storage engine,
you need to write a class which can interact with that engine and then tell
Phabricator to load it.
In general, you perform these one-time setup steps:
- Create a new directory.
- Use `arc liberate` to initialize and name the library.
- Add a dependency on Phabricator if necessary.
- Add the library to your Phabricator config or `.arcconfig` so it will be
loaded at runtime.
Then, to add new code, you do this:
- Write or update classes.
- Update the library metadata by running `arc liberate` again.
= Creating a New Library =
To **create a new libphutil library**:
$ mkdir libcustom/
$ cd libcustom/
libcustom/ $ arc liberate src/
Now you'll get a prompt like this:
lang=txt
No library currently exists at that path...
The directory '/some/path/libcustom/src' does not exist.
Do you want to create it? [y/N] y
Creating new libphutil library in '/some/path/libcustom/src'.
Choose a name for the new library.
What do you want to name this library?
Choose a library name (in this case, "libcustom" would be appropriate) and it
you should get some details about the library initialization:
lang=txt
Writing '__phutil_library_init__.php' to
'/some/path/libcustom/src/__phutil_library_init__.php'...
Using library root at 'src'...
Mapping library...
Verifying library...
Finalizing library map...
OKAY Library updated.
This will write three files:
- `src/.phutil_module_cache` This is a cache which makes "arc liberate"
faster when you run it to update the library. You can safely remove it at
any time. If you check your library into version control, you can add this
file to ignore rules (like .gitignore).
- `src/__phutil_library_init__.php` This records the name of the library and
tells libphutil that a library exists here.
- `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
at runtime and dependencies to be statically managed by "arc liberate".
= Linking with Phabricator =
If you aren't using this library with Phabricator (e.g., you are only using it
with Arcanist or are building something else on libphutil) you can skip this
step.
But, if you intend to use this library with Phabricator, you need to define its
dependency on Phabricator by creating a `.arcconfig` file which points at
Phabricator. For example, you might write this file to
`libcustom/.arcconfig`:
{
"load": [
"phabricator/src/"
]
}
For details on creating a `.arcconfig`, see
@{article:Arcanist User Guide: Configuring a New Project}. In general, this
tells `arc liberate` that it should look for symbols in Phabricator when
performing static analysis.
NOTE: If Phabricator isn't located next to your custom library, specify a
path which actually points to the `phabricator/` directory.
You do not need to declare dependencies on `arcanist` or `libphutil`,
since `arc liberate` automatically loads them.
Finally, edit your Phabricator config to tell it to load your library at
runtime, by adding it to `load-libraries`:
...
'load-libraries' => array(
'libcustom' => 'libcustom/src/',
),
...
Now, Phabricator will be able to load classes from your custom library.
= Writing Classes =
To actually write classes, create a new module and put code in it:
libcustom/ $ mkdir src/example/
libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
Now, run `arc liberate` to regenerate the static resource map:
libcustom/ $ arc liberate src/
This will automatically regenerate the static map of the library.
= What You Can Extend And Invoke =
libphutil, Arcanist and Phabricator are strict about extensibility of classes
and visibility of methods and properties. Most classes are marked `final`, and
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
invoke, so your code will keep working as the upstream changes.
When developing libraries to work with libphutil, Arcanist and Phabricator, you
should respect method and property visibility and extend only classes marked
`@stable`. They are rendered with a large callout in the documentation (for
example: @{class@libphutil:AbstractDirectedGraph}). These classes are external
interfaces intended for extension.
If you want to extend a class but it is not marked `@stable`, here are some
approaches you can take:
- Good: If possible, use composition rather than extension to build your
feature.
- Good: Check the documentation for a better way to accomplish what you're
trying to do.
- Good: Let us know what your 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 why we don't let you do it.
- Discouraged: Send us a patch removing "final" (or turning "protected" or
"private" into "public"). We generally will not accept these patches, unless
there's a good reason that the current behavior is wrong.
- Discouraged: Create an ad-hoc local fork and remove "final" in your copy of
the code. This will make it more difficult for you to upgrade in the future.
- Discouraged: Use Reflection to violate visibility keywords.