Page MenuHomePhabricator

D13358.id32355.diff
No OneTemporary

D13358.id32355.diff

diff --git a/src/docs/book/contributor.book b/src/docs/book/contributor.book
--- a/src/docs/book/contributor.book
+++ b/src/docs/book/contributor.book
@@ -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",
diff --git a/src/docs/book/phabricator.book b/src/docs/book/phabricator.book
--- a/src/docs/book/phabricator.book
+++ b/src/docs/book/phabricator.book
@@ -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",
diff --git a/src/docs/contributor/adding_new_classes.diviner b/src/docs/contributor/adding_new_classes.diviner
new file mode 100644
--- /dev/null
+++ b/src/docs/contributor/adding_new_classes.diviner
@@ -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.
diff --git a/src/docs/contributor/internationalization.diviner b/src/docs/contributor/internationalization.diviner
--- a/src/docs/contributor/internationalization.diviner
+++ b/src/docs/contributor/internationalization.diviner
@@ -9,57 +9,370 @@
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 plural nouns.
+
+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.
+
+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".
+
+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:
-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:
+```lang=php, counterexample
+if ($count == 1) {
+ return pht('This will take an hour.');
+} else {
+ return pht('This will take hours.');
+}
+```
- pht('%d beer(s)', $count);
+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.
-Translators will translate this text for all different forms the language uses:
+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:
- // English translation
- array('%d beer', '%d beers');
+```lang=php
+return pht('This will take %s hour(s).', new PhutilNumber($count));
+```
- // Czech translation
- array('%d pivo', '%d piva', '%d piv');
+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:
-The ugly identifier passed to @{function@libphutil:pht} will remain in the text
-only if the translation doesn't exist.
+```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 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.
+
+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.
+
+```lang=php
+pht('%s wrote', $actor);
+```
+
+Translators will create these translations:
+
+```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.
-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:
+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.
- pht('%s wrote', $actor);
-Translators will create this translations:
+Next Steps
+==========
- // English translation
- '%s wrote';
+Continue by:
- // Czech translation
- array('%s napsal', '%s napsala');
+ - adding a new locale or translation file with @{article:Adding New Classes}.
diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner
--- a/src/docs/user/configuration/custom_fields.diviner
+++ b/src/docs/user/configuration/custom_fields.diviner
@@ -207,5 +207,5 @@
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}.
diff --git a/src/docs/user/configuration/managing_daemons.diviner b/src/docs/user/configuration/managing_daemons.diviner
--- a/src/docs/user/configuration/managing_daemons.diviner
+++ b/src/docs/user/configuration/managing_daemons.diviner
@@ -109,7 +109,7 @@
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 @@
- 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}.
diff --git a/src/docs/user/userguide/arcanist_lint_unit.diviner b/src/docs/user/userguide/arcanist_lint_unit.diviner
--- a/src/docs/user/userguide/arcanist_lint_unit.diviner
+++ b/src/docs/user/userguide/arcanist_lint_unit.diviner
@@ -38,7 +38,7 @@
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:
{
diff --git a/src/docs/user/userguide/arcanist_new_project.diviner b/src/docs/user/userguide/arcanist_new_project.diviner
--- a/src/docs/user/userguide/arcanist_new_project.diviner
+++ b/src/docs/user/userguide/arcanist_new_project.diviner
@@ -47,7 +47,7 @@
- **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.
diff --git a/src/docs/user/userguide/events.diviner b/src/docs/user/userguide/events.diviner
--- a/src/docs/user/userguide/events.diviner
+++ b/src/docs/user/userguide/events.diviner
@@ -21,7 +21,7 @@
- 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 @@
- 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
diff --git a/src/docs/user/userguide/libraries.diviner b/src/docs/user/userguide/libraries.diviner
deleted file mode 100644
--- a/src/docs/user/userguide/libraries.diviner
+++ /dev/null
@@ -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.

File Metadata

Mime Type
text/plain
Expires
Fri, Jun 21, 4:50 AM (1 w, 3 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/2o/ax/icx4um2wjwh2dwws
Default Alt Text
D13358.id32355.diff (35 KB)

Event Timeline