PHPackages                             grithin/control-path - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. grithin/control-path

ActiveLibrary

grithin/control-path
====================

Load a series of control files based on path

0.2(4y ago)04MITPHPPHP &gt;=5.3.0

Since Jul 21Pushed 4y ago1 watchersCompare

[ Source](https://github.com/grithin/php-control-path)[ Packagist](https://packagist.org/packages/grithin/control-path)[ Docs](https://github.com/grithin/php-file-include)[ RSS](/packages/grithin-control-path/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (4)Versions (3)Used By (0)

ControlPath
===========

[](#controlpath)

Load control files in path, using section controllers if they are found

Why
---

[](#why)

The router I previously built would load control files parallel to a requested url path. It included section control files that acted like frontware, and it provided an intuitive location for control files. But, it had some downsides:

- variable injection was limited to a preset array
- could not bundle multiple pages into a single controller class which might have utility methods for the pages and a \_\_construct

I also considered that standard Controller class paradigm had [downsides](#controller-class-downsides).

So, I resolved to have the best of both worlds and made this tool.

What
----

[](#what)

ControlPath loads [section](#section-control) and [page controls](#page-control). The returns from the controls are collected, and they form the array that is returned from the `load` function.

```
use \Grithin\ServiceLocator;
$sl = new ServiceLocator();
$cp = new ControlPath(__DIR__.'/test_files/app1/', $sl->injector_get());
$returns = $cp->load('/section1/page');
```

How to interpret these returns is up to the application. For instance, the controls could return a response object, or a string which something else turns into a response object.

Let's look at an example.

With a path of `/section1/page`, the steps a flow goes through `while($Flow->next() !== false)` are:

1. .
    - load `/Controller.php`
    - `$Controller = new Controller()`
    - `$Controller->_always()`
2. .
    - load `/section1/Controller.php`
    - `$Controller = new section1\Controller()`
    - `$Controller->_always()`
3. .
    - load `page.php`

Section Control
---------------

[](#section-control)

section1\\Controller.php

```
namespace App\Control\section1;
class Controller{
	# always called, but return is not used
	public function __construct(){

	}
	# always called, return is used
	public function _always(){

	}

}
```

Page Control
------------

[](#page-control)

There are four variations that a page control can take:

### Section Class Method

[](#section-class-method)

In Controller, within the section Controller.php, a method can correspond to the page name `function page1(){}`

### Page Class

[](#page-class)

Parellels section controller class, but is named for the page. `/page1` -&gt; `class page1{}`

### Closure

[](#closure)

Can return a closure. The closure will have parameters injected.

### Page Logic

[](#page-logic)

The page itself can just do stuff. It can still return something - like the output of a template.

Injection
---------

[](#injection)

The methods, closures, and \_\_constructs have normal dependency injection. Additionally, some parameters are injected based on the name of the parameter. The named injections are based on the `inject` option. Two variables are always injected:

- `Flow` the current Flow instance
- `control` and ArrayObject used for sharing data between controls
- `ControlPath` the current ControlPath instance

You can have these injected just by name:

```
# within a method/constructor
function __construct($ControlPath){}
# within a closure
return function($ControlPath){}
```

Sharing
-------

[](#sharing)

It is intended that section controls and page control may want to share data. To enable this, the `$share` variable, which is an ArrayObject, is injected. This can also be used to capture methods within a Controller class.

Controller.php

```
class Controller{
	function __construct($share){
		$share->do_x = function(){
			$this->do_x();
		};
	}
	protected function do_x(){
		echo 'bob';
	}
}
```

page.php

```
($share->do_x)();
```

Whereas, normally, a section Controller would have protected methods that it shares amongst the page methods within the class, if a section controller wanted to provide access to those methods to either page controls that weren't methods, or page controls that were deeper, it would have to do something like the above.

If necessary, the previous Controller classes can be access with `$Flow->controllers`. This allows accessing public data (object properties) on the Controller that might be useful. However, owing to the fact that public methods with the Controller are accessible via path, only pages (and builtins) should be public methods, and utility methods should be protected.

Stopping Control Flow
---------------------

[](#stopping-control-flow)

There are a few ways to stop control flow:

1. return false from a control
2. call $Flow-&gt;stop()
3. throw exception

Where #2 occurs can be in multiple places. Since `$Flow` is injected:

- within a section \_\_construct
- within a section \_always
- within a page control file
- within a return\_handler

`Controller.php`

```
class Controller{
	public function __construct($Flow){
		$Flow->stop();
	}
}
```

Return handler

```
$return_handler = function($return, $flow){
	# there can be no "bill"'s, only bob's
	if($return == 'bill'){
		$flow->stop();
	}
}
$ControlPath->load('/section1/page', ['return_handler'=>$return_handler]);
```

Conforming Path Tokens
----------------------

[](#conforming-path-tokens)

Since path characters can diverge from what is allowable as a namespace or class, the token parts of the path are converted using ControlPath::token\_to\_class.

'.-' are used as separated, non-conforming characters are removed, and the token is turned into camel case:

`029bob.bill-sue1` =&gt; `bobBillSue1`

Exceptions
----------

[](#exceptions)

- `Grithin\ControlPath\NotFound` : when a page is not found, or when a path is directed to a built in
- `Grithin\IoC\ContainerException` : when injection can not resolve the service for a typed paramaeter
- `Grithin\IoC\MissingParam` : when injection can not resolve a param

Misc Design Notes
-----------------

[](#misc-design-notes)

### Class Files Vs Logic Files

[](#class-files-vs-logic-files)

Multiple `load`'s become a problem when there is a mixture of class files and logic files. You can not re-include a class file, but a logic file expects to be re-included. As such, when ControlPath discoveres a class file, it will not try to load it again on subsequent runs.

### ServiceLocator singleton ControlPath

[](#servicelocator-singleton-controlpath)

There is a question of whether the ControlPath should clone the ServiceLocator or not. ControlPath adds itself to the SL. By having a cloned SL, it ensures if a control file calls SL-&gt;get(ControlPath), this will always points to the instance loading the control file. However, this creates a mismatch of expectation:

- A control file might add a middleware, and it might add a service for that middleware that will be injected, but because the ServiceLocator the control file is using is a clone, that service will not be registered in the ServiceLocator that loads the middleware.

In the end, I decided not to clone ServiceLocater. ControlPath can be injected by name instead of by type, which means control files only need to name the param `$ControlPath` to ensure they are getting the current instance.

### Why is Route Middleware Deficient?

[](#why-is-route-middleware-deficient)

Generally, there are complexities that apply to specific paths that would make the abstraction into front/middle ware both unnecessarily disconnected and would result in unnecessarily complex general middleware or route specific frone/middle ware. One off frontwares, indicated by path, fit perfectly for these path specific complexities, to be loaded as section controls. And example might be some series of complex permission checks which would not fit in a Auth middleware configuration parameter.

### Controller Class Downsides

[](#controller-class-downsides)

- Giant methods or manied methods lead to giant, unmanageable controller files.
    - large control pages would have to fit inside a Controller class, making the class large
    - buried pages
    - page methods in no definite order
- Page syntax error causes section failure

### Why Not Controller As Api?

[](#why-not-controller-as-api)

APIs + response handlers are good for handling standards. But, a controller can output anything, and sometimes a large variance from a standard is necessary.

Plans
-----

[](#plans)

Setting this up into a framework takes some effort.

Having something like laravel with route methods `(Model $model)` would mean injecting `Model(Request $request)`, which would mean Request is a service or is otherwise injectable at that point.

Fitting ControlPath in with other wares, where there is the potential section controls will add more wares is also complicated:

[![Wares](about/wares.png?raw=true "Wares")](about/wares.png?raw=true)

###  Health Score

18

—

LowBetter than 8% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity3

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

1761d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4543facab3c88d548b98d8472b532faf7bcd00555cc47c0dc808d935f8d3d73f?d=identicon)[grithin](/maintainers/grithin)

---

Top Contributors

[![grithin](https://avatars.githubusercontent.com/u/7241358?v=4)](https://github.com/grithin "grithin (2 commits)")

---

Tags

file include

### Embed Badge

![Health badge](/badges/grithin-control-path/health.svg)

```
[![Health](https://phpackages.com/badges/grithin-control-path/health.svg)](https://phpackages.com/packages/grithin-control-path)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
