Modern, Stable APIs for Your Nextcloud Application
Home
Projects
Contact
Archives
Search
Photography
Back<br>Modern, Stable APIs for Your Nextcloud Application
Nextcloud provides a large public PHP interface to developers to use for<br>building their application. It is commonly called OCP. Some of big components<br>of OCP are the HTTP stack with<br>IRequest,<br>Response<br>and<br>Controllerto<br>handle requests, the<br>IQueryBuilder/IDBConnection<br>to query the database and many feature oriented components and utilities.<br>Outside of OCP, applications can also use a lot of private APIs and 3rd party<br>libraries included by Nextcloud, but these don’t have the same stability<br>guarantees as the official public interface.<br>With Nextcloud Hub 26 Summer (35.0.0), there are 3 big changes coming to the OCP<br>API. The first two are that we are now providing a public API for the Symfony<br>Console component and<br>the Doctrine database schema abstraction.<br>The first one is required when wanting to extend the Nextcloud command line tool<br>occ and<br>the second one is used to create database migrations. Both are very important<br>but would break every app every time we would update the dependencies, which is<br>forcing us to keep older versions of these dependencies (thankfully older<br>versions are still maintained).<br>Declaring Commands with #[AsCommand]<br>To replace the first one, we added a new family of PHP attributes and interfaces.<br>Instead of inheriting from OC\Core\Command\Base to implement a command, you can now<br>use a simple PHP invokable class and annotate it with the #[AsCommand] (link)<br>attribute as follows.<br>#[AsCommand(<br>name: 'app:create-user',<br>description: 'Creates a new user.',<br>help: 'This command allows you to create a user...',<br>usages: ['bob', 'alice --as-admin'],<br>)]<br>class CreateUserCommand {<br>public function __invoke(): ExitCode {<br>// ...<br>return ExitCode::Success;
Options and arguments for your commands can be defined declaratively in the<br>__invoke method parameters.<br>The parameter’s type and default value decide whether the argument or option is<br>required, repeatable, or a flag.<br>#[AsCommand(name: 'app:user:created')]<br>class CreateUserCommand {<br>public function __invoke(<br>#[Argument(description: "The username of the user")]<br>string $userId,<br>#[Option(description: "Force the creation")]<br>bool $force = false,<br>): ExitCode {<br>// ...<br>return ExitCode::Success;
Additionally it is possible to inject<br>IOutput<br>and<br>IInput<br>in the __invoke method parameters to be able to ask questions; print texts,<br>progress bars and tables.<br>This new API doesn’t replace the existing private API, which is still available, but<br>using it allows you to improve the coverage of the static analyser as stubs for these<br>commands are available in the nextcloud/ocp<br>package and this will prevent you from API breakage when using the Symfony Console API<br>directly.<br>Manipulating SQL Tables with OCP\DB\Schema<br>This is actually a small breaking change, but for the schema migration,<br>ISchemaWrapper<br>won’t return Doctrine\DBAL types anymore but instead our own wrapper in OCP\DB\Schema.<br>The wrapper has essentially the same API as the doctrine implementation<br>so the breaking change should be minimal. But there are a few cases which won’t work<br>anymore, for example:<br>Type hinting methods with a DBAL type where the type needs to be replaced<br>with the new OCP type or use a union type if you want to support multiple<br>versions.<br>Type::lookupName($column->getType()) -> $column->getType()->getName()<br>Fortunately all these issues should be found quite easily by installing your<br>application, which should be the case when running your unit tests. Grepping<br>for DBAL and running psalm/phpstan<br>should also help find the issues.<br>Introducing the Nextcloud ORM<br>The third big change is the addition of a simple ORM to Nextcloud: OCP\AppFramework\ORM.<br>This allows you to define an easy mapping between your database tables and PHP objects<br>by using PHP attributes. This can be used for simple tables.<br>php<br>use OCP\AppFramework\ORM\Attribute\Column;<br>use OCP\AppFramework\ORM\Attribute\Entity;<br>use OCP\AppFramework\ORM\Attribute\Id;<br>use OCP\DB\Schema\ColumnType;
#[Entity(name: 'twofactor_backupcodes')]<br>final class BackupCode {<br>#[Id]<br>#[Column(name: 'id', type: ColumnType::Integer, nullable: false)]<br>public ?int $id = null;
#[Column(name: 'user_id', type: ColumnType::String, length: 64, nullable: false)]<br>public string $userId;
#[Column(name: 'code', type: ColumnType::String, length: 128, nullable: false)]<br>public string $code;
#[Column(name: 'used', type: ColumnType::Smallint, nullable: false, default: 0)]<br>public int $used = 0;
Which can then be fetched by using a Repository which provides convenient utilities<br>to fetch, delete, update or insert entries in the database.<br>php<br>class BackupCodeRepository extends Repository {<br>public const string entityClass = BackupCode::class;
/**<br>* @return \Generator<br>*/<br>public function findByUser(IUser $user): \Generator {<br>return $this->findBy([<br>'userId' => $user->getUID(),<br>]);
public function...