PHP frontend for App Tutorial

Having been out of programming business for ~12 years I am trying to work my way up to NC development, starting with the AppTutorial. To start on better-known grounds, I am working on handling the frontend in plain PHP, without vue.

So, what would I do to access a Note object from index.php?

Clearly, I need a NoteController.
To get a NoteController, I need an IRequest and a NoteService.
To get a NoteService, I need a NoteMapper.
For the NoteMapper, I need an IDBConnection.

At least the request object and the db connection should be around but I can’t find access to them. Shouldn’t there be a global injector object or something?

Any help appreciated and please excuse for the dummy question.

Hello @birnbacs and welcome back to the mysterious world of software development!

I’m not entirely sure if can follow but it sounds like you want to create your own instance of your controller in order to invoke a method on it, but you are confused by how to actually get hold off all the things that it takes to build the tree of objects.

In Nextcloud we use the principle of inversion of control with dependency injection. Our docs cover some of this at Dependency injection — Nextcloud latest Developer Manual latest documentation but you might find it useful to watch/read some info about these concepts on the internet.

So basically you don’t create these instances yourself. You let Nextcloud do that.

What you are likely missing is the part of registering a route that maps a URL to a controller method. You do not need (or possible shouldn’t) need an old-school index.php file as entrypoint for your code. Let Nextcloud handle this for you.

Christoph,

thanks for your response and for the excellent tutorial app. I spent some time playing with it and I feel like I learned about quite a few key concepts for NC. Much needs to be understood but I feel I am catching up.

You are right, I am definitely missing out something between routes and dependency injection. Maybe you can help me make ends meet.

On the first end, I registered services for NoteMapper, NoteContainer and Note Service in class NotesApp\Application, following the example in the docs. I see how these definitions allow the container to accept a request for, say, a NoteController(), recursively scrape together the required arguments and return the object. Auto-wiring is neat stuff :slight_smile:

The second end appears to be method PageController->index(), which ultimately gets called upon access to the corresponding URL. The connection between URL and method is made in routes.php. I have not fully understood this bit but it works for now and I will investigate later.

Now, how do I connect these two bits and instantiate a NoteController inside the index method?

Bonus question: assuming the index() method returns a TemplateResponse, how would I access the NoteController from inside the template file?

1 Like

Very good summary, I think you are on an excellent track.

The index method is a method of the NoteController, right? You are already “there”.

You don’t. If you need any data in the template then you can assign it if I remember correctly. That makes it accessible through the $_['something'] array. That should be in the docs as well.

The index method is a method of the NoteController, right? You are already “there”.

Unfortunately not. The index method is a method of PageController. It returns the name of the page to display, which is also index but denotes the PHP file and not a method. I still cannot instantiate a NoteController object. :frowning:

I know it’s right in front of me but I can’t grab it

It finally dawned on me that I need to set up a route to the specific controller I need. The controller’s configured method - index() for example - will then be called and can provide the correct output using its own data access methods. The notestutorial app is using this:

public function index(): DataResponse { return new DataResponse($this->service->findAll()); }

New problems:
1.) I get a CSRF check failed-error
2.) what if I have two controllers that need to interact? I set up a route to a method of controller A and then do what to get hold of a controller B?

The first problem actually seems to be the second one as the error is thrown when I create a new DataResponse.

Oh, I missed the note about the two controllers. So the controllers are basically your entry points for HTTP requests. The business logic remains in the service. So if you also add the service to the constructor of the NotesController, you can use $this->service->findAll() in there as well. You don’t call one controller from the other.

As said that shouldn’t be necessary. If there is anything common between the two controllers then it’s likely to be some business logic that should be in the service layer.

Did you annotate your controller method with @NoCSRFRequired?

Thanks for the hint with the @noAdminRequired and the reminder that it’s not a controller I seek but a service. Anyways, I needed a way to instantiate one or several user defined classes to implement my business logic.

After much reading the docs and quite a bit of “Jugend Forscht” I finally got my answers. Here is what I learned:

  • you don’t normally register services in Application.php, it’s done automatically if your app extends the AppFramework
  • auto-wiring will figure out what is required for the class you call and how to get it
  • there is more to routes.php than meets the eye at first glance
  • note that underscore names for db columns get transposed into camelCase properties (e.g. note_id --> noteId)
  • instantiate the service you seek like this:
  $app = new \OCP\AppFramework\App('myapp');
  $container = $app->getContainer()
  $myservice = $container->query('OCA\CamelApp\Service\MyService');
1 Like

Oh, and make sure the docs you read match your server version.

1 Like

while not necessarily wrong, at least the first line is problematic. did you find this example in the docs?

you are totally right about the auto-wiring. I think the docs are still a tad outdated because there was a time where every service needed to be registered. luckily these days we need that rarely. so in that sense you can also have your service injected. then you don’t have to locate it like in the snippet. Have you seen Dependency injection — Nextcloud latest Developer Manual latest documentation?

Christoph,

I took the line from the docs, exactly from the page and section you referenced :smiley:

This is where I got the clue that the container’s get method requires a full path for the service. I’m still puzzled why this is necessary with all the magic around.

Re-reading your comments above I now realize what you meant: by passing all the required services into the constructor of the controller, each service is already part of the class and locating them (as in the snippet) becomes unnecessary. Phew, that took me a while!

Thanks for bearing with me for so long. I do appreciate your help in getting the first few stones right before I build a castle on top.

I hereby award you the dependency injection award :medal_sports:

But yes, the concept isn’t trivial and imperative thinking makes it hard to grasp but once the brain flips the switch and thinks about this the other way around it suddenly makes sense :slight_smile:

Sure, happy I could help :slight_smile: