diff --git a/src/docs/book/contributor.book b/src/docs/book/contributor.book index 10db63c011..1d0386251f 100644 --- a/src/docs/book/contributor.book +++ b/src/docs/book/contributor.book @@ -1,36 +1,36 @@ { - "name": "phabcontrib", - "title": "Phabricator Contributor Documentation", - "short": "Phabricator Contributor Docs", - "preface": "Information for Phabricator contributors and developers.", + "name": "contrib", + "title": "Phorge Contributor Documentation", + "short": "Contributor Docs", + "preface": "Information for Phorge contributors and developers.", "root": "../../../", "uri.source": - "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l", + "https://we.phorge.it/diffusion/P/browse/master/%f$%l", "rules": { "(\\.diviner$)": "DivinerArticleAtomizer" }, "exclude": [ "(^externals/)", "(^resources/)", "(^scripts/)", "(^src/docs/flavor/)", "(^src/docs/tech/)", "(^src/docs/user/)", "(^support/)", "(^webroot/rsrc/externals/)" ], "groups": { "contrib": { "name": "Contributor Overview" }, "detail": { "name": "Contributing in Detail" }, "developer": { "name": "Developer Guides" }, "standards": { "name": "Coding Standards" } } } diff --git a/src/docs/book/flavor.book b/src/docs/book/flavor.book index 978244f19d..4404e94bf7 100644 --- a/src/docs/book/flavor.book +++ b/src/docs/book/flavor.book @@ -1,42 +1,39 @@ { - "name": "phabflavor", - "title": "Phabricator Flavor Text", + "name": "flavor", + "title": "Phorge Flavor Text", "short": "Flavor Text", - "preface": "Recommendations, lore, and dark rituals.", + "preface": "A collection of short articles which pertain to software development in general, not necessarily to Phorge specifically.", "root": "../../../", "uri.source": - "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l", + "https://we.phorge.it/diffusion/P/browse/master/%f$%l", "rules": { "(\\.diviner$)": "DivinerArticleAtomizer" }, "exclude": [ "(^externals/)", "(^resources/)", "(^scripts/)", "(^src/docs/contributor/)", "(^src/docs/tech/)", "(^src/docs/user/)", "(^support/)", "(^webroot/rsrc/externals/)" ], "groups": { - "overview": { - "name": "Overview" - }, "javascript": { "name": "Javascript" }, "lore": { - "name": "Phabricator Lore" + "name": "Phorge Lore" }, "php": { "name": "PHP" }, "review": { "name": "Revision Control and Code Review" }, "sundry": { "name": "Sundries" } } } diff --git a/src/docs/book/phabricator.book b/src/docs/book/phorge.book similarity index 97% rename from src/docs/book/phabricator.book rename to src/docs/book/phorge.book index 2beef23023..d423482813 100644 --- a/src/docs/book/phabricator.book +++ b/src/docs/book/phorge.book @@ -1,343 +1,343 @@ { - "name": "phabdev", - "title": "Phabricator Technical Documentation", - "short": "Phabricator Tech Docs", - "preface": "Technical reference material for Phabricator developers.", + "name": "dev", + "title": "Phorge Technical Documentation", + "short": "Tech Docs", + "preface": "Technical reference material for Phorge developers.", "root": "../../../", "uri.source": - "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l", + "https://we.phorge.it/diffusion/P/browse/master/%f$%l", "rules": { "(\\.diviner$)": "DivinerArticleAtomizer", "(\\.php$)": "DivinerPHPAtomizer" }, "exclude": [ "(^externals/)", "(^resources/)", "(^scripts/)", "(^src/docs/contributor/)", "(^src/docs/flavor/)", "(^src/docs/user/)", "(^support/)", "(^webroot/rsrc/externals/)" ], "groups": { "aphront": { "name": "Aphront", "include": "(^src/aphront/)" }, "almanac": { "name": "Almanac", "include": "(^src/applications/almanac/)" }, "aphlict": { "name": "Aphlict", "include": "(^src/applications/aphlict/)" }, "arcanist": { "name": "Arcanist Integration", "include": "(^src/applications/arcanist/)" }, "auth": { "name": "Auth", "include": "(^src/applications/auth/)" }, "baseapp": { "name": "Application Basics", "include": "(^src/applications/base/)" }, "cache": { "name": "Cache", "include": "(^src/applications/cache/)" }, "calendar": { "name": "Calendar", "include": "(^src/applications/calendar/)" }, "celerity": { "name": "Celerity", "include": "(^src/applications/celerity/)" }, "chatlog": { "name": "Chatlog", "include": "(^src/applications/chatlog/)" }, "conduit": { "name": "Conduit", "include": "(^src/applications/conduit/)" }, "config": { "name": "Config", "include": "(^src/applications/config/)" }, "conpherence": { "name": "Conpherence", "include": "(^src/applications/conpherence/)" }, "console": { "name": "Console", "include": "(^src/applications/console/)" }, "countdown": { "name": "Countdown", "include": "(^src/applications/countdown/)" }, "customfield": { "name": "Custom Fields", "include": "(^src/infrastructure/customfield/)" }, "daemon": { "name": "Daemons", "include": [ "(^src/applications/daemon/)", "(^src/infrastructure/daemon/)" ] }, "dashboard": { "name": "Dashboard", "include": "(^src/applications/dashboard/)" }, "differential": { "name": "Differential", "include": "(^src/applications/differential/)" }, "diffusion": { "name": "Diffusion", "include": "(^src/applications/diffusion/)" }, "diviner": { "name": "Diviner", "include": "(^src/applications/diviner/)" }, "doorkeeper": { "name": "Doorkeeper", "include": "(^src/applications/doorkeeper/)" }, "draft": { "name": "Draft", "include": "(^src/applications/draft/)" }, "drydock": { "name": "Drydock", "include": "(^src/applications/drydock/)" }, "edges": { "name": "Edges", "include": "(^src/infrastructure/edges/)" }, "events": { "name": "Events", "include": "(^src/infrastructure/events/)" }, "fact": { "name": "Fact", "include": "(^src/applications/fact/)" }, "feed": { "name": "Feed", "include": "(^src/applications/feed/)" }, "files": { "name": "Files", "include": "(^src/applications/files/)" }, "flag": { "name": "Flags", "include": "(^src/applications/flag/)" }, "fund": { "name": "Fund", "include": "(^src/applications/fund/)" }, "harbormaster": { "name": "Harbormaster", "include": "(^src/applications/harbormaster/)" }, "help": { "name": "Help", "include": "(^src/applications/help/)" }, "herald": { "name": "Herald", "include": "(^src/applications/herald/)" }, "home": { "name": "Home", "include": "(^src/applications/home/)" }, "legalpad": { "name": "Legalpad", "include": "(^src/applications/legalpad/)" }, "lipsum": { "name": "Lipsum", "include": "(^src/applications/lipsum/)" }, "macro": { "name": "Macro", "include": "(^src/applications/macro/)" }, "maniphest": { "name": "Maniphest", "include": "(^src/applications/maniphest/)" }, "meta": { "name": "Applications", "include": "(^src/applications/meta/)" }, "metamta": { "name": "MetaMTA", "include": "(^src/applications/metamta/)" }, "multimeter": { "name": "Multimeter", "include": "(^src/applications/multimeter/)" }, "notification": { "name": "Notifications", "include": "(^src/applications/notification/)" }, "nuance": { "name": "Nuance", "include": "(^src/applications/nuance/)" }, "oauthserver": { "name": "OAuth Server", "include": "(^src/applications/oauthserver/)" }, "owners": { "name": "Owners", "include": "(^src/applications/owners/)" }, "passphrase": { "name": "Passphrase", "include": "(^src/applications/passphrase/)" }, "paste": { "name": "Paste", "include": "(^src/applications/paste/)" }, "people": { "name": "People", "include": "(^src/applications/people/)" }, "phame": { "name": "Phame", "include": "(^src/applications/phame/)" }, "phid": { "name": "PHIDs", "include": "(^src/applications/phid/)" }, "phlux": { "name": "Phlux", "include": "(^src/applications/phlux/)" }, "pholio": { "name": "Pholio", "include": "(^src/applications/pholio/)" }, "phortune": { "name": "Phortune", "include": "(^src/applications/phortune/)" }, "phpast": { "name": "PHPAST", "include": "(^src/applications/phpast/)" }, "phragment": { "name": "Phragment", "include": "(^src/applications/phragment/)" }, "phrequent": { "name": "Phrequent", "include": "(^src/applications/phrequent/)" }, "phriction": { "name": "Phriction", "include": "(^src/applications/phriction/)" }, "phui": { "name": "PHUI", "include": "(^src/view/phui/)" }, "policy": { "name": "Policy", "include": "(^src/applications/policy/)" }, "ponder": { "name": "Ponder", "include": "(^src/applications/ponder/)" }, "project": { "name": "Projects", "include": "(^src/applications/project/)" }, "releeph": { "name": "Releeph", "include": "(^src/applications/releeph/)" }, "remarkup": { "name": "Remarkup", "include": [ "(^src/applications/remarkup/)", "(^src/infrastructure/markup/)" ] }, "repository": { "name": "Repositories", "include": "(^src/applications/repository/)" }, "search": { "name": "Search", "include": "(^src/applications/search/)" }, "settings": { "name": "Settings", "include": "(^src/applications/settings/)" }, "slowvote": { "name": "Slowvote", "include": "(^src/applications/slowvote/)" }, "spaces": { "name": "Spaces", "include": "(^src/applications/spaces/)" }, "storage": { "name": "Storage", "include": "(^src/infrastructure/storage/)" }, "subscriptions": { "name": "Subscriptions", "include": "(^src/applications/subscriptions/)" }, "support": { "name": "Support", "include": "(^src/applications/support/)" }, "system": { "name": "System", "include": "(^src/applications/system/)" }, "tokens": { "name": "Tokens", "include": "(^src/applications/tokens/)" }, "transactions": { "name": "Transactions", "include": "(^src/applications/transactions/)" }, "typeahead": { "name": "Typeahead", "include": "(^src/applications/typeahead/)" }, "uiexample": { "name": "UI Examples", "include": "(^src/applications/uiexample/)" }, "xhprof": { "name": "XHProf", "include": "(^src/applications/xhprof/)" } } } diff --git a/src/docs/book/user.book b/src/docs/book/user.book index fb2dccc578..449acd4ffc 100644 --- a/src/docs/book/user.book +++ b/src/docs/book/user.book @@ -1,45 +1,42 @@ { - "name": "phabricator", - "title": "Phabricator User Documentation", - "short": "Phabricator User Docs", - "preface": "Instructions for installing, configuring, and using Phabricator.", + "name": "phorge", + "title": "Phorge User Documentation", + "short": "User Docs", + "preface": "Instructions for installing, configuring, and using Phorge.", "root": "../../../", "uri.source": - "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l", + "https://we.phorge.it/diffusion/P/browse/master/%f$%l", "rules": { "(\\.diviner$)": "DivinerArticleAtomizer" }, "exclude": [ "(^externals/)", "(^resources/)", "(^scripts/)", "(^src/docs/contributor/)", "(^src/docs/flavor/)", "(^src/docs/tech/)", "(^support/)", "(^webroot/rsrc/externals/)" ], "groups": { "intro": { "name": "Introduction" }, "config": { "name": "Configuration" }, "userguide": { "name": "Application User Guides" }, "conduit": { "name": "API Documentation" }, "cluster": { "name": "Cluster Configuration" }, "fieldmanual": { "name": "Field Manuals" - }, - "cellar": { - "name": "Musty Cellar" } } } diff --git a/src/docs/contributor/adding_new_classes.diviner b/src/docs/contributor/adding_new_classes.diviner index ea932eba5c..64ab228ea3 100644 --- a/src/docs/contributor/adding_new_classes.diviner +++ b/src/docs/contributor/adding_new_classes.diviner @@ -1,256 +1,255 @@ @title Adding New Classes @group developer -Guide to adding new classes to extend Phabricator. +Guide to adding new classes to extend Phorge. 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 -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 ============ -Phabricator primarily discovers functionality by looking at concrete subclasses -of some base class. For example, Phabricator determines which applications are +Phorge primarily discovers functionality by looking at concrete subclasses +of some base class. For example, Phorge 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@arcanist: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 +new classes with no other work. Phorge 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/`. +The easiest way to extend Phorge by adding new classes is to drop them +into the extensions directory, at `pohrge/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 +features, or get started on a larger project. Extending Phorge like this 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/`. 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 array( '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 =============== 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 ============================== -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 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. +IMPORTANT: We'll still break APIs frequently. The upstream offers limited + 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. 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 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. + why we don't let you do it. 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/ | + - visiting the [[ https://we.phorge.it/w/community_resources/ | Community Resources ]] page to find or share extensions and libraries. diff --git a/src/docs/contributor/adding_new_css_and_js.diviner b/src/docs/contributor/adding_new_css_and_js.diviner index 00a3808fba..16bf5ebe03 100644 --- a/src/docs/contributor/adding_new_css_and_js.diviner +++ b/src/docs/contributor/adding_new_css_and_js.diviner @@ -1,96 +1,94 @@ @title Adding New CSS and JS @group developer -Explains how to add new CSS and JS files to Phabricator. +Explains how to add new CSS and JS files to Phorge. = Overview = -Phabricator uses a system called **Celerity** to manage static resources. If you -are a current or former Facebook employee, Celerity is based on the Haste system -used at Facebook and generally behaves similarly. +Phorge uses a system called **Celerity** to manage static resources. -This document is intended for Phabricator developers and contributors. This +(NOTE) This document is intended for Phorge developers and contributors. This process will not work correctly for third-party code, plugins, or extensions. = Adding a New File = To add a new CSS or JS file, create it in an appropriate location in -`webroot/rsrc/css/` or `webroot/rsrc/js/` inside your `phabricator/` +`webroot/rsrc/css/` or `webroot/rsrc/js/` inside your `phorge/` directory. Each file must `@provides` itself as a component, declared in a header comment: LANG=css /** * @provides duck-styles-css */ .duck-header { font-size: 9001px; } Note that this comment must be a Javadoc-style comment, not just any comment. If your component depends on other components (which is common in JS but rare and inadvisable in CSS), declare then with `@requires`: LANG=js /** * @requires javelin-stratcom * @provides duck */ /** * Put class documentation here, NOT in the header block. */ JX.install('Duck', { ... }); Then rebuild the Celerity map (see the next section). = Changing an Existing File = When you add, move or remove a file, or change the contents of existing JS or CSS file, you should rebuild the Celerity map: - phabricator/ $ ./bin/celerity map + phorge/ $ ./bin/celerity map If you've only changed file content things will generally work even if you don't, but they might start not working as well in the future if you skip this step. The generated file `resources/celerity/map.php` causes merge conflicts quite often. They can be resolved by running the Celerity mapper. You can automate this process by running: - phabricator/ $ ./scripts/celerity/install_merge.sh + phorge/ $ ./scripts/celerity/install_merge.sh This will install Git merge driver which will run when a conflict in this file occurs. = Including a File = To include a CSS or JS file in a page, use @{function:require_celerity_resource}: require_celerity_resource('duck-style-css'); require_celerity_resource('duck'); If your map is up to date, the resource should now be included correctly when the page is rendered. You should place this call as close to the code which actually uses the resource as possible, i.e. **not** at the top of your Controller. The idea is that you should @{function:require_celerity_resource} a resource only if you are actually using it on a specific rendering of the page, not just because some views of the page might require it. = Next Steps = Continue by: - reading about Javascript-specific guidelines in @{article:Javascript Coding Standards}; or - reading about CSS-specific guidelines and features in @{article:CSS Coding Standards}. diff --git a/src/docs/contributor/assistive_technologies.diviner b/src/docs/contributor/assistive_technologies.diviner index a519bde881..98113b2a07 100644 --- a/src/docs/contributor/assistive_technologies.diviner +++ b/src/docs/contributor/assistive_technologies.diviner @@ -1,76 +1,76 @@ @title Assistive Technologies @group developer -Information about making Phabricator accessible to assistive technologies. +Information about making Phorge accessible to assistive technologies. Overview ======== Assistive technologies help people with disabilities use the web. For example, screen readers can assist people with limited or no eyesight by reading the contents of pages aloud. -Phabricator has some support for assistive technologies, and we'd like to have +Phorge has some support for assistive technologies, and we'd like to have more support. This document describes how to use the currently available -features to improve the accessibility of Phabricator. +features to improve the accessibility of Phorge. Aural-Only Elements =================== The most common issue assistive technologies encounter is buttons, links, or other elements which only convey information visually (usually through an icon or image). These elements can be made more accessible by providing an aural-only label. This label will not be displayed by visual browsers, but will be read by screen readers. To add an aural-only label to an element, use `javelin_tag()` with the `aural` attribute: javelin_tag( 'span', array( 'aural' => true, ), pht('Aural Label Here')); This label should be placed inside the button or link that you are labeling. You can also use `aural` on a container to provide an entirely different replacement element, but should be cautious about doing this. NOTE: You must use `javelin_tag()`, not `phutil_tag()`, to get support for this attribute. Visual-Only Elements ==================== Occasionally, a visual element should be hidden from screen readers. This should be rare, but some textual elements convey very little information or are otherwise disruptive for aural users. This technique can also be used to offer a visual alternative of an element and a different aural alternative element. However, this should be rare: it is usually better to adapt a single element to work well for both visual and aural users. You can mark an element as visual-only by using `javelin_tag()` with the `aural` attribute: javelin_tag( 'span', array( 'aural' => false, ), $ascii_art); Previewing Aural Pages ====================== To verify aural markup, you can add `?__aural__=1` to any page URI. This will -make Phabricator render the page with styles that reveal aural-only elements and +make Phorge render the page with styles that reveal aural-only elements and mute visual-only elements. diff --git a/src/docs/contributor/bug_reports.diviner b/src/docs/contributor/bug_reports.diviner index e86fc917a5..3ded817a48 100644 --- a/src/docs/contributor/bug_reports.diviner +++ b/src/docs/contributor/bug_reports.diviner @@ -1,5 +1,156 @@ @title Contributing Bug Reports @group detail -Effective June 1, 2021: Phabricator is no longer actively maintained and no longer accepts bug reports. +Describes how to file an effective Phorge bug report. + +Overview +======== + +This article describes how to file an effective Phorge bug report. + +The most important things to do are: + + - check the list of common fixes below; + - make sure Phorge is up to date; + - make sure we support your setup; + - gather debugging information; and + - explain how to reproduce the issue. + +The rest of this article walks through these points in detail. + +For general information on contributing to Phorge, see +@{article:Contributor Introduction}. + + +Common Fixes +============ + +Before you file a report, here are some common solutions to problems: + + - **Update Phorge**: We receive a lot of bug reports about issues we have + already fixed in HEAD. Updating often resolves issues. It is common for + issues to be fixed in less than 24 hours, so even if you've updated recently + you should update again. If you aren't sure how to update, see the next + section. + - **Update Libraries**: Make sure `arcanist/` and `phorge/` are all up + to date. Users often update `phorge/` but forget to update `arcanist/`. + When you update, make sure you update both libraries. + - **Restart Apache or PHP-FPM**: Phorge uses caches which don't get + reset until you restart Apache or PHP-FPM. After updating, make sure you + restart. + + +Update Phorge +============= + +Before filing a bug, make sure you are up to date. We receive many bug reports +for issues we have already fixed, and even if we haven't fixed an issue we'll +be able to resolve it more easily if you file a report based on HEAD. (For +example, an old stack trace may not have the right line numbers, which will +make it more difficult for us to figure out what's going wrong.) + +To update Phorge, use a script like the one described in +@{article:Upgrading Phorge}. + +**If you can not update** for some reason, please include the version of +Phorge and Arcanist you are running when you file a report. + +For help, see @{article:Providing Version Information}. + + +Supported Issues +================ + +Before filing a bug, make sure you're filing an issue against something we +support. + +**We can NOT help you with issues we can not reproduce.** It is critical that +you explain how to reproduce the issue when filing a report. + +For help, see @{article:Providing Reproduction Steps}. + +**We do NOT support prototype applications.** If you're running into an issue +with a prototype application, you're on your own. For more information about +prototype applications, see @{article:User Guide: Prototype Applications}. + +**We do NOT support third-party packages or instructions.** If you installed +Phorge (or configured some aspect of it) using a third-party package or by +following a third-party guide (like a blog post), we can not help you. +Phorge changes quickly and third-party information is unreliable and often +falls out of date. Contact the maintainer of the package or guide you used, +or reinstall following the upstream instructions. + +**We do NOT support custom code development or third-party libraries.** If +you're writing an extension, you're on your own. We provide some documentation, +but can not help you with extension or library development. If you downloaded a +library from somewhere, contact the library maintainer. + +**We do NOT support bizarre environments.** If your issue is specific to an +unusual installation environment, we generally will not help you find a +workaround. Install Phorge in a normal environment instead. Examples of +unusual environments are shared hosts, nontraditional hosts (gaming consoles, +storage appliances), and hosts with unusually tight resource constraints. The +vast majority of users run Phorge in normal environments (modern computers +with root access) and these are the only environments we support. + +Otherwise, if you're having an issue with a supported first-party application +and followed the upstream install instructions on a normal computer, we're happy +to try to help. + + +Getting More Information +======================== + +For some issues, there are places you can check for more information. This may +help you resolve the issue yourself. Even if it doesn't, this information can +help us figure out and resolve an issue. + + - For issues with `arc` or any other command-line script, you can get more + details about what the script is doing by adding the `--trace` flag. + - For issues with Phorge, check your webserver error logs. + - For Apache, this is often `/var/log/httpd/error.log`, or + `/var/log/apache2/error.log` or similar. + - For nginx, check both the nginx and php-fpm logs. + - For issues with the UI, check the Javascript error console in your web + browser. + - Some other things, like daemons, have their own debug flags or + troubleshooting steps. Check the documentation for information on + troubleshooting. Adjusting settings or enabling debugging modes may give + you more information about the issue. + + +Reproducibility +=============== + +The most important part of your report content is instructions on how to +reproduce the issue. What did you do? If you do it again, does it still break? +Does it depend on a specific browser? Can you reproduce the issue on +a fresh, unmodified Phorge instance? + +It is nearly impossible for us to resolve many issues if we can not reproduce +them. We will not accept reports which do not contain the information required +to reproduce problems. + +For help, see @{article:Providing Reproduction Steps}. + + +File a Bug Report +================= + +If you're up to date, have collected information about the problem, and have +the best reproduction instructions you can come up with, you're ready +to file a report. + +It is **particularly critical** that you include reproduction steps. + +You can file a report [[ https://we.phorge.it/maniphest/task/edit/form/2/ | on this instance]]. + + +Next Steps +========== + +Continue by: + + - reading general support information in @{article:Support Resources}; or + - returning to the @{article:Contributor Introduction}. diff --git a/src/docs/tech/celerity.diviner b/src/docs/contributor/celerity.diviner similarity index 99% rename from src/docs/tech/celerity.diviner rename to src/docs/contributor/celerity.diviner index 8501b1b536..a5706198d3 100644 --- a/src/docs/tech/celerity.diviner +++ b/src/docs/contributor/celerity.diviner @@ -1,62 +1,62 @@ @title Celerity Technical Documentation -@group celerity +@group developer Technical overview of the Celerity system. = Overview = Celerity is a static resource (CSS and JS) management system, which handles: - Keeping track of which resources a page needs. - Generating URIs for the browser to access resources. - Managing dependencies between resources. - Packaging resources into fewer HTTP requests for performance. - Preprocessing resources (e.g., stripping comments and whitespace). - Delivering resources and managing resource cache lifetimes. - Interfacing with the client to manage resources. Celerity is an outgrowth of the //Haste// system at Facebook. You can find more information about Celerity here: - @{article:Things You Should Do Soon: Static Resources} describes the history and context of the system and the problems it solves. - @{article:Adding New CSS and JS} provides a developer guide to using Celerity. = Class Relationships = Celerity's primary API is @{function:require_celerity_resource}, which marks a resource for inclusion when a response is rendered (e.g., when the HTML page is generated, or when the response to an Ajax request is built). For instance, if you use a CSS class like "widget-view", you must ensure the appropriate CSS is included by calling `require_celerity_resource('widget-view-css')` (or similar), at your use site. This function uses @{class:CelerityAPI} to access the active @{class:CelerityStaticResourceResponse} and tells it that it needs to include the resource later, when the response actually gets built. (This layer of indirection provides future-proofing against certain complex situations Facebook eventually encountered). When the time comes to render the response, the page renderer uses @{class:CelerityAPI} to access the active @{class:CelerityStaticResourceResponse} and requests that it render out appropriate references to CSS and JS resources. It uses @{class:CelerityResourceMap} to determine the dependencies for the requested resources (so you only have to explicitly include what you're actually using, and not all of its dependencies) and any packaging rules (so it may be able to generate fewer resource requests, improving performance). It then generates `` and `` references to these resources. These references point at `/res/` URIs, which are handled by @{class:CelerityResourceController}. It responds to these requests and delivers the relevant resources and packages, managing cache lifetimes and handling any necessary preprocessing. It uses @{class:CelerityResourceMap} to locate resources and read packaging rules. The dependency and packaging maps are generated by `bin/celerity map`, which updates `resources/celerity/map.php`. @{class:CelerityStaticResourceResponse} also manages some Javelin information, and @{function:celerity_generate_unique_node_id} uses this metadata to provide a better uniqueness guarantee when generating unique node IDs. diff --git a/src/docs/contributor/cla.diviner b/src/docs/contributor/cla.diviner deleted file mode 100644 index 54e0142beb..0000000000 --- a/src/docs/contributor/cla.diviner +++ /dev/null @@ -1,169 +0,0 @@ -@title Understanding the Phacility CLA -@group detail - -Describes the Contributor License Agreement (CLA). - -Overview -======== - -IMPORTANT: This document is not legal advice. - -Phacility requires contributors to sign a Contributor License Agreement -(often abbreviated "CLA") before we can accept contributions into the upstream. -This document explains what this document means and why we require it. - -This requirement is not unusual, and many large open source projects require a -similar CLA, including Python, Go, jQuery, and Apache Software Foundation -projects. - -You can read more about CLAs and find more examples of companies and projects -which require them on Wikipedia's -[[ https://en.wikipedia.org/wiki/Contributor_License_Agreement | CLA ]] page. - -Our CLA is substantially similar to the CLA required by Apache, the -"Apache Individual Contributor License Agreement V2.0". Many projects which -require a CLA use this CLA or a similar one. - - -Why We Require a CLA -==================== - -While many projects require a CLA, others do not. This project requires a CLA -primarily because: - - - it gives us certain rights, particularly the ability to relicense the work - later; - - it makes the terms of your contribution clear, protecting us from liability - related to copyright and patent disputes. - -**More Rights**: We consider the cost of maintaining changes to greatly -outweigh the cost of writing them in the first place. When we accept work -into the upstream, we are agreeing to bear that maintenance cost. - -This cost is not worthwhile to us unless the changes come with no strings -attached. Among other concerns, we would be unable to redistribute Phabricator -under a different license in the future without the additional rights the CLA -gives us. - -For a concrete example of the problems this causes, Bootstrap switched from -GPLv2 to MIT in 2012-2013. You can see the issue tracking the process and read -about what they had to go through to do this here: - -https://github.com/twbs/bootstrap/issues/2054 - -This took almost 18 months and required a huge amount of effort. We are not -willing to encumber the project with that kind of potential cost in order to -accept contributions. - -The rights you give us by signing the CLA allow us to release the software -under a different license later without asking you for permission, including a -license you may not agree with. - -They do not allow us to //undo// the existing release under the Apache license, -but allow us to make an //additional// release under a different license, or -release under multiple licenses (if we do, users may choose which license or -licenses they wish to use the software under). It would also allow us to -discontinue updating the release under the Apache license. - -While we do not currently plan to relicense Phabricator, we do not want to -give up the ability to do so: we may want or need to in the future. - -The most likely scenario which would lead to us changing the license is if a -new version of the Apache license is released. Open source software licenses -are still largely untested in the US legal system, and they may face challenges -in the future which could require adapting them to a changing legal -environment. If this occurs, we would want to be able to update to a newer -version of the license which accounted for these changes. - -It is also possible that we may want to change open source licenses (for -example, to MIT) or adopt dual-licensing (for example, both Apache and MIT). We -might want to do this so that our license is compatible with the licenses used -by other software we want to be distributed alongside. - -Although we currently believe it is unlikely, it is also possible we may want -to relicense Phabricator under a closed, proprietary, or literally evil license. -By signing the CLA, you are giving us the power to do this without requiring -you to consent. If you are not comfortable with this, do not sign the CLA and -do not contribute to Phabricator. - -**Limitation of Liability**: The second benefit the CLA provides is that it -makes the terms of your contribution explicitly clear upfront, and it puts us -in a much stronger legal position if a contributor later claims there is -ambiguity about ownership of their work. We can point at the document they -signed as proof that they consented to our use and understood the terms of -their contribution. - -//SCO v. IBM// was a lawsuit filed in 2003 alleging (roughly) that IBM had -improperly contributed code owned by SCO to Linux. The details of this and the -subsequent cases are very complex and the situation is not a direct parallel to -anything we are likely to face, but SCO claimed billions of dollars in damages -and the litigation has now been ongoing for more than a decade. - -We want to avoid situations like this in the future by making the terms of -contribution explicit upfront. - -Generally, we believe the terms of the CLA are fair and reasonable for -contributors, and that the primary way contributors benefit from contributing -to Phabricator is that we publish and maintain their changes so they do not -have to fork the software. - -If you have strong ideological reasons for contributing to open source, you may -not be comfortable with the terms of the CLA (for example, it may be important -to you that your changes are never available under a license which you haven't -explicitly approved). This is fine and we can understand why contributors may -hold this viewpoint, but we can not accept your changes into the upstream. - - -Corporate vs Individual CLAs -============================ - -We offer two CLAs: - - - {L28} - - {L30} - -These are both substantially similar to the corresponding Apache CLAs. - -If you own the work you are contributing, sign the individual CLA. If your -employer owns the work you are contributing, have them sign the corporate CLA. - -**If you are employed, there is a substantial possibility that your employer -owns your work.** If they do, you do not have the right to contribute it to us -or assign the rights that we require, and can not contribute under the -individual CLA. Work with your employer to contribute under the corporate CLA -instead. - -Particularly, this clause in the individual CLA is the important one: - -> 4. You represent that you are legally entitled to grant the above license. If -> your employer(s) has rights to intellectual property that you create that -> includes your Contributions, you represent that you have received permission -> to make Contributions on behalf of that employer, that your employer has -> waived such rights for your Contributions to Phacility, or that your employer -> has executed a separate Corporate CLA with Phacility. - -Ownership of your work varies based on where you live, how you are employed, -and your agreements with your employer. However, at least in the US, it is -likely that your employer owns your work unless you have anticipated conflicts -and specifically avoided them. This generally makes sense: if you are paid by -your employer for your work, they own the product of your work and you receive -salary and benefits in fair exchange for that work. - -Your employer may have an ownership claim on your work even if you perform it -on your own time, if you use their equipment (like a company laptop or phone), -resources, facilities, or trade secrets, or signed something like an "Invention -Assignment Agreement" when you were hired. Such agreements are common. The -details of the strength of their claim will vary based on your situation and -local law. - -If you are unsure, you should speak with your employer or a lawyer. If you -contribute code you do not own under the individual CLA, you are exposing -yourself to liability. You may also be exposing us to liability, but we'll have -the CLA on our side to show that we were unwilling pawns in your malicious -scheme to defraud your employer. - -The good news is that most employers are happy to contribute to open source -projects. Incentives are generally well aligned: they get features they want, -and it reflects well on them. In the past, potential contributors who have -approached their employers about a corporate CLA have generally had little -difficulty getting approval. diff --git a/src/docs/contributor/contrib_intro.diviner b/src/docs/contributor/contrib_intro.diviner index 59ad9b44df..00a2e42c0e 100644 --- a/src/docs/contributor/contrib_intro.diviner +++ b/src/docs/contributor/contrib_intro.diviner @@ -1,54 +1,41 @@ @title Contributor Introduction @group contrib -Introduction to contributing to Phabricator and Arcanist. +Introduction to contributing to Phorge and Arcanist. Overview ======== -If you'd like to contribute to Phabricator, this document can guide you though +If you'd like to contribute to Phorge, this document can guide you though ways you can help improve the project. Writing code is valuable, but often isn't the best or easiest way to contribute. In most cases we are pretty good at fixing easy stuff quickly, so we don't have a big pile of easy stuff sitting around waiting for new contributors. This can make it difficult to contribute code if you only have a little bit of time to spend since most of the work that needs to be done usually requires some heavy lifting. Without writing any code, learning the whole codebase, making a big time commitment, or having to touch PHP, here are some ways you can materially -contribute to Phabricator: +contribute to Phorge: - - Drop by the [[ https://phurl.io/u/discourse | community forum ]] just to - say "thanks". A big part of the reason we build this software is to help - people solve problems, and knowing that our efforts are appreciated is - really rewarding. - - Recommend Phabricator to people who you think might find it useful. Our + - Recommend Phorge to people who you think might find it useful. Our most powerful growth channel is word of mouth, and mentioning or tweeting - about Phabricator helps the project grow. If writing a tweet sounds like - too much work, you can use one of these form tweets written by our PR - department to quickly and easily shill on our behalf. Hail corporate! - -> Phabricator seems like it's pretty okay - -> I am not being paid to mention Phabricator in this extemporaneous, completely organic tweet - -> Phabricator is objectively the best thing. Source: I am a certified, internationally recognized expert. - + about Phorge helps the project grow. - Submit high-quality bug reports by carefully following the guide in @{article:Contributing Bug Reports}. If all of this sounds nice but you really just want to write some code, be aware that this project often presents a high barrier to entry for new contributors. To continue, see @{article:Contributing Code}. Next Steps ========== Continue by: - learning about bug reports in @{article:Contributing Bug Reports}; - learning about code contributions in @{article:Contributing Code}. diff --git a/src/docs/contributor/contributing_code.diviner b/src/docs/contributor/contributing_code.diviner index b6816e03b5..c7bdec25c9 100644 --- a/src/docs/contributor/contributing_code.diviner +++ b/src/docs/contributor/contributing_code.diviner @@ -1,4 +1,197 @@ @title Contributing Code @group detail -Effective June 1, 2021: Phabricator is no longer actively maintained, and no longer accepting contributions. +Phorge is an open-source project, and welcomes contributions from the community +at large. However, there are some guidelines we ask you to follow. + + +Overview +======== + +The most important parts of contributing code to Phorge are: + + - File a task with a bug report or feature request //before// you write code. + - We do not accept GitHub pull requests. + - Some alternative approaches are available if your change isn't something + we want to bring upstream. + +The rest of this article describes these points in more detail, and then +provides guidance on writing and submitting patches. + +If you just want to contribute some code but don't have a specific bug or +feature in mind, see the bottom of this document for tips on finding ways to get +started. + +For general information on contributing to Phorge, see +@{article:Contributor Introduction}. + + +Coordinate First +================ + +Before sending code, you should file a task describing what you'd like to write. + +When you file a task, mention that you'd like to write the code to fix it. We +can help contextualize your request or bug and guide you through writing an +upstreamable patch, provided it's something that's upstreamable. If it isn't +upstreamable, we can let you know what the issues are and help find another +plan of attack. + +You don't have to file first (for example, if you spot a misspelling it's +normally fine to just send a diff), but for anything even moderately complex +you're strongly encouraged to file first and coordinate with the upstream. + + +Rejecting Patches +================= + +If you send us a patch without coordinating it with us first, it will probably +be immediately rejected, or sit in limbo for a long time and eventually be +rejected. The reasons we do this vary from patch to patch, but some of the most +common reasons are: + +**Unjustifiable Costs**: We support code in the upstream forever. Support is +enormously expensive and takes up a huge amount of our time. The cost to support +a change over its lifetime is often 10x or 100x or 1000x greater than the cost +to write the first version of it. Many uncoordinated patches we receive are +"white elephants", which would cost much more to maintain than the value they +provide. + +As an author, it may look like you're giving us free work and we're rejecting it +as too expensive, but this viewpoint doesn't align with the reality of a large +project which is actively supported by a small, experienced team. Writing code +is cheap; maintaining it is expensive. + +By coordinating with us first, you can make sure the patch is something we +consider valuable enough to put long-term support resources behind, and that +you're building it in a way that we're comfortable taking over. + +**Not a Good Fit**: Many patches aren't good fits for the upstream: they +implement features we simply don't want. Coordinating with us first helps +make sure we're on the same page and interested in a feature. + +The most common type of patch along these lines is a patch which adds new +configuration options. We consider additional configuration options to have +an exceptionally high lifetime support cost and are very unlikely to accept +them. Coordinate with us first. + +**Not a Priority**: If you send us a patch against something which isn't a +priority, we probably won't have time to look at it. We don't give special +treatment to low-priority issues just because there's code written: we'd still +be spending time on something lower-priority when we could be spending it on +something higher-priority instead. + +If you coordinate with us first, you can make sure your patch is in an area +of the codebase that we can prioritize. + +**Overly Ambitious Patches**: Sometimes we'll get huge patches from new +contributors. These can have a lot of fundamental problems and require a huge +amount of our time to review and correct. If you're interested in contributing, +you'll have more success if you start small and learn as you go. + +We can help you break a large change into smaller pieces and learn how the +codebase works as you proceed through the implementation, but only if you +coordinate with us first. + +**Generality**: We often receive several feature requests which ask for similar +features, and can come up with a general approach which covers all of the use +cases. If you send us a patch for //your use case only//, the approach may be +too specific. When a cleaner and more general approach is available, we usually +prefer to pursue it. + +By coordinating with us first, we can make you aware of similar use cases and +opportunities to generalize an approach. These changes are often small, but can +have a big impact on how useful a piece of code is. + +**Infrastructure and Sequencing**: Sometimes patches are written against a piece +of infrastructure with major planned changes. We don't want to accept these +because they'll make the infrastructure changes more difficult to implement. + +Coordinate with us first to make sure a change doesn't need to wait on other +pieces of infrastructure. We can help you identify technical blockers and +possibly guide you through resolving them if you're interested. + + +No Prototype Changes +==================== + +With rare exceptions, we do not accept patches for prototype applications for +the same reasons that we don't accept feature requests or bug reports. To learn +more about prototype applications, see +@{article:User Guide: Prototype Applications}. + + +No Pull Requests +================ + +We do not accept pull requests on GitHub: + + - Pull requests do not get lint and unit tests run, so issues which are + normally caught statically can slip by. + - Phorge is code review software, and developed using its own workflows. + Pull requests bypass some of these workflows (for example, they will not + trigger Herald rules to notify interested parties). + - GitHub is not the authoritative master repository and we maintain a linear + history, so merging pull requests is cumbersome on our end. + - If you're comfortable enough with Phorge to contribute to it, you + should also be comfortable using it to submit changes. + +Instead of sending a pull request, use `arc diff` to create a revision on the +upstream install. Your change will go through the normal Phorge review +process. + +(GitHub does not allow repositories to disable pull requests, which is why +it's technically possible to submit them.) + + +Alternatives +============ + +If you've written code but we're not accepting it into the upstream, some +alternative approaches include: + +**Maintain a local fork.** This will require some ongoing effort to port your +changes forward when you update, but is often very reasonable for simple +changes. + +**Develop as an application.** Many parts of Phorge's infrastructure are +modular, and modularity is increasing over time. A lot of changes can be built +as external modules or applications without forking Phorge itself. There +isn't much documentation for this right now, but you can look at +how other applications are implemented, and at other third-party code that +extends Phorge. + +**Rise to prominence.** We're more willing to accept borderline changes from +community members who are active, make multiple contributions, or have a history +with the project. This is not carte blanche, but distinguishing yourself can +make us feel more comfortable about supporting a change which is slightly +outside of our comfort zone. + + +Writing and Submitting Patches +================== + +To actually submit a patch, run `arc diff` in `phorge/` or `arcanist/`. +When executed in these directories, `arc` should automatically talk to the +upstream install. You can add #blessed_reviewers as a reviewer. + +You should read the relevant coding convention documents before you submit a +change. If you're a new contributor, you don't need to worry about this too +much. Just try to make your code look similar to the code around it, and we +can help you through the details during review. + + - @{article:General Coding Standards} (for all languages) + - @{article:PHP Coding Standards} (for PHP) + - @{article:Javascript Coding Standards} (for Javascript) + +In general, if you're coordinating with us first, we can usually provide +guidance on how to implement things. The other articles in this section also +provide information on how to work in the Phorge codebase. + + +Next Steps +========== + +Continue by: + + - returning to the @{article:Contributor Introduction}. diff --git a/src/docs/contributor/css_coding_standards.diviner b/src/docs/contributor/css_coding_standards.diviner index c321124eae..e83778f24c 100644 --- a/src/docs/contributor/css_coding_standards.diviner +++ b/src/docs/contributor/css_coding_standards.diviner @@ -1,91 +1,91 @@ @title CSS Coding Standards @group standards -This document describes CSS features and coding standards for Phabricator. +This document describes CSS features and coding standards for Phorge. = Overview = This document describes technical and style guidelines for writing CSS in -Phabricator. +Phorge. -Phabricator has a limited CSS preprocessor. This document describes the features +Phorge has a limited CSS preprocessor. This document describes the features it makes available. = Z-Indexes = You should put all `z-index` rules in `z-index.css`, and keep them sorted. The goal is to make indexes relatively manageable and reduce the escalation of the Great Z-Index War where all indexes grow without bound in an endless arms race. = Color Variables = -Phabricator's preprocessor provides some standard color variables. You can +Phorge's preprocessor provides some standard color variables. You can reference these with `{$color}`. For example: lang=css span.critical { color: {$red}; } You can find a list of all available colors in the **UIExamples** application. = Printable Rules = If you preface a rule with `!print`, it will be transformed into a print rule and activated when the user is printing the page or viewing a printable version of the page: lang=css !print div.menu { display: none; } Specifically, this directive causes two copies of the rule to be written out. The output will look something like this: lang=css .printable div.menu { display: none; } @media print { div.menu { display: none; } } The former will activate when users look at the printable versions of pages, by adding `__print__` to the URI. The latter will be activated in print contexts by the media query. = Device Rules = -Phabricator's environment defines several device classes which can be used to +Phorge's environment defines several device classes which can be used to adjust behavior responsively. In particular: lang=css .device-phone { /* Smallest breakpoint, usually for phones. */ } .device-tablet { /* Middle breakpoint, usually for tablets. */ } .device-desktop { /* Largest breakpoint, usually for desktops. */ } Since many rules are specific to handheld devices, the `.device` class selects either tablets or phones: lang=css .device { /* Phone or tablet (not desktop). */ } = Image Inlining = -Phabricator's CSS preprocessor automatically inlines images which are less than +Phorge's CSS preprocessor automatically inlines images which are less than 32KB using `data:` URIs. This is primarily useful for gradients or textures which are small and difficult to sprite. diff --git a/src/docs/contributor/database.diviner b/src/docs/contributor/database.diviner index aaea485dc6..fc39c1ff1c 100644 --- a/src/docs/contributor/database.diviner +++ b/src/docs/contributor/database.diviner @@ -1,211 +1,213 @@ @title Database Schema @group developer This document describes key components of the database schema and should answer questions like how to store new types of data. Database System =============== -Phabricator uses MySQL or another MySQL-compatible database (like MariaDB +Phorge uses MySQL or another MySQL-compatible database (like MariaDB or Amazon RDS). -Phabricator uses the InnoDB table engine. The only exception is the +Phorge uses the InnoDB table engine. The only exception is the `search_documentfield` table which uses MyISAM because MySQL doesn't support fulltext search in InnoDB (recent versions do, but we haven't added support yet). We are unlikely to ever support other incompatible databases like PostgreSQL or SQLite. PHP Drivers =========== -Phabricator supports [[ http://www.php.net/book.mysql | MySQL ]] and +Phorge supports [[ http://www.php.net/book.mysql | MySQL ]] and [[ http://www.php.net/book.mysqli | MySQLi ]] PHP extensions. Databases ========= -Each Phabricator application has its own database. The names are prefixed by -`phabricator_` (this is configurable). +Each Phorge application has its own database. The names are prefixed by +`phorge_` (this is configurable). -Phabricator uses a separate database for each application. To understand why, -see @{article:Why does Phabricator need so many databases?}. +Phorge uses a separate database for each application. To understand why, +see @{article:Why does Phorge need so many databases?}. Connections =========== -Phabricator specifies if it will use any opened connection just for reading or +Phorge specifies if it will use any opened connection just for reading or also for writing. This allows opening write connections to a primary and read connections to a replica in primary/replica setups (which are not actually supported yet). Tables ====== Most table names are prefixed by their application names. For example, -Differential revisions are stored in database `phabricator_differential` and +Differential revisions are stored in database `phorge_differential` and table `differential_revision`. This generally makes queries easier to recognize and understand. The exception is a few tables which share the same schema over different databases such as `edge`. We use lower-case table names with words separated by underscores. Column Names ============ -Phabricator uses `camelCase` names for columns. The main advantage is that they +Phorge uses `camelCase` names for columns. The main advantage is that they directly map to properties in PHP classes. Don't use MySQL reserved words (such as `order`) for column names. Data Types ========== -Phabricator defines a set of abstract data types (like `uint32`, `epoch`, and +Phorge defines a set of abstract data types (like `uint32`, `epoch`, and `phid`) which map to MySQL column types. The mapping depends on the MySQL version. -Phabricator uses `utf8mb4` character sets where available (MySQL 5.5 or newer), +Phorge uses `utf8mb4` character sets where available (MySQL 5.5 or newer), and `binary` character sets in most other cases. The primary motivation is to allow 4-byte unicode characters to be stored (the `utf8` character set, which is more widely available, does not support them). On newer MySQL, we use `utf8mb4` to take advantage of improved collation rules. -Phabricator stores dates with an `epoch` abstract data type, which maps to +Phorge stores dates with an `epoch` abstract data type, which maps to `int unsigned`. Although this makes dates less readable when browsing the database, it makes date and time manipulation more consistent and straightforward in the application. We don't use the `enum` data type because each change to the list of possible values requires altering the table (which is slow with big tables). We use numbers (or short strings in some cases) mapped to PHP constants instead. JSON and Other Serialized Data ============================== Some data don't require structured access -- we don't need to filter or order by them. We store these data as text fields in JSON format. This approach has several advantages: - If we decide to add another unstructured field then we don't need to alter the table (which is slow for big tables in MySQL). - Table structure is not cluttered by fields which could be unused most of the time. An example of such usage can be found in column `differential_diffproperty.data`. Primary Keys ============ Most tables have an auto-increment column named `id`. Adding an ID column is appropriate for most tables (even tables that have another natural unique key), as it improves consistency and makes it easier to perform generic operations on objects. For example, @{class:LiskMigrationIterator} allows you to very easily apply a migration to a table using a constant amount of memory provided the table has an `id` column. Indexes ====== Create all indexes necessary for fast query execution in most cases. Don't create indexes which are not used. You can analyze queries @{article:Using DarkConsole}. Older MySQL versions are not able to use indexes for tuple search: `(a, b) IN ((%s, %d), (%s, %d))`. Use `AND` and `OR` instead: `((a = %s AND b = %d) OR (a = %s AND b = %d))`. Foreign Keys ============ We don't use foreign keys because they're complicated and we haven't experienced significant issues with data inconsistency that foreign keys could help prevent. Empirically, we have witnessed first hand as `ON DELETE CASCADE` relationships accidentally destroy huge amounts of data. We may pursue foreign keys eventually, but there isn't a strong case for them at the present time. PHIDs ===== -Each globally referencable object in Phabricator has an associated PHID -("Phabricator ID") which serves as a global identifier, similar to a GUID. +Each globally referencable object in Phorge has an associated PHID +("Phorge ID") which serves as a global identifier, similar to a GUID. We use PHIDs for referencing data in different databases. We use both auto-incrementing IDs and global PHIDs because each is useful in different contexts. Auto-incrementing IDs are meaningfully ordered and allow us to construct short, human-readable object names (like `D2258`) and URIs. Global PHIDs allow us to represent relationships between different types of objects in a homogeneous way. For example, infrastructure like "subscribers" can be implemented easily with PHID relationships: different types of objects (users, projects, mailing lists) are permitted to subscribe to different types of objects (revisions, tasks, etc). Without PHIDs, we would need to add a "type" column to avoid ID collision; using PHIDs makes implementing features like this simpler. +For more information, see @{article:Handles Technical Documentation} + Transactions ============ Transactional code should be written using transactions. Example of such code is inserting multiple records where one doesn't make sense without the other, or selecting data later used for update. See chapter in @{class:LiskDAO}. Advanced Features ================= We don't use MySQL advanced features such as triggers, stored procedures or events because we like expressing the application logic in PHP more than in SQL. Some of these features (especially triggers) can also cause a great deal of confusion, and are generally more difficult to debug, profile, version control, update, and understand than application code. Schema Denormalization ====================== -Phabricator uses schema denormalization sparingly. Avoid denormalization unless +Phorge uses schema denormalization sparingly. Avoid denormalization unless there is a compelling reason (usually, performance) to denormalize. Schema Changes and Migrations ============================= To create a new schema change or migration: **Create a database patch**. Database patches go in `resources/sql/autopatches/`. To change a schema, use a `.sql` file and write in SQL. To perform a migration, use a `.php` file and write in PHP. Name your file `YYYYMMDD.patchname.ext`. For example, `20141225.christmas.sql`. **Keep patches small**. Most schema change statements are not transactional. If a patch contains several SQL statements and fails partway through, it normally can not be rolled back. When a user tries to apply the patch again later, the first statement (which, for example, adds a column) may fail (because the column already exists). This can be avoided by keeping patches small (generally, one statement per patch). **Use namespace and character set variables**. When defining a `.sql` patch, you should use these variables instead of hard-coding namespaces or character set names: | Variable | Meaning | Notes | |---|---|---| | `{$NAMESPACE}` | Storage Namespace | Defaults to `phabricator` | | `{$CHARSET}` | Default Charset | Mostly used to specify table charset | | `{$COLLATE_TEXT}` | Text Collation | For most text (case-sensitive) | | `{$COLLATE_SORT}` | Sort Collation | For sortable text (case-insensitive) | | `{$CHARSET_FULLTEXT}` | Fulltext Charset | Specify explicitly for fulltext | | `{$COLLATE_FULLTEXT}` | Fulltext Collate | Specify explicitly for fulltext | **Test your patch**. Run `bin/storage upgrade` to test your patch. See Also ======== - @{class:LiskDAO} diff --git a/src/docs/contributor/describing_problems.diviner b/src/docs/contributor/describing_problems.diviner index d06d7b7d64..30f3b4cad0 100644 --- a/src/docs/contributor/describing_problems.diviner +++ b/src/docs/contributor/describing_problems.diviner @@ -1,159 +1,159 @@ @title Describing Root Problems @group detail Explains how to describe a root problem effectively. Overview ======== We receive many feature requests with poor problem descriptions. You may have filed such a request if you've been sent here. This document explains what we want, and how to give us the information to help you. We will **never** implement a feature request without first understanding the root problem. Good problem descriptions let us answer your questions quickly and correctly, and suggest workarounds or alternate ways to accomplish what you want. Poor problem descriptions require us to ask multiple clarifying questions and do not give us enough information to suggest alternate solutions or workarounds. We need to keep going back and forth to understand the problem you're really facing, which means it will take a long time to get the answer you want. What We Want ============ We want a description of your overarching goal. The problem you started trying to solve first, long before you decided what feature you needed. This doesn't need to be very detailed, we just need to know what you are ultimately hoping to accomplish. Problem descriptions should include context and explain why you're encountering a problem and why it's important for you to resolve it. Here are some examples of good ways to start a problem description: -> My company does contracting work for government agencies. Because of the -> nature of our customers, deadlines are critical and it's very important -> for us to keep track of where we are on a timeline. We're using Maniphest -> to track tasks... +(NOTE) My company does contracting work for government agencies. Because of the + nature of our customers, deadlines are critical and it's very important +for us to keep track of where we are on a timeline. We're using Maniphest +to track tasks... -> I have poor eyesight, and use a screenreader to help me use software like -> Phabricator in my job as a developer. I'm having difficulty... +(NOTE) I have poor eyesight, and use a screenreader to help me use software like + Phorge in my job as a developer. I'm having difficulty... -> We work on a large server program which has very long compile times. -> Switching branches is a huge pain (you have to rebuild the binary after -> every switch, which takes about 8 minutes), but we've recently begun using -> `git worktree` to help, which has made life a lot better. However, ... +(NOTE) We work on a large server program which has very long compile times. + Switching branches is a huge pain (you have to rebuild the binary after + every switch, which takes about 8 minutes), but we've recently begun using + `git worktree` to help, which has made life a lot better. However, ... -> I triage manual test failures from our offshore QA team. Here's how our -> workflow works... +(NOTE) I triage manual test failures from our offshore QA team. Here's how our + workflow works... All of these descriptions are helpful: the provide context about what goals you're trying to accomplish and why. Here are some examples of ways to start a problem description that probably are not very good: -> {icon times color=red} Add custom keyboard shortcuts. +(IMPORTANT) Add custom keyboard shortcuts. -> {icon times color=red} I have a problem: there is no way to download -> .tar archives of repositories. +(IMPORTANT) I have a problem: there is no way to download + .tar archives of repositories. -> {icon times color=red} I want an RSS feed of my tokens. My root problem is -> that I do not have an RSS feed of my tokens. +(IMPORTANT) I want an RSS feed of my tokens. My root problem is + that I do not have an RSS feed of my tokens. -> {icon times color=red} There is no way to see other users' email addresses. -> That is a problem. +(IMPORTANT) There is no way to see other users' email addresses. + That is a problem. -> {icon times color=red} I've used some other software that has a cool -> feature. Phabricator should have that feature too. +(IMPORTANT) I've used some other software that has a cool + feature. Phorge should have that feature too. These problem descriptions are not helpful. They do not describe goals or provide context. "5 Whys" Technique ================ If you're having trouble understanding what we're asking for, one technique which may help is ask yourself "Why?" repeatedly. Each answer will usually get you closer to describing the root problem. For example: > I want custom keyboard shortcuts. This is a very poor feature request which does not describe the root problem. It limits us to only one possible solution. Try asking "Why?" to get closer to the root problem. > **Why?** > I want to add a shortcut to create a new task. This is still very poor, but we can now think about solutions involving making this whole flow easier, or adding a shortcut for exactly this to the upstream, which might be a lot easier than adding custom keyboard shortcuts. It's common to stop here and report this as your root problem. This is **not** a root problem. This problem is only //slightly// more general than the one we started with. Let's ask "Why?" again to get closer to the root problem. > **Why?** > I create a lot of very similar tasks every day. This is still quite poor, but we can now think about solutions like a bulk task creation flow, or maybe point you at task creation templating or prefilling or the Conduit API or email integration or Doorkeeper. > **Why?** > The other developers email me issues and I copy/paste them into Maniphest. This is getting closer, but still doesn't tell us what your goal is. > **Why?** > We set up email integration before, but each task needs to have specific > projects so that didn't work and now I'm stuck doing the entry by hand. This is in the realm of reasonable, and likely easy to solve with custom inbound addresses and Herald rules, or with a small extension to Herald. We might try to improve the documentation to make the feature easier to discover or understand. You could (and should) go even further than this and explain why tasks need to be tagged with specific projects. It's very easy to provide more context and can only improve the speed and quality of our response. Note that this solution (Herald rules on inbound email) has nothing to do with the narrow feature request (keyboard shortcuts) that you otherwise arrived at, but there's no possible way we can suggest a solution involving email integration or Herald if your report doesn't even mention that part of the context. Additional Resources ==================== Poor problem descriptions are a common issue in software development and extensively documented elsewhere. Here are some additional resources describing how to describe problems and ask questions effectively: - [[ http://www.catb.org/esr/faqs/smart-questions.html | How To Ask Questions The Smart Way ]], by Eric S. Raymond - [[ http://xyproblem.info | XY Problem ]] - [[ https://en.wikipedia.org/wiki/5_Whys | 5 Whys Technique ]] Asking good questions and describing problems clearly is an important, fundamental communication skill that software professionals should cultivate. Next Steps ========== Continue by: - returning to @{article:Contributing Feature Requests}. diff --git a/src/docs/contributor/developer_setup.diviner b/src/docs/contributor/developer_setup.diviner index 95508ccd19..8c39c5fc3d 100644 --- a/src/docs/contributor/developer_setup.diviner +++ b/src/docs/contributor/developer_setup.diviner @@ -1,112 +1,112 @@ @title Developer Setup @group developer -How to configure a Phabricator development environment. +How to configure a Phorge development environment. Overview ======== There are some options and workflows that may be useful if you are developing -or debugging Phabricator. +or debugging Phorge. Configuration ============= -To adjust Phabricator for development: +To adjust Phorge for development: - Enable `phabricator.developer-mode` to enable some options and show more debugging information. - Enable `phabricator.show-prototypes` to show all the incomplete applications. - See @{article: Using DarkConsole} for instructions on enabling the debugging console. Error Handling ============== Errors normally go to DarkConsole (if enabled) and the webserver error log, which is often located somewhere like `/var/log/apache/error_log`. This file often contains relevant information after you encounter an error. When debugging, you can print information to the error log with `phlog(...)`. You can `phlog(new Exception(...))` to get a stack trace. You can print information to the UI with `throw new Exception(...)`, `print_r(...)`, or `var_dump(...)`. You can abort execution with `die(...)` if you want to make sure execution does not make it past some point. Normally `throw` does this too, but callers can `catch` exceptions; they can not catch `die(...)`. Utilities ========= After adding, renaming, or moving classes, run `arc liberate` to rebuild the class map: ``` -phabricator/ $ arc liberate +phorge/ $ arc liberate ``` -Until you do this, Phabricator won't recognize your new, moved, or renamed +Until you do this, Phorge won't recognize your new, moved, or renamed classes. You do not need to run this after modifying an existing class. After any modifications to static resources (CSS / JS) but before sending changes for review or pushing them to the remote, run `bin/celerity map`: ``` -phabricator/ $ ./bin/celerity map +phorge/ $ ./bin/celerity map ``` This rebuilds the static resource map. If you forget to run these commands you'll normally be warned by unit tests, but knowing about them may prevent confusion before you hit the warnings. Command Line ============ Almost every script supports a `--trace` flag, which prints out service calls and more detailed error information. This is often the best way to get started with debugging command-line scripts. Performance =========== Although it is more user-focused than developer-focused, the @{article:Troubleshooting Performance Problems} guide has useful information on the tools available for diagnosing and understanding performance problems. Custom Domains ============== If you're working with applications that support custom domains (like Phurl or Phame) you can normally test them by adding more entries to your webserver configuration that look exactly like the primary entry (or expanding the primary entry to match more domains). -Phabricator routes all requests based on host headers, so alternate domains +Phorge routes all requests based on host headers, so alternate domains do not normally need any kind of special configuration. You may also need to add `/etc/hosts` entries for the domains themselves. Creating Test Data ================== You can create test objects with the "Lipsum" utility: ``` -phabricator/ $ ./bin/lipsum help generate -phabricator/ $ ./bin/lipsum generate ... +phorge/ $ ./bin/lipsum help generate +phorge/ $ ./bin/lipsum generate ... ``` Test data can make your local install feel a little more realistic. With `--quickly`, you can generate a large amount of test data to help test issues with performance or scale. diff --git a/src/docs/contributor/feature_requests.diviner b/src/docs/contributor/feature_requests.diviner index 20fe4b2d30..b2ca702cec 100644 --- a/src/docs/contributor/feature_requests.diviner +++ b/src/docs/contributor/feature_requests.diviner @@ -1,4 +1,211 @@ @title Contributing Feature Requests @group detail -Effective June 1, 2021: Phabricator is no longer actively maintained, and there is no way to file a feature request. +Describes how to file an effective Phorge feature request. + +Overview +======== + +Phorge is an open-source project, and welcomes feature requests from the community +at large. However, there are some guidelines we ask you to follow. + +Overview +======== + +This article describes how to file an effective feature request. + +The most important things to do are: + + - understand the upstream; + - make sure your feature makes sense in the project; + - align your expectations around timelines and priorities; + - describe your problem, not your solution. + +The rest of this article walks through these points in detail. + +If you have a bug report (not a feature request), see +@{article:Contributing Bug Reports} for a more tailored guide. + +For general information on contributing to Phorge, see +@{article:Contributor Introduction}. + + +Understanding the Upstream +========================== + +Before filing a feature request, it may be useful to understand how the +upstream operates. + +Phorge has a designated core team who controls the project and roadmap. +We have a cohesive vision for the project in the long term, and a general +roadmap that extends for years into the future. While the specifics of how +we get there are flexible, many major milestones are well-established. + +Although we set project direction, the community is also a critical part of +Phorge. We aren't all-knowing, and we rely on feedback to help us identify +issues, guide product direction, prioritize changes, and suggest features. + +Feature requests are an important part of this, but we ultimately build only +features which make sense as part of the long term plan. + +Since it's hard to absorb a detailed understanding of that vision, //describing +a problem// is often more effective than //requesting a feature//. We have the +context to develop solutions which fit into our plans, address similar use +cases, make sense with the available infrastructure, and work within the +boundaries of our product vision. For more details on this, see below. + + +Target Audiences +================ + +Some feature requests support very unusual use cases. Although we are broadly +inclusive of many different kinds of users and use cases, we are not trying +to make the software all things to all users. Use cases which are far afield +from the things the majority of users do with Phorge often face substantial +barriers. + +Phorge is primarily targeted at software projects and organizations with +a heavy software focus. We are most likely to design, build, and prioritize +features which serve these organizations and projects. + +Phorge is primarily targeted at software professionals and other +professionals with adjacent responsibilities (like project management and +operations). Particularly, we assume users are proficient computer users and +familiar with software development concepts. We are most likely to design, build +and prioritize features which serve these users. + +Phorge is primarily targeted at professionals working in teams on full-time +projects. Particularly, we assume most users will use the software regularly and +are often willing to spend a little more time up front to get a more efficient +workflow in the long run. We are most likely to design, build and prioritize +features which serve these use cases. + +Phorge is not limited to these kinds of organizations, users and use cases, +but features which are aimed at a different group of users (like students, +casual projects, or inexperienced computer users) may be harder to get +upstreamed. Features aimed at very different groups of users (like wedding +planners, book clubs, or dogs) will be much harder to get upstreamed. + +In many cases, a feature makes something better for all users. For example, +suppose we fixed an issue where colorblind users had difficulty doing something. +Dogs would benefit the most, but colorblind human users would also benefit, and +no one would be worse off. If the benefit for core users is very small these +kinds of features may be hard to prioritize, but there is no exceptional barrier +to getting them upstreamed. + +In other cases, a feature makes something better for some users and worse for +other users. These kinds of features face a high barrier if they make the +software better at planning weddings and worse at reviewing code. + + +Setting Expectations +==================== + +We have a lot of users and a small team. Even if your feature is something we're +interested in and a good fit for where we want the product to go, it may take +us a long time to get around to building it. + +Our long-term roadmap (which we call our +[[ https://we.phorge.it/w/starmap/ | Starmap ]]) has many years worth +of work. Your feature request is competing against thousands of other requests +for priority. + +In general, we try to prioritize work that will have the greatest impact on the +most users. Many feature requests are perfectly reasonable requests, but have +very little impact, impact only a few users, and/or are complex to develop and +support relative to their impact. It can take us a long time to get to these. + +Even if your feature request is simple and has substantial impact for a large +number of users, the size of the request queue means that it is mathematically +unlikely to be near the top. + +As a whole, this means that the overwhelming majority of feature requests will +sit in queue for a long time without any updates, and that we won't be able to +give you any updates or predictions about timelines. One day, out of nowhere, +your feature will materialize. That day may be a decade from now. You should +have realistic expectations about this when filing a feature request. + + +Describe Problems +================= + +When you file a feature request, we need you to describe the problem you're +facing first, not just your desired solution. Describing the problem you are +facing is the **most important part** of a feature request. + +Often, your problem may have a lot in common with other similar problems. If we +understand your use case we can compare it to other use cases and sometimes find +a more powerful or more general solution which solves several problems at once. + +At other times, we'll have a planned solution to the problem that might be +different from your desired solution but accomplish the same goal. Understanding +the root issue can let us merge and contextualize things. + +Sometimes there's already a way to solve your problem that might just not be +obvious. + +Finally, your proposed solution may not be compatible with the direction we +want to take the product, but we may be able to come up with another solution +which has approximately the same effect and does fit into the product direction. + +If you only describe the solution and not the problem, we can't generalize, +contextualize, merge, reframe, or offer alternative solutions or workarounds. + +You must describe the problem you are facing when filing a feature request. We +will not accept feature requests which do not contextualize the request by +describing the root problem. + +If you aren't sure exactly what we're after when we ask you to describe a root +problem, you can find examples and more discussion in +@{article:Describing Root Problems}. + + +Hypotheticals +============= + +We sometimes receive hypothetical feature requests about anticipated problems +or concerns which haven't actually occurred yet. We usually can't do much about +these until the problems actually occur, since the context required to +understand and properly fix the root issue won't exist. + +One situation where this happens is when installs are thinking about adopting +Phorge and trying to guess what problems users might encounter during the +transition. More generally, this includes any request like "if users do **X**, +they might find **Y** confusing", where no actual users have encountered +confusion yet. + +These requests are necessarily missing important context, maybe including the +answers to questions like these: + + - Why did users do **X**? + - What were they trying to do? + - What did they expect to happen? + - How often do users do this? + +The answers to these questions are important in establishing that the issue is +really a problem, figuring out the best solution for it, and prioritizing the +issue relative to other issues. + +Without knowing this information, we can't be confident that we've found a good +solution to the problem, can't know if we've actually fixed the problem, and +can't even know if the issue was really a problem in the first place (some +hypothetical requests describe problems which no users ever encounter). + +We usually can't move forward without this information. In particular, we don't +want to spend time solving hypothetical problems which no real users will ever +encounter: the value of those changes is zero (or negative, by making the +product more complex without providing a benefit), but they consume development +time which could be better spent building much more valuable features. + +Generally, you should wait until a problem actually occurs before filing a +request about it. + + +Next Steps +========== + +Continue by: + + - learning about @{article: Contributing Bug Reports}; or + - reading general support information in @{article:Support Resources}; or + - returning to the @{article:Contributor Introduction}. diff --git a/src/docs/contributor/general_coding_standards.diviner b/src/docs/contributor/general_coding_standards.diviner index 9b151312fd..5127aebbfc 100644 --- a/src/docs/contributor/general_coding_standards.diviner +++ b/src/docs/contributor/general_coding_standards.diviner @@ -1,148 +1,148 @@ @title General Coding Standards @group standards -This document is a general coding standard for contributing to Phabricator, +This document is a general coding standard for contributing to Phorge, Arcanist, and Diviner. = Overview = This document contains practices and guidelines which apply across languages. Contributors should follow these guidelines. These guidelines are not hard-and-fast but should be followed unless there is a compelling reason to deviate from them. = Code Complexity = - Prefer to write simple code which is easy to understand. The simplest code is not necessarily the smallest, and some changes which make code larger (such as decomposing complex expressions and choosing more descriptive names) may also make it simpler. Be willing to make size tradeoffs in favor of simplicity. - Prefer simple methods and functions which take a small number of parameters. Avoid methods and functions which are long and complex, or take an innumerable host of parameters. When possible, decompose monolithic, complex methods into several focused, simpler ones. - Avoid putting many ideas on a single line of code. For example, avoid this kind of code: COUNTEREXAMPLE $category_map = array_combine( $dates, array_map(create_function('$z', 'return date("F Y", $z);'), $dates)); Expressing this complex transformation more simply produces more readable code: $category_map = array(); foreach ($dates as $date) { $category_map[$date] = date('F Y', $date); } And, obviously, don't do this sort of thing: COUNTEREXAMPLE if ($val = $some->complicatedConstruct() && !!~blarg_blarg_blarg() & $flags ? HOPE_YOU_MEMORIZED == $all_the_lexical_binding_powers : <<<'Q' ${hahaha} Q ); = Performance = - Prefer to write efficient code. - Strongly prefer to drive optimization decisions with hard data. Avoid optimizing based on intuition or rumor if you can not support it with concrete measurements. - Prefer to optimize code which is slow and runs often. Optimizing code which is fast and runs rarely is usually a waste of time, and can even be harmful if it makes that code more difficult to understand or maintain. You can determine if code is fast or slow by measuring it. - Reject performance discussions that aren't rooted in concrete data. -In Phabricator, you can usually use the builtin XHProf profiling to quickly +In Phorge, you can usually use the builtin XHProf profiling to quickly gather concrete performance data. = Naming Things = - Follow language-specific conventions. - Name things unambiguously. - Choose descriptive names. - Avoid nonstandard abbreviations (common abbreviations like ID, URI and HTTP are fine). - Spell words correctly. - Use correct grammar. For example, avoid these sorts of naming choices: COUNTEREXAMPLE $PIE->GET_FLAVOR(); // Unconventional. $thing->doStuff(); // Ambiguous. $list->empty(); // Ambiguous -- is it isEmpty() or makeEmpty()? $e = 3; // Not descriptive. $this->updtHndlr(); // Nonstandard abbreviation. $this->chackSpulls(); // Misspelling, ungrammatical. Prefer these: $pie->getFlavor(); // Conventional. $pie->bake(); // Unambiguous. $list->isEmpty(); // Unambiguous. $list->makeEmpty(); // Unambiguous. $edge_count = 3; // Descriptive. $this->updateHandler(); // No nonstandard abbreviations. $this->getID(); // Standard abbreviation. $this->checkSpelling(); // Correct spelling and grammar. = Error Handling = - Strongly prefer to detect errors. - Strongly prefer to fail fast and loudly. The maximum cost of script termination is known, bounded, and fairly small. The maximum cost of continuing script execution when errors have occurred is unknown and unbounded. This also makes APIs much easier to use and problems far easier to debug. When you ignore errors, defer error handling, or degrade the severity of errors by treating them as warnings and then dismissing them, you risk dangerous behavior which may be difficult to troubleshoot: COUNTEREXAMPLE exec('echo '.$data.' > file.bak'); // Bad! do_something_dangerous(); exec('echo '.$data.' > file.bak', $out, $err); // Also bad! if ($err) { debug_rlog("Unable to copy file!"); } do_something_dangerous(); Instead, fail loudly: exec('echo '.$data.' > file.bak', $out, $err); // Better if ($err) { throw new Exception("Unable to copy file!"); } do_something_dangerous(); But the best approach is to use or write an API which simplifies condition handling and makes it easier to get right than wrong: execx('echo %s > file.bak', $data); // Good do_something_dangerous(); Filesystem::writeFile('file.bak', $data); // Best do_something_dangerous(); See @{article@arcanist:Command Execution} for details on the APIs used in this example. = Documentation, Comments and Formatting = - Prefer to remove code by deleting it over removing it by commenting it out. It shall live forever in source control, and can be retrieved therefrom if it is ever again called upon. - In source code, use only ASCII printable characters plus space and linefeed. Do not use UTF-8 or other multibyte encodings. diff --git a/src/docs/tech/handles.diviner b/src/docs/contributor/handles.diviner similarity index 97% rename from src/docs/tech/handles.diviner rename to src/docs/contributor/handles.diviner index 18e33dc133..ffdd0f0705 100644 --- a/src/docs/tech/handles.diviner +++ b/src/docs/contributor/handles.diviner @@ -1,101 +1,101 @@ @title Handles Technical Documentation -@group handles +@group developer Technical overview of Handles. Overview ======== -Most objects in Phabricator have PHIDs, which are globally unique identifiers +Most objects in Phorge have PHIDs, which are globally unique identifiers that look like `PHID-USER-2zw4hwdt4i5b5ypikv6x`. If you know the PHID for an object, you can load a **handle** for that object to get more information about it. Handles are lightweight reference objects which provide some basic information common across all objects (like their type, icons, names, monograms, URIs, and whether they are open or closed). Applications don't need to know anything about other types of objects in order to load and use handles. There are uniform mechanisms available to load and work with handles which work across all types of objects in every application. Loading Handles =============== To load handles, you'll usually call `loadHandles(...)` on the viewer: $handles = $viewer->loadHandles($phids); This returns a @{class:PhabricatorHandleList}. This object behaves like an array, and you can access handle objects by using their PHIDs as indexes: $handle = $handles[$phid]; Handles will always load, even if the PHID is invalid or the object it identifies is restricted or broken. In these cases, the handle will accurately represent the state of the associated object. This means that you generally do not need to check if a handle loaded. Rendering Handles ================= After loading handles, you'll usually call `renderHandle($phid)` to render a link to an object: $view = $handles->renderHandle($phid); This returns a @{class:PHUIHandleView}. The class exposes some methods which can adjust how the handle renders. If you want to render a list of handles, you can use `renderList()`: $list_view = $handles->renderList(); This returns a @{class:PHUIHandleListView}. This class also exposes some methods to adjust how the list renders. Convenience methods for these operations are also available on the viewer object itself: $view = $viewer->renderHandle($phid); $list_view = $viewer->renderHandleList($phids); When you only need to render a handle once, these methods make it easier. Fetch Semantics =============== When you load and render handles through the viewer, the actual data fetching occurs just-in-time. Specifically, all of the required PHIDs are queued up until a concrete representation //needs// to be produced. Handles are then bulk loaded. This means that, unlike most other types of data fetching, it's OK to single-fetch handles, because they won't //really// single-fetch. This code is correct and desirable: $list->addProperty(pht('Pilot'), $viewer->renderHandle($pilot_phid)); $list->addProperty(pht('Copilot'), $viewer->renderHandle($copilot_phid)); If you're rendering a very large number of handles (for example, 100+ handles in a result list view) it's //slightly// more efficient to render them through a @{class:PhabricatorHandleList}: $handles = $viewer->loadHandles($phids); foreach ($items as $item) { // ... $view = $handles->renderHandle($item->getPHID()); // ... } This shaves off a tiny bit of internal bookkeeping overhead. This does not change the underlying semantics of the data fetch. Handles are particularly well suited to use this just-in-time fetch pattern because they're ubiquitous and code essentially never makes decisions based on handles, so it's very rare that they need to be made concrete until final page rendering. Most other kinds of data do not have the same sort of application-level semantics. This generally makes other objects much less suitable to be fetched just-in-time. diff --git a/src/docs/contributor/internationalization.diviner b/src/docs/contributor/internationalization.diviner index 99c35e675e..84fe2d2591 100644 --- a/src/docs/contributor/internationalization.diviner +++ b/src/docs/contributor/internationalization.diviner @@ -1,381 +1,381 @@ @title Internationalization @group developer -Describes Phabricator translation and localization. +Describes Phorge translation and localization. Overview ======== -Phabricator partially supports internationalization, but many of the tools +Phorge partially supports internationalization, but many of the tools are missing or in a prototype state. 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@phabcontrib:Adding New Classes}. +@{article@contrib: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@phabcontrib:Adding New Classes}. +@{article@contrib:Adding New Classes}. Writing Translatable Code ========================= Strings are marked for translation with @{function@arcanist:pht}. 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. If text strings will ultimately be read by humans, they should essentially always be wrapped in `pht()`. For example: ```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 +version of the text, if the viewer is using Phorge 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@arcanist: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 colloquialisms, 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. 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: ```lang=php, counterexample if ($count == 1) { return pht('This will take an hour.'); } else { return pht('This will take hours.'); } ``` 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: +string in the @{class: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 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@arcanist: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@arcanist:PhutilInvalidStateException} and @{class@arcanist: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 raised 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@phabcontrib:Adding New Classes}. + @{article@contrib:Adding New Classes}. diff --git a/src/docs/contributor/javascript_coding_standards.diviner b/src/docs/contributor/javascript_coding_standards.diviner index 3b47a566a6..39103e94ec 100644 --- a/src/docs/contributor/javascript_coding_standards.diviner +++ b/src/docs/contributor/javascript_coding_standards.diviner @@ -1,139 +1,139 @@ @title Javascript Coding Standards @group standards -This document describes Javascript coding standards for Phabricator and Javelin. +This document describes Javascript coding standards for Phorge and Javelin. = Overview = This document outlines technical and style guidelines which are followed in -Phabricator and Javelin. Contributors should also follow these guidelines. Many +Phorge and Javelin. Contributors should also follow these guidelines. Many of these guidelines are automatically enforced by lint. These guidelines are essentially identical to the Facebook guidelines, since I basically copy-pasted them. If you are already familiar with the Facebook guidelines, you can probably get away with skimming this document. = Spaces, Linebreaks and Indentation = - Use two spaces for indentation. Don't use literal tab characters. - Use Unix linebreaks ("\n"), not MSDOS ("\r\n") or OS9 ("\r"). - Put a space after control keywords like `if` and `for`. - Put a space after commas in argument lists. - Put space around operators like `=`, `<`, etc. - Don't put spaces after function names. - Parentheses should hug their contents. - Generally, prefer to wrap code at 80 columns. = Case and Capitalization = The Javascript language unambiguously dictates casing/naming rules; follow those rules. - Name variables using `lowercase_with_underscores`. - Name classes using `UpperCamelCase`. - Name methods and properties using `lowerCamelCase`. - Name global functions using `lowerCamelCase`. Avoid defining global functions. - Name constants using `UPPERCASE`. - Write `true`, `false`, and `null` in lowercase. - "Internal" methods and properties should be prefixed with an underscore. For more information about what "internal" means, see **Leading Underscores**, below. = Comments = - Strongly prefer `//` comments for making comments inside the bodies of functions and methods (this lets someone easily comment out a block of code while debugging later). = Javascript Language = - Use `[]` and `{}`, not `new Array` and `new Object`. - When creating an object literal, do not quote keys unless required. = Examples = **if/else:** lang=js if (x > 3) { // ... } else if (x === null) { // ... } else { // ... } You should always put braces around the body of an if clause, even if it is only one line. Note that operators like `>` and `===` are also surrounded by spaces. **for (iteration):** lang=js for (var ii = 0; ii < 10; ii++) { // ... } Prefer ii, jj, kk, etc., as iterators, since they're easier to pick out visually and react better to "Find Next..." in editors. **for (enumeration):** lang=js for (var k in obj) { // ... } Make sure you use enumeration only on Objects, not on Arrays. For more details, see @{article:Javascript Object and Array}. **switch:** lang=js switch (x) { case 1: // ... break; case 2: if (flag) { break; } break; default: // ... break; } `break` statements should be indented to block level. If you don't push them in, you end up with an inconsistent rule for conditional `break` statements, as in the `2` case. If you insist on having a "fall through" case that does not end with `break`, make it clear in a comment that you wrote this intentionally. For instance: lang=js switch (x) { case 1: // ... // Fall through... case 2: //... break; } = Leading Underscores = By convention, methods names which start with a leading underscore are considered "internal", which (roughly) means "private". The critical difference is that this is treated as a signal to Javascript processing scripts that a symbol is safe to rename since it is not referenced outside the current file. The upshot here is: - name internal methods which shouldn't be called outside of a file's scope with a leading underscore; and - **never** call an internal method from another file. If you treat them as though they were "private", you won't run into problems. diff --git a/src/docs/contributor/n_plus_one.diviner b/src/docs/contributor/n_plus_one.diviner index 6d259671a1..21bac266e7 100644 --- a/src/docs/contributor/n_plus_one.diviner +++ b/src/docs/contributor/n_plus_one.diviner @@ -1,77 +1,77 @@ @title Performance: N+1 Query Problem @group developer How to avoid a common performance pitfall. = Overview = The N+1 query problem is a common performance antipattern. It looks like this: COUNTEREXAMPLE $cats = load_cats(); foreach ($cats as $cat) { $cats_hats = load_hats_for_cat($cat); // ... } Assuming `load_cats()` has an implementation that boils down to: SELECT * FROM cat WHERE ... ..and `load_hats_for_cat($cat)` has an implementation something like this: SELECT * FROM hat WHERE catID = ... ..you will issue "N+1" queries when the code executes, where N is the number of cats: SELECT * FROM cat WHERE ... SELECT * FROM hat WHERE catID = 1 SELECT * FROM hat WHERE catID = 2 SELECT * FROM hat WHERE catID = 3 SELECT * FROM hat WHERE catID = 4 SELECT * FROM hat WHERE catID = 5 ... The problem with this is that each query has quite a bit of overhead. **It is //much faster// to issue 1 query which returns 100 results than to issue 100 queries which each return 1 result.** This is particularly true if your database is on a different machine which is, say, 1-2ms away on the network. In this case, issuing 100 queries serially has a minimum cost of 100-200ms, even if they can be satisfied instantly by MySQL. This is far higher than the entire -server-side generation cost for most Phabricator pages should be. +server-side generation cost for most Phorge pages should be. = Batching Queries = Fix the N+1 query problem by batching queries. Load all your data before iterating through it (this is oversimplified and omits error checking): $cats = load_cats(); $hats = load_all_hats_for_these_cats($cats); foreach ($cats as $cat) { $cats_hats = $hats[$cat->getID()]; } That is, issue these queries: SELECT * FROM cat WHERE ... SELECT * FROM hat WHERE catID IN (1, 2, 3, 4, 5, ...) In this case, the total number of queries issued is always 2, no matter how many objects there are. You've removed the "N" part from the page's query plan, and are no longer paying the overhead of issuing hundreds of extra queries. This will perform much better (although, as with all performance changes, you should verify this claim by measuring it). See also @{method:LiskDAO::loadRelatives} method which provides an abstraction to prevent this problem. = Detecting the Problem = Beyond reasoning about it while figuring out how to load the data you need, the easiest way to detect this issue is to check the "Services" tab in DarkConsole (see @{article:Using DarkConsole}), which lists all the service calls made on a page. If you see a bunch of similar queries, this often indicates an N+1 query issue (or a similar kind of query batching problem). Restructuring code so you can run a single query to fetch all the data at once will always improve the performance of the page. diff --git a/src/docs/contributor/phabricator_code_layout.diviner b/src/docs/contributor/phorge_code_layout.diviner similarity index 78% rename from src/docs/contributor/phabricator_code_layout.diviner rename to src/docs/contributor/phorge_code_layout.diviner index 422f228a27..fee99ed897 100644 --- a/src/docs/contributor/phabricator_code_layout.diviner +++ b/src/docs/contributor/phorge_code_layout.diviner @@ -1,111 +1,111 @@ -@title Phabricator Code Layout +@title Phorge Code Layout @group developer -Guide to Phabricator code layout, including how URI mapping works through +Guide to Phorge code layout, including how URI mapping works through application class and subdirectory organization best practices. = URI Mapping = -When a user visits a Phabricator URI, the Phabricator infrastructure parses -that URI with a regular expression to determine what controller class to load. +When a user visits a Phorge URI, the Phorge infrastructure parses that URI with + a regular expression to determine what controller class to load. -The Phabricator infrastructure knows where a given controller class lives on +The Phorge infrastructure knows where a given controller class lives on disk from a cache file the Arcanist phutil mapper generates. This mapping should be updated whenever new classes or files are added: - arc liberate /path/to/phabricator/src + arc liberate /path/to/phorge/src Finally, a given controller class will map to an application which will have most of its code in standardized subdirectories and classes. = Best Practice Class and Subdirectory Organization = Suppose you were working on the application `Derp`. - phabricator/src/applications/derp/ + phorge/src/applications/derp/ If `Derp` were as simple as possible, it would have one subdirectory: - phabricator/src/applications/derp/controller/ + phorge/src/applications/derp/controller/ containing the file `DerpController.php` with the class - `DerpController`: minimally implements a `processRequest()` method which returns some @{class:AphrontResponse} object. The class would probably extend @{class:PhabricatorController}. If `Derp` were (relatively) complex, one could reasonably expect to see the following directory layout: - phabricator/src/applications/derp/conduit/ - phabricator/src/applications/derp/constants/ - phabricator/src/applications/derp/controller/ - phabricator/src/applications/derp/editor/ - phabricator/src/applications/derp/exception/ - phabricator/src/applications/derp/query/ - phabricator/src/applications/derp/replyhandler/ - phabricator/src/applications/derp/storage/ - phabricator/src/applications/derp/view/ + phorge/src/applications/derp/conduit/ + phorge/src/applications/derp/constants/ + phorge/src/applications/derp/controller/ + phorge/src/applications/derp/editor/ + phorge/src/applications/derp/exception/ + phorge/src/applications/derp/query/ + phorge/src/applications/derp/replyhandler/ + phorge/src/applications/derp/storage/ + phorge/src/applications/derp/view/ (The following two folders are also likely to be included for JavaScript and CSS respectively. However, static resources are largely outside the scope of this document. See @{article:Adding New CSS and JS}.) - phabricator/webroot/rsrc/js/application/derp/ - phabricator/webroot/rsrc/css/application/derp/ + phorge/webroot/rsrc/js/application/derp/ + phorge/webroot/rsrc/css/application/derp/ -These directories under `phabricator/src/applications/derp/` represent -the basic set of class types from which most Phabricator applications are +These directories under `phorge/src/applications/derp/` represent +the basic set of class types from which most Phorge applications are assembled. Each would contain a class file. For `Derp`, these classes could be something like: - **DerpConstants**: constants used in the `Derp` application. - **DerpController**: business logic providing functionality for a given URI. Typically, controllers load data via Storage or Query classes, then present the data to the user via one or more View classes. - **DerpEditor**: business logic for workflows that change one or more Storage objects. Editor classes are only necessary for particularly complicated edits and should be used pragmatically versus Storage objects. - **DerpException**: exceptions used in the `Derp` application. - **DerpQuery**: query one or more storage objects for pertinent `Derp` application data. @{class:PhabricatorOffsetPagedQuery} is particularly handy for pagination and works well with @{class:AphrontPagerView}. - **DerpReplyHandler**: business logic from any configured email interactions users can have with the `Derp` application. - **DerpStorage**: storage objects for the `Derp` application. Typically there is a base class which extends @{class:PhabricatorLiskDAO} to configure application-wide storage settings like the application (thus database) name. Reading more about the @{class:LiskDAO} is highly recommended. - **DerpView**: view objects for the `Derp` application. Typically these extend @{class:AphrontView}. - **DerpConduitAPIMethod**: provides any and all `Derp` application functionality that is accessible over Conduit. However, it is likely that `Derp` is even more complex, and rather than containing one class, each directory has several classes. A typical example happens around the CRUD of an object: - **DerpBaseController**: typically extends @{class:PhabricatorController} and contains any controller-specific functionality used throughout the `Derp` application. - **DerpDeleteController**: typically extends `DerpBaseController` and presents a confirmation dialogue to the user about deleting a `Derp`. - **DerpEditController**: typically extends `DerpBaseController` and presents a form to create and edit `Derps`. Most likely uses @{class:AphrontFormView} and various `AphrontFormXControl` classes such as @{class:AphrontFormTextControl} to create the form. - **DerpListController**: typically extends `DerpBaseController` and displays a set of one or more `Derps`. Might use @{class:AphrontTableView} to create a table of `Derps`. - **DerpViewController**: typically extends `DerpBaseController` and displays a single `Derp`. Some especially awesome directories might have a `__tests__` subdirectory containing all pertinent unit test code for the class. = Next Steps = - Learn about @{article:Adding New CSS and JS}; or - learn about the @{class:LiskDAO}; or - learn about @{article:Writing Unit Tests}; or - learn how to contribute (see @{article:Contributor Introduction}). diff --git a/src/docs/contributor/php_coding_standards.diviner b/src/docs/contributor/php_coding_standards.diviner index a14acf17f2..bb54478fa3 100644 --- a/src/docs/contributor/php_coding_standards.diviner +++ b/src/docs/contributor/php_coding_standards.diviner @@ -1,178 +1,178 @@ @title PHP Coding Standards @group standards -This document describes PHP coding standards for Phabricator and related +This document describes PHP coding standards for Phorge and related projects (like Arcanist). = Overview = This document outlines technical and style guidelines which are followed in -Phabricator and Arcanist. Contributors should also follow these guidelines. +Phorge and Arcanist. Contributors should also follow these guidelines. Many of these guidelines are automatically enforced by lint. These guidelines are essentially identical to the Facebook guidelines, since I basically copy-pasted them. If you are already familiar with the Facebook guidelines, you probably don't need to read this super thoroughly. = Spaces, Linebreaks and Indentation = - Use two spaces for indentation. Don't use tab literal characters. - Use Unix linebreaks ("\n"), not MSDOS ("\r\n") or OS9 ("\r"). - Put a space after control keywords like `if` and `for`. - Put a space after commas in argument lists. - Put a space around operators like `=`, `<`, etc. - Don't put spaces after function names. - Parentheses should hug their contents. - Generally, prefer to wrap code at 80 columns. = Case and Capitalization = - Name variables and functions using `lowercase_with_underscores`. - Name classes using `UpperCamelCase`. - Name methods and properties using `lowerCamelCase`. - Use uppercase for common acronyms like ID and HTML. - Name constants using `UPPERCASE`. - Write `true`, `false` and `null` in lowercase. = Comments = - Do not use "#" (shell-style) comments. - Prefer "//" comments inside function and method bodies. = PHP Language Style = - Use "" tag. - Prefer casts like `(string)` to casting functions like `strval()`. - Prefer type checks like `$v === null` to type functions like `is_null()`. - Avoid all crazy alternate forms of language constructs like "endwhile" and "<>". - Always put braces around conditional and loop blocks. = PHP Language Features = - Use PHP as a programming language, not a templating language. - Avoid globals. - Avoid extract(). - Avoid eval(). - Avoid variable variables. - Prefer classes over functions. - Prefer class constants over defines. - Avoid naked class properties; instead, define accessors. - Use exceptions for error conditions. - Use type hints, use `assert_instances_of()` for arrays holding objects. = Examples = **if/else:** lang=php if ($some_variable > 3) { // ... } else if ($some_variable === null) { // ... } else { // ... } You should always put braces around the body of an if clause, even if it is only one line long. Note spaces around operators and after control statements. Do not use the "endif" construct, and write "else if" as two words. **for:** lang=php for ($ii = 0; $ii < 10; $ii++) { // ... } Prefer $ii, $jj, $kk, etc., as iterators, since they're easier to pick out visually and react better to "Find Next..." in editors. **foreach:** lang=php foreach ($map as $key => $value) { // ... } **switch:** lang=php switch ($value) { case 1: // ... break; case 2: if ($flag) { // ... break; } break; default: // ... break; } `break` statements should be indented to block level. **array literals:** lang=php $junk = array( 'nuts', 'bolts', 'refuse', ); Use a trailing comma and put the closing parenthesis on a separate line so that diffs which add elements to the array affect only one line. **operators:** lang=php $a + $b; // Put spaces around operators. $omg.$lol; // Exception: no spaces around string concatenation. $arr[] = $element; // Couple [] with the array when appending. $obj = new Thing(); // Always use parens. **function/method calls:** lang=php // One line eject($cargo); // Multiline AbstractFireFactoryFactoryEngine::promulgateConflagrationInstance( $fuel, $ignition_source); **function/method definitions:** lang=php function example_function($base_value, $additional_value) { return $base_value + $additional_value; } class C { public static function promulgateConflagrationInstance( IFuel $fuel, IgnitionSource $source) { // ... } } **class:** lang=php class Dog extends Animal { const CIRCLES_REQUIRED_TO_LIE_DOWN = 3; private $favoriteFood = 'dirt'; public function getFavoriteFood() { return $this->favoriteFood; } } diff --git a/src/docs/contributor/rendering_html.diviner b/src/docs/contributor/rendering_html.diviner index a8fe5a899d..40892b5ad7 100644 --- a/src/docs/contributor/rendering_html.diviner +++ b/src/docs/contributor/rendering_html.diviner @@ -1,182 +1,182 @@ @title Rendering HTML @group developer -Rendering HTML in the Phabricator environment. +Rendering HTML in the Phorge environment. = Overview = -Phabricator attempts to prevent XSS by treating strings as default-unsafe when +Phorge attempts to prevent XSS by treating strings as default-unsafe when rendering. This means that if you try to build HTML through string concatenation, it won't work: the string will be escaped by the rendering pipeline, and the browser will treat it as plain text, not HTML. This document describes the right way to build HTML components so they are safe from XSS and render correctly. Broadly: - Use @{function@arcanist:phutil_tag} (and @{function:javelin_tag}) to build tags. - Use @{function@arcanist:hsprintf} where @{function@arcanist:phutil_tag} is awkward. - Combine elements with arrays, not string concatenation. - @{class:AphrontView} subclasses should return a @{class@arcanist:PhutilSafeHTML} object from their `render()` method. - @{class:AphrontView} subclasses act like tags when rendering. - @{function:pht} has some special rules. - There are some other things that you should be aware of. See below for discussion. = Building Tags: phutil_tag() = Build HTML tags with @{function@arcanist:phutil_tag}. For example: phutil_tag( 'div', array( 'class' => 'some-class', ), $content); @{function@arcanist:phutil_tag} will properly escape the content and all the attributes, and return a @{class@arcanist:PhutilSafeHTML} object. The rendering pipeline knows that this object represents a properly escaped HTML tag. This allows @{function@arcanist:phutil_tag} to render tags with other tags as content correctly (without double-escaping): phutil_tag( 'div', array(), phutil_tag( 'strong', array(), $content)); -In Phabricator, the @{function:javelin_tag} function is similar to +In Phorge, the @{function:javelin_tag} function is similar to @{function@arcanist:phutil_tag}, but provides special handling for the `sigil` and `meta` attributes. = Building Blocks: hsprintf() = Sometimes, @{function@arcanist:phutil_tag} can be particularly awkward to use. You can use @{function@arcanist:hsprintf} to build larger and more complex blocks of HTML, when @{function@arcanist:phutil_tag} is a poor fit. @{function:hsprintf} has `sprintf()` semantics, but `%s` escapes HTML: // Safely build fragments or unwieldy blocks. hsprintf( '