Вы используете устаревший браузер. Этот и другие сайты могут отображаться в нём некорректно. Вам необходимо обновить браузер или попробовать использовать другой.
Выпуск версии 1.5 был последним в серии XenForo 1.x.x, далее разработчики начнут, точнее уже начали заниматься разработкой XenForo 2.0. Все новости о XenForo 2.0 мы будем размещать в данной теме. :-)
Приближается время официального релиза XenForo 1.5 и теперь необходимо переключить наше основное внимание на развитие новой версии, которой станет XenForo 2.0.
Несмотря на то, что мы пока не можем предоставить большое количество деталей о версии 2.0, у нас есть рамки принципы и идеи, которые мы хотим реализовать именно в новой версии, в том числе будет произведён целый ряд структурных изменений, которые позволят повысить производительность труда разработчиков и гибкость кода. Мы уже писали некоторые особенности основного кода для того, чтобы проверить жизнеспособность некоторых наших архитектурных идей, и мы в восторге от прогресса, который получился.
Основная версия выпуска, например, XenForo 2.0 дает нам больше свободы, чтобы сделать значительные, радикальные изменения.
Требования к версии PHP с выходом XenForo 2.0 конечно же будут увеличены, прежде всего, потому что мы хотим использовать новые технологии, которые сейчас предлагают новые версии PHP. Текущая минимальная версия, необходимая для корректной работы XenForo - PHP 5.3, но к сожалению он уже достаточно устарел.
Сразу скажу, что о выходе новой версии ничего неизвестно. Как только что-то будет проясняться, мы напишем здесь об этом. :-)
Что нового для разработчиков в XenForo 2 (часть 1):
Entities and finders
Entities are XF2's version of data writers. However, they're much more extensive, as:
They are used for reads and writes. Most work you do with data will be through an entity.
They automatically handle encoding and decoding of values. For example, if you define a column as JSON, you just give it the original data structure (probably an array) and it will encode on save. Similarly, when the record is fetched, the value will be decoded back to that array when you access it.
They can have getters to wrap logic around grabbing a value that isn't necessarily stored in the DB. For example, option titles are actually phrases, so there's no title column in the option table. However, we can call $option->title or $option['title'] and it will return a phrase object.
They define their relations and they can be dynamically loaded as needed. $post->Thread->Forum will return the forum that the post belongs to, regardless of whether you included the thread and/or forum records when you loaded the post.
They support some amount of horizontal reuse through the "behavior" system. This currently only adds extra behaviors when write actions are taken, but examples are to automatically adjust likes when content is deleted or hidden and applying change logging to an entity.
Other methods can be added to expose whatever other functionality you need. Call $user->authenticate($password) to see if the password is correct or $thread->canView() to determine if the current visitor can view the thread.
Finders are essentially query builders. In the generic case, they provide methods such as where or order that allow you to apply conditions, ordering and the like based on the DB structure of an entity. However, they can also expose entity-specific methods to provide more complex functionality. Here's an example:
Most of these are generic finder methods, but unreadOnly() is a thread-specific method that applies logic based on the current visitor and complex conditions to limit the results to only unread threads. While XF1 had some similar concepts (implemented very differently), the complex restrictions weren't generally re-usable and that cause a lot of repeated code. In XF2, these constraints are generally shared by the finder object and can be reused. As an example, it's trivial to display only threads that the user has posted in but are also unread.
There's a lot more to this topic. It's a major change to the fundamental data access layer of XenForo and is probably one of the most important concepts to grasp in XF2. As we have other things to cover, let's just add a couple bullet points:
When fetching multiple entities, a collection object is returned rather than a bare array. You can get the array if you like, but the collection object has various helper methods (pluck, filter, groupBy, etc).
Relationships in entities can return many entities. To take a specific example, a thread may be watched by many people, but we generally only care about one person at a time. $thread->Watch[$userId] will return the watch record for the specified $userId. It won't eager load all of the other watchers. The "Watch" value is a "sparse" collection of entities that loads individual records on demand (or you can tell it to fetch all of them).
You can eager load a single record of a "to many" relationship: $finder->with("Watch|$userId").
Finders can be built in any order. You can apply a condition after you set the order or limits.
You don't need to manually create a finder in every case. We have common methods in "repository" objects that setup finders for base/common cases. These repositories can also hold other logic/actions relating to a type of entity. Some examples include methods like countUnreadThreadsInForum(\) or rebuildThreadUserPostCounters(). While these aren't meant to be a generic dumping group, they can also be used for various helper methods, particularly when they manipulate the database directly or don't relate to a single entity.
Most of our finder-related repository methods return the finder rather than the results. This means that they can be manipulated for specific cases or extended to add extra behaviors. For example, findThreadsForForumView() could be extended to always include a specific relation (though there is another place this could be done within the thread finder).
Ok I lied, that was more than a couple.
Service oriented system
Most complex actions have now been moved to service objects that represent that action only. Most services work in a "setup and go" type of approach; you setup your configuration and then call a method to complete the action.
This example was posted in the development update, but it's a good example:
PHP:
/** @var \XF\Service\Thread\Mover $mover */
$mover = $this->app()->service('XF:Thread\Mover', $thread);
if ($options['alert'])
{
$mover->setSendAlert(true, $options['alert_reason']);
}
if ($options['notify_watchers'])
{
$mover->setNotifyWatchers();
}
if ($options['redirect'])
{
$mover->setRedirect(true, $options['redirect_length']);
}
if ($options['prefix_id'] !== null)
{
$mover->setPrefix($options['prefix_id']);
}
$mover->move($targetForum);
This is roughly the sort of code you'd see in the controller. $options would be based on the user input. The actual code for doing the changes is done within the thread move service. This includes actually moving the thread and changing the thread prefix, creating a redirect, notifying the watchers, sending the alert and logging the action. Any other location wanting to move a thread can call this service with the desired configuration and go; there's no duplication of "business logic".
Now this example is a little simplified for the sake of keeping it focused on the service. However, what you'll actually find is that most service calls in XF2 create and configure the service in one method and then pass the service to another method to actually carry out the action. This is what's actually in the move method of the thread controller:
Because the services do all of the setup in the setupThreadMove method, this creates a perfect hook/injection point for custom behavior. Add your custom options to the service and then configure them by extending the setupThreadMove method. No creating hacky global variables/registry keys to try to inject your additional changes.
Form actions
Speaking of avoiding hacky global variables and registry keys to do extensions...
Not all actions use services. A lot of the admin control panel pages fit in the CRUD model: create, read, update, delete. They just take user input, pass it to the entity and save it. They don't try to manipulate the user input in any way or trigger other actions.
Instead of creating a service for every single case, we use a FormAction object that allows us to mimic the "setup and go" behavior of services. These actions break down into several phases: setup, validate, apply, complete. Behaviors can be added to each one and when ready, we "run" the action. If any errors occur during validation, they're thrown and apply never runs.
The simplest case would be something like how we save a style:
basicEntitySave() is a helper method that pushes the input into the entity during setup, validates it through preSave(), and saves it during the apply step.
Like in services, this happens within a method and the form action is returned from it and run:
PHP:
$this->styleSaveProcess($style)->run();
So if you have modified the form to add extra input, you'd just inject that using something like:
PHP:
$form->setup(function() use ($style, $customValue)
{
$style->custom_value = $customValue;
});
Use of LESS (CSS pre-processor) and BEM-styled CSS
We've discussed this in the past so I won't go into too much detail but it's worth mentioning it again.
In general, we're using
У Вас недостаточно прав для просмотра ссылок.
Вход или Регистрация
rather than raw CSS. If you're not familiar with LESS, I recommend checking out the functionality listed on their site to get a better idea of how it works. You'll find it's very similar to CSS, so there isn't a huge learning curve. It provides functionality like various manipulation functions (such as darkening colors) and support for nesting selectors. Significantly, it also provides "mixins" which allow us to easily create functions to encapsulate common CSS patterns.
(Yes, we considered SCSS on multiple occasions. It wasn't a fit for our needs. We won't be changing from LESS.)
Generally speaking, our CSS is structured along the lines of BEM: block, element, modifier. This means our classes are organized along the lines of .block, .block-row, .block-row--minor. If you explore the HTML you see here, it should be much clearer.
Unified template system, templates in the file system
There is now only one template system to power public-facing, admin and email templates. They are separated by type, but otherwise they are identical. This means:
The same template syntax is available everywhere (though you wouldn't want to use most complex features in email templates).
You can include a template of a different type from the current template by prefixing the type (public:thread_view).
The same editing system is used for each type of template, so functionality is shared (includes history, merging and template modifications, for example).
This means that email templates are now customizable. The email will be build using the site's default style. Email components now come from specific parts within the rendered template base on tags like <mail:subject>. We also automatically inline any CSS in emails to improve display in various clients.
Finally, all templates are now always served from the file system. This means that they will benefit from opcode caching.
Extended template syntax
While the template syntax is fairly similar to XF1, XF2's functionality is expanded. Let's look at an example from a user's profile:
You can call (some) methods on entities in templates. They must have a whitelisted prefix (that implies the method does not change data).
{$profilePosts|first} is using a filter to pull out just the first value from the collection/array. Filters are roughly another syntax around manipulating the first parameter. They can be chained and take arguments though: {$value|replace('from', 'to')|to_upper}
Conditions can be "is" and "is not". These are known as tests. Currently only empty is exposed but this may be expanded. These tests can wrap complex logic in an easy to understand way. (The "empty" test is true if the value is an empty string, false, null or empty array; or if the value is a countable object with a count of 0; or if the value is an object with __toString that evaluates to an empty string.)
Complex expressions are triggered with {{ ... }}. You can see calls to templater functions here (phrase), the use of the ternary operator, and the creation of name-value pairs like in JavaScript with curly braces. Other operators (such as math and concatenation) are available and non-string values such as true, false and null can be represented as such.
The big one thing here are the calls to macros.
Macros are similar to template includes, but they take particular arguments. It is much easier to see what options are available for a given macro and the macro variables don't pollute the scope of the calling template. The profile post macro is defined as:
It defines all of the available arguments, which become available within the body of the macro as {$profilePost}, {$showTargetUser}, {$allowInlineMod}. The value of the macro arguments is their default value if not provided; the "!" indicates that it's a required argument and will error if not provided.
Again, there's a lot more to this can than be covered in this overview.
New routing system
In XF1, custom PHP code had to be written to match and generate most URLs. In XF2, we use a custom syntax to allow us to do both automatically in most cases.
Threads are a simple example. The "threads" route prefix is entered and the following is given as the "route format":
PHP:
:int<thread_id,title>/:page
(Because of the route prefix, this is effectively matching threads/:int<thread_id,title>/:page.)
The :int<thread_id,title> indicates that it's an integer-based parameter. For building an outgoing link, we pull the integer from the thread_id key of the data passed in. If a title key is passed into the data, it will be "slugified" and prepended to the integer ID like you see in the URL to this page. For matching an incoming URL, this gets turned into a regular expression that matches the integer parameter format.
:page is a shortcut for generating the page-123 part of a link. In this case, it looks for the page in the link parameters. If found, it's put in the URL and then removed from the params. For incoming parsing, if matched (it can be empty), it will add the page number to the parameters passed to a controller.
When a link is built in template, it's done through a call like this: {{ link('members/following', $user, {'page': 3}) }} I'm using this particular example because of the "following" part. Normally, this will be the action; the link will be built with the basic "members" match. However, if there's a route that matches the prefix "members" and the "sub-name" following, it will be used instead. This is true here, so it builds a link like the following:
PHP:
members/:int<user_id,username>/following/:page
For incoming route matching, this route will be tested before the basic members route; if it matches, it will be used.
This sub-name system allows behavior changes (such as moving where the page param would normally go in this example) or sub-grouping (such as used in things like the resource manager and add-ons).
As with the other systems discussed today, there are a lot more options available to the routing system.
Content type management
Content type handlers are now managed through the control panel and will be included as part of add-ons. No more manual management.
To account for this, content types themselves are not attached to add-ons. Only the specific handlers. This avoids certain bugs and issues that happened in XF1.
Most content type handlers are fairly similar to what you'd find in XF1. For example, attachment_handler_class would map to XF\Attachment\Post for posts. There are some new ones though, including:
entity, which maps a content type to an entity (and therefore to a finder and repository)
phrase, which maps a content type to a phrase name (singular version)
phrase_plural, which maps a content type to a phrase name (plural version)
...and other handler class systems which may not have existed in XF1
This is far from everything. There's too much to cover in one day, s we'll be back later with more details.
It's that time again! Let's have a look at what we've been working on over the last two weeks. There haven't been a lot of changes to areas that you can see here, but there have been some significant changes.
Some of the more interesting/visible bits:
Re-add the "your content" link to the visitor menu. We may try to support a nested menu to allow this to be filtered down to specific content types more easily.
When opening the page nav "..." menu, the page number will now default to the first overflow value and, if possible, the entire text will be selected to make typing a new value easier.
When you see your avatar on your profile or in the visitor menu, there's now a link that appears (on hover) to allow you to quickly change it.
Add support for sharing to WhatsApp (only shown on small touch screens).
Re-add image expansion for images that have been shrunk below their natural width. They will now trigger the lightbox (which allows zooming) when the icon shown on an image is clicked.
Fonts are now more consistently based on the OS system font. (For example, MacOS will now use San Francisco.)
Lots of ACP styling improvements, though you wouldn't have seen it before. It's now fully responsive.
Do some ACP navigation reorganization. There are changes, but it's not drastically different; you should still be able to find things easily enough.
New responsive data tables system that dynamically converts tables to a vertical, key-value styled system on small displays.
Added a system for tracking our CSS breakpoints in JS. This allows JS to know the current width and how that relates to a target width. This system also triggers an event when a resize changes the current breakpoint.
Re-add distinct styling of invisible users on the online members list.
Add a dynamic menu builder system, similar to the system used with off canvas menus. This allows us to dynamically build menus out of existing HTML. Use this for "internal" action bar links on messages on smaller displays to prevent the action bar from wrapping. Similarly, use this in the ACP to move data list links to a menu on smaller displays.
And the rest:
Small reordering of the account links (in the account pages and the menu).
Ensure that the tweaked is-selected blockLink styling (with a background color) applies to blockLinkSplitToggle's as well.
Make the "show ignored content" link much less pronounced.
Drop the menu-blockLink CSS and use blockLink directly instead as they are generally identical and blockLinks have more variants (blockLinkSplitToggle).
Support a input--numberNarrow variant for places where we really don't need much space for numbers.
Add "concealed" links to the visitor menu and member tooltip counts to appropriate locations.
Ensure that trophies are hidden properly when the option is toggled, including automatically hiding the most trophy points member stat.
Form row CSS was changed to opt-in to the extra label alignment padding for input-based rows. Add a label aligner variant for when the content contains buttons.
Only handle share links in a special way if they are using http/https links. Fixes issues with mailto sharing.
Include <link> tags for RSS feeds in a few places.
Adjust the widget position tags to support both sidebar and sidenav positions.
Give button--primary a min-width and simplify many button wordings to things like "save" and "delete".
Allow some button wordings to be inferred by the icon specified in the template.
Move most delete buttons from the submit row to a page action (upper right of the page).
Dropped the gradient on flash message as it was pretty much the only place we used one like that.
Tweak the button hover/active state method to increase saturation and slightly darken (rather than lightening).
Add support for a hideempty attribute on checkbox, radio and select rows. When enabled, if there are no available choices, the entire row will be hidden.
inputGroups now use flex box when available to avoid some issues with the previous table-based implementation.
Adjust the drag target for multi-quote to use a dedicated drag gadget (always shown).
By default, disable caching for member stats based on values that are displayed in member list items.
Don't clear the code overlay in the RTE when it closes (unless you insert the code). This prevents code being lost accidentally.
As usual, there's always a few other changes that aren't mentioned here.
Given that a fair amount of the focus was on ACP elements, I figure it's probably time to give a peak at that. So, here you go:
And with that, it's time to wish everyone a Merry Christmas and a Happy New Year!=)
Зачем ждать? Или ты бессмертный? :)
Я к тому, что время идет, не нужно ждать и откладывать, нужно брать и делать.
Тем более в случае с XenForo тут явно будет всё четко, переедешь с версии 1.5.*** на 2.0 и всё.
В итоге, когда появится 2.0 будешь начинать уже не с нуля, что-то уже будет за плечами.
В любом случае, дело конечно твоё, но я бы не ждал. Может разработчики еще на пару лет отложат релиз. И что? Ждать 2 года? :)
Len, не могу говорить за разработчиков и почему они приняли такое решение, но некоторыми движет набирающая популярность адаптация под различные типы устройств. И в этом плане вертикальное меню достаточно хорошее решение. Конечно можно и оспаривать это приводя примеры отличных горизонтальных адаптаций, но нам остаётся лишь гадать почему так. :)
Что нового для разработчиков в XenForo 2 (часть 2):
A couple of weeks ago, we went into some detail about what's new for developers in XenForo 2. (You can read that
У Вас недостаточно прав для просмотра ссылок.
Вход или Регистрация
). We promised we would be back for more and so here it is:
Add-ons
In XF1 there are very few conventions and little structure in terms of the naming of add-ons, where the files are stored and what those files are named. We are changing that in XF2 in quite a significant way.
First of all, there is now a specific directory for all add-ons. Each add-on will have its own directory inside this parent add-ons directory and each directory will be identified by the add-on ID of the add-on. For example, files for XF Resource Manager and Media Gallery will be in the addons/XFRM and addons/XFMG directories respectively where XFRM and XFMG are the new add-on IDs for these add-ons.
On a similar note, a common pattern among existing add-ons is for an add-on vendor to put all of their add-ons within a parent directory indicated by the vendor's name. For example, if you release add-ons under the "Acme Add-ons" brand, you may currently store your add-ons files in the library/AcmeAddOns/AddOnName directory. Your add-on IDs may even be something like AcmeAddOns_AddOnName. In XF2, we support add-on IDs having a vendor prefix as part of the add-on ID, and the vendor prefix is separated by a /. So, if an add-on ID contains a slash, e.g. AcmeAddOns/AddOnName we know the files for the add-on will be stored in addons/AcmeAddOns/AddOnName. As we now use PHP namespaces in XF2, the rest of the add-on ID should be compliant with that format, so add-on IDs should contain only a-z, A-Z, 0-9 and / characters, must not start or end with a /, and must not start with 0-9.
This convention is certainly less flexible, by design, but having a predictable format for such things represents a significant improvement. For example, all add-ons will now have nearly identical install instructions, manually removing add-on files will be a clearer process and it allows us to make the process of installing add-ons different, too.
With that in mind, the new process for installing or upgrading an add-on is to simply upload the contents of the upload directory to the add-onsdirectory on your server. After you have done that, you no longer have to fish out a specific XML file to trigger the install or upgrade - we already know where the required files for an add-on are.
To demonstrate, this is a good point to show you what the new add-on list looks like:
The add-on list is split into three separate lists of add-ons.
When an add-on directory contains a version of files newer than that of the version installed, we mark it as upgradeable and push it to the top of the page.
When an add-on exists on the file system but is not installed (not in the xf_addon table of the database), we pull out some information about the add-on and mark it as installable.
Finally, you will see a list of add-ons which are installed, though you will notice that one of those is disabled.
Of course in XF1 all of the data for an add-on is kept inside an XML file. In XF2, each add-on should provide an XML file (if applicable) for the various types of add-on data (phrases, templates, etc.) and this file is stored in the add-on directory and named data.xml. This file no longer stores information about the add-on title and version. Instead, every add-on should include a manually built addon.json file.
Here's a somewhat complete example from XFMG 2.0:
Код:
{
"legacy_addon_id": "XenGallery",
"title": "XenForo Media Gallery",
"description": "The XenForo Media Gallery is an add-on that allows you and your users to create galleries of images and videos in your forum, organized into admin-defined categories or user-created albums.",
"version_string": "2.0.0 Alpha",
"version_id": 902000010,
"dev": "XenForo Ltd.",
"dev_url": "XenForo.com",
"faq_url": "XenForo Media Gallery | XenForo",
"support_url": "Media Gallery Support",
"extra_urls": {},
"require": {
"XF": [2000010, "XenForo 2.0.0+"],
"php": ["5.4.0", "PHP 5.4.0+"]
},
"icon": "icon.png"
}
For the most part, this should be fairly self-explanatory based on what you can see in the above screenshot, so we won't go through everything. Though there are a couple of interesting things here.
We mentioned earlier that the add-on IDs for XFMG and XFRM have changed, and this add-on ID change is handled automatically during upgrade based on the legacy_addon_id key in the JSON file. If that add-on ID exists, it will be marked as upgradeable by this add-on.
extra_urls is an array of link text: url pairs and these will render below the add-on list item (e.g. after the developer, FAQ and support links) and allow you to display links to other relevant things related to the add-on (perhaps a bug reports link, a manual or whatever you like).
The require array is a standard way of blocking an add-on install or upgrade if the environment doesn't support the required dependencies. In this particular JSON file, we block install and upgrade if the current XF version ID is below 2000010 and we block if the current PHP version is below 5.4.0. It's also possible to require other add-ons to be installed first, and also block install if certain PHP extensions are missing or not enabled.
The icon path is relative to the add-on directory. In this example, our XF logo is picked up from addons/XFMG/icon.png.
Another thing missing from both the XML file and the JSON file is information about how to install/upgrade the add-on. In XF1 we specify a class and method for installing and uninstalling an add-on. In a perhaps somewhat predictable move, we have also made this somewhat more predictable in XF2 If you have any install/upgrade/uninstall operations that need to happen, you will add these to a file named Setup.php. There is a BaseSetup class that you can extend here which can, amongst other things, loop through install steps and upgrade versions automatically.
PHP:
public function installStep1()
{
// Do stuff here
}
Development output
Although somewhat still related to add-ons, this is significant enough to deserve its own section.
If you enable development mode in config.php, this will cause add-on related data to be output as files (usually JSON) to the file system. Files will be written out to a location in the format addons/<addon_id>/_output/<type>/<id>.json.
For example, this is the XFMG navigation tab entry, stored in addons/XFMG/_output/navigation/xfmg.json:
Outputting all associated development data in this way is perfect if you manage your projects using a version control system. It also provides a way to easily import new/updated development data when working across separate databases, without having to do a full add-on install/upgrade.
As well as outputting the files themselves, we also track the metadata for all file types which at the minimum will include a hash which represents the current content of each file but can also include information such as the version ID and string of the add-on when it was last changed.
Not all development output files are output in JSON format. For example, Phrases are output as .txt files but, perhaps more significantly, template files are output in .html, .less and .css format...
Template file watcher
As mentioned above, templates are written out to the filesystem using the development output system. The location for these files will be addons/<addon_id>/_output/<admin|email|public>/. As each template is rendered, we watch the file system to see if there are any changes by comparing the current hash of the content to the hash stored in the development output metadata. At this point, the template will be updated, imported or recompiled.
This essentially means that the ability to edit templates from the filesystem is available for add-on developers which is far more convenient than editing templates in the Admin CP.
Class extensions
In XF1, to extend certain classes, you have to go through the following process:
Create a listener class, and a method similar to:
PHP:
public static function extendThreadController($class, array &$extend)
{
$extend[] = 'My_Extended_Thread_Controller';
}
In the Admin CP create a new code event listener, pick an event, e.g. load_class or load_class_controller and point it at your class and method and provide an event hint.
This doesn't particularly seem too onerous, but with a lot of classes being extended it can ultimately represent a lot of boilerplate code - and where's the fun in that?
So, in XF2, we have simplified this into a system in the Admin CP we call "Class extensions". Let's take a look:
All you need to do then is create your extended class, and that's it! It is, however, important to bear in mind that wherever possible a code event listener should still be used to listen to more specific events if there is one available.
The app object, dependency injection and service location
XF2's application config and setup actions are now configured through a general app object. (This is similar to XF1's dependencies objects, though the app object is more encompassing.)
The most significant component of this object is the dependency injection container that it exposes (and the helper methods it provides to work with it). Without getting into too much detail, the main advantage of this container is that it wraps up the dependencies for creating various objects within XenForo (or loading specific types of data) so you don't need to worry about that; you'll always get the correctly setup object. (If you're not familiar with dependency injection or containers, have a look at the first 2 parts of this blog:
У Вас недостаточно прав для просмотра ссылок.
Вход или Регистрация
)
As an example, if you request the router for generating/analyzing public routes, the container will load the actual list of routes, setup the pre-processors, add the route filters (which wouldn't apply to the admin router) and apply some of the global link-building configuration such as URL romanization. This object is only created on demand, so you don't have the overhead of doing this until you need the object. To emphasize the point, even if we had no expectation that you wanted a particular type of object somewhere, you should be able to instantiate it quickly and correctly through the dependency injection container.
The specific app object that is instantiated varies based on the location being loaded; for example, public and admin pages have separate app objects which lead to different configurations and dependencies being setup. As an example, this changes the session configuration for each app so that admin sessions come from the DB while public sessions may come from a cache layer.
Generally speaking, one request will have one app object. While in an ideal world, you would be given the specific object/data that your code depends on. However, that's not always viable, particularly in add-ons. There are certain types of classes where we pass the app object in (such as services), but in a pinch, you can always access it via \XF::app() and then pull the specific elements out that you need. This approach would fit more of a service locator pattern.
In most cases, container entries are represented as closures that are cached when initialized. This isn't always the case they; the container can store raw data or uncached closures. It also provides factory-style instantiation tools.
If this sounds complex, it's mostly because we're staying very abstract in the explanation. In most cases, your interaction with the app object may be something like $app->db() to get the DB adapter out. You won't need to worry about what happens behind the scenes. However, if you are interested in manipulating the container, add-ons will be able to extend entries and change how they are instantiated. (This can even be done via config.php.)
Improved JS Framework
XF2 itself still uses jQuery as its primary JS framework but has removed its dependency on the aging and unsupported jQuery Tools. We have built our own framework around jQuery which makes instantiation and extension of JS easier.
We have two types of handler; a click handler and an element handler. Each handler type is an object which has similar behaviors and similar functions within that should be extended. Click handlers execute code once the element is clicked, and element handlers execute once the element is initialized. Let's take a look at a basic click handler:
Код:
XF.LinkClick = XF.Click.newHandler({
eventNameSpace: 'XFMyLinkClick',
options: {
alertText: 'Link has been clicked!'
},
init: function()
{
alert('Initialization. This fires on the first click.');
},
click: function(e)
{
e.preventDefault();
alert(this.options.alertText); // Alerts: 'Link has been clicked!'
}
});
XF.Click.register('mylink', 'XF.LinkClick');
To set up an element to call the above code when clicked, you simply add a data-xf-click attribute with a value of mylink (the identifier, the first argument of the register line).
We specify a unique event namespace which means that a number of events will be fired when the link is clicked. Specifically, they are before the handler is initialized, after the handler is initialized, before the click code executes and after the click code executes.
We have an options object. Options are passed to the handler automatically and allow an element to adjust its configuration based on data-Xattributes passed in. For example, when the above link is clicked, the alert "Link has been clicked!" will display. If we wanted this to alert something different, we can do that by adjusting the HTML as follows:
Element handlers work in a nearly identical way, but the init function for element handlers executes when the element is activated (usually on page load for the entire document, or on specific elements on demand).
It is also trivially possible to extend an existing handler. Let's take a look at a very basic element handler which when assigned to an element will change its text color to red, and underlined:
<div data-xf-init="red-text">This is black text.</div>
When this element initializes, the "This is black text" text will turn red and become underlined and an alert 'The text changed to red and underline...' will be displayed on the screen.
We might want to re-use or extend this code for a different element handler, and this is possible using XF.extend. This allows you to extend an existing handler with your own code. You can then change default options, or even override or extend entire functions with the added option of still calling the original function if desired. Let's see that in action:
Код:
XF.BlueText = XF.extend(XF.RedText, {
__backup: {
'alert': '_alert'
},
options: $.extend({}, XF.RedText.prototype.options, {
color: 'blue',
decoration: 'line-through'
}),
alert: function()
{
this._alert();
alert('...actually, it changed to blue and strike through!');
}
});
XF.Element.register('blue-text', 'XF.BlueText');
XF.Element.register('blue-text', 'XF.BlueText');
HTML:
<div data-xf-init="blue-text">This is black text.</div>
What we have done here is we have registered a new element handler called blue-text but the bulk of the behavior is coming from the red-texthandler.
Notice that the default options of the red-text handler are overridden so that the text is going to be blue and line-through rather than red and underline.
We've also used a __backup object to map the alert function in the original handler to a new name of _alert in the new handler. This is equivalent to extending a class and method in PHP and calling the parent:: method.
The end result of this blue-text handler is that the color of the text is changed to blue and underline (based on our extended options), the alert text from the original handler is displayed ('The text changed to red and underline...'), followed by the new alert text we've specified in the new handler ('...actually, it changed to blue and strike through!').
While this demonstrates a clear hierarchy style, we have also exposed a more dynamic method of extension. This would likely fall into the realm of "monkey patching".
Код:
XF.Element.extend('red-text', {
alert: function()
{
alert('Hey a different message');
}
});
This dynamically extends the XF.RedText instance in place and overrides the functionality of the alert function. All code that is using the red-textelement will automatically use the extended version. Further, multiple extensions can happen to the same element handler without causing name conflicts; in this regard, it's similar to the class extension system for PHP.
Additionally, this XF.Element.extend method can be called before or after the base element is registered. This is relevant when JS is loaded asynchronously. (Clearly, it needs to be called before the element is instantiated, but the JS framework won't do that until all pending asynchronous JS has loaded.)
Abstracted file system
Instead of writing to semi-hard coded paths, XF2's main file writes now write through an abstracted system. This makes it much simpler to write files out to locations other than a local disk. Commonly, this would involve writing the files to a cloud storage system such as AWS S3, but there are various other potential approaches.
Reading and writing is done through the file system object grabbed from the app ($app->fs()). Here's what a path might look like:
This would identify the internal-data as the "mount" and the rest would represent any subsequent path and file name. The specific adapter would represent these as is appropriate for their usage.
The available mounts are:
internal-data -- for data that is only accessible via PHP. By default, this is still the local internal_data directory.
data -- data that is accessible via PHP and directly via a browser.
code-cache -- this is specifically for generated PHP code that we then write out to files. By default this is within the internal_data directory. This is distinguished from regular internal data files because we will be including/requiring files via the specified path so that we can take advantage of opcode caching. You want accessing these to be fast.
temp -- just for temporary files. This always resolves to a local temporary directory. This isn't used commonly but it can help transition from a potentially remote file to a file that is known to be locally accessible (as some processes require local files).
Phrase grouping
When we dynamically load phrases in XF1, if the phrase is not globally cached there can be a query overhead in terms of retrieving that phrase. Although globally caching is an option, it's often unnecessary to have something cached globally as it may only be used on certain pages.
Our solution for this in XF2 is phrase groups.
A phrase group is indicated by a special prefix applied to a phrase name in the format of <group>.<phrase>. All phrases that belong to the same group are compiled and written out to a file on the file system for each language and loaded from there instead when needed.
Generic change logging system
In XF 1.3 we introduced a
У Вас недостаточно прав для просмотра ссылок.
Вход или Регистрация
which causes all changes made by any user to any other user (or themselves) to be logged. The system itself is still present in XF2 and hasn't changed much on the surface, but behind the scenes, it is now handled by an entirely generic system which developers can extend for logging other changes to other content types besides users.
The change logging system is very simple to implement for your content types, too. You just extend our AbstractHandler and configure the Entity/Entities to use the ChangeLoggable behavior and the bulk of the logging and formatting of the logs happen automatically.
You can either configure an entire Entity to be "change-loggable", which means all fields by default will have changes logged, or you can opt into the logging on specific fields you explicitly want to log.
CLI Framework
This feature doesn't actually exclusively benefit developers, it also benefits site admins too, especially those with larger sites.
XF2 includes a framework which allows CLI commands to be defined and allow you to run those commands from a shell/command line. From a non-development point of view, it means admins of larger sites can perform intensive tasks such as site upgrades without having to worry about various timeouts that can be problematic when running such things through the web interface.
Upgrading the site using the CLI is not actually something new and it is something we actively recommend when we detect an upgrade of a larger site, but the framework behind it is generic, so it opens the door eventually for more tasks to be run in that way. As an example, however, here's what you would type from a shell on your server to upgrade XF to a new version:
Код:
C:\xf> php cmd.php xf:upgrade
Current version: 1051170
Upgrade target: 2000010 (2.0.0 Alpha)
Are you sure you want to continue with the upgrade? [y/n]
In addition to upgrading, you can also install XF2 from scratch, and upgrade your tables to support utf8mb4.
In terms of developers, there are some development commands which you may find useful. Here's a few of them:
xf-dev:addon-create This will take the initial steps necessary to create an add-on, and write out the initial addon.json file. The command asks various questions such as add-on ID and title and uses the responses to build and write out the file.
xf-dev:addon-export <addon_id> Exports all of the add-on related data (phrases, templates, navigation etc.) to the [/ICODE]data.xml[/ICODE] file for the specified addon ID file.
xf-dev:import --addon <addon_id> Imports all of the add-on related data (phrases, templates, navigation etc.) from the development output files on the file system to the database for the specified add-on.
xf-dev:export --addon <addon_id> Exports all of the add-on related data (phrases, templates, navigation etc.) from the database out to files on the file system for the specified add-on.
In addition, it will be possible for developers to create their own commands for their own personal use or for distribution with add-ons.
Master/slave support in DB adapter
XF2 introduces master/slave replication via a new DB adapter which allows separate read and write connections to be made. By default, select statements will be sent to the read server and all other statements will be sent to the write server.
You can see a typical example of configuration below:
As well as inferring the correct connection to make from the type of query, you can also control the behavior by prefixing a query with a comment in the form of -- XFDB=modifier.
Where the modifier is one of:
fromWrite - forces a specific read query to come from the write server even if it normally wouldn't
forceAllWrite - forces this query and all subsequent queries to the write server
noForceAllWrite - if this query would normally force all subsequent queries to the write server, this option disables that (useful for a write you know won't be read back immediately or if it is, can tolerate lag)
General improvements
While we have called out a lot of new things in XF2, there are plenty of systems that have also changed under the hood but generally just represent improvements. These include:
General reduction of static method usage. We now operate on object instances (where possible) except for some basic utility functions. This allows easier customization.
Rebuilt permission compilation code which allows significantly more reuse for new content types that require permissions (such as resource or media gallery categories).
Rebuilt BB code parsing system. This now separates rules defining the available BB codes, the parser, and the rendering mechanisms more cleanly. Significantly, BB code "processing" systems -- such as auto-linking, adding mentions, or limiting the allowed tags -- have now been split out separate analyzer and filterer systems, independent from the core rendering framework.
Improved generic systems for prefixes and custom fields so that add-ons do not need to repeat as much boilerplate code to implement the basic functionality.
A generalized connected accounts system to make it easier to support logging in/account creation through external services. We support Facebook, Google, Twitter, Microsoft, GitHub and LinkedIn out of the box, but we now include a powerful OAuth client library that includes Amazon, Dropbox, Instagram, Reddit, Spotify and others so add-ons should be able to add more connected account providers easily.
I think that sums of the most significant changes to the core of the XF2 code. Inevitably, there will be thousands of other changes that we haven't covered here and those will become more apparent once you start working with the code. We're still working very hard and marching towards the developer preview release.
And with that, we wish everyone a very happy new year!