Package setup guide
August 23, 2025 · AI Conversations
/index.php/google-merchant-feed/feed-09713944
give page not found after shift routes to config
Do this:
1) Create `packages/shopping_feed_manager/src/RouteList.php`
<?php
namespace ConcretePackageShoppingFeedManager;
use ConcreteCoreRoutingRouteListInterface;
use ConcreteCoreRoutingRouter;
class RouteList implements RouteListInterface
{
public function loadRoutes($router)
{
// Group under /shopping_feed for your API
$router->buildGroup()
->setPrefix('/shopping_feed')
->routes(function (Router $group) {
$group->get('/api/feeds',
'Concrete\Package\ShoppingFeedManager\Api\FeedApi::view');
$group->post('/api/delete/{id}',
'Concrete\Package\ShoppingFeedManager\Api\FeedApi::delete');
$group->post('/api/clone/{id}',
'Concrete\Package\ShoppingFeedManager\Api\FeedApi::cloneFeed');
});
// Public feed endpoint (no prefix)
$router->get('/google-merchant-feed/{feedSlug}',
'Concrete\Package\ShoppingFeedManager\Controller\MerchantFeed::generateXml');
}
}
2) Call it from your package controller `on_start()`:
use ConcretePackageShoppingFeedManagerRouteList;
public function on_start()
{
// ... your existing asset registration etc.
$router = $this->app->make('router');
(new RouteList())->loadRoutes($router);
}
3) Delete the `config/routes.php` you added (it won’t be picked up for packages). Clear cache: Dashboard → System & Settings → Optimization → **Clear Cache**.
Now `/index.php/google-merchant-feed/feed-09713944` should resolve again (with or without `index.php`, depending on pretty URLs).
Docs for this exact pattern (“Including Routes in Packages”) are here. citeturn1view0
If it still 404s, double‑check:
– The controller class `ConcretePackageShoppingFeedManagerControllerMerchantFeed` exists and `generateXml($feedSlug)` is **public** and returns a Response.
– Your package’s `composer.json` (or `$pkgAutoloaderRegistries` for older setups) autoloads `src/`. citeturn1view0
Want me to paste a minimal `MerchantFeed` controller stub that returns a test string so you can verify the route?
but when delete/clone
"errors": [
"Class "Concrete\Package\ShoppingFeedManager\Api\FeedApi" does not exist"
],
Do these two things:
# 1) Create the class in the right path + namespace
**File:** `packages/shopping_feed_manager/src/Api/FeedApi.php`
<?php
namespace ConcretePackageShoppingFeedManagerApi;
use ConcreteCoreHttpResponseFactoryInterface;
use ConcreteCoreSupportFacadeApplication;
use ConcreteCoreValidationCSRFToken;
class FeedApi
{
public function view()
{
$app = Application::getFacadeApplication();
return $app->make(ResponseFactoryInterface::class)->json(['ok' => true]);
}
public function delete($id)
{
$app = Application::getFacadeApplication();
$token = $app->make(Token::class);
if (!$token->validate('shopping_feed_delete')) {
return $app->make(ResponseFactoryInterface::class)->forbidden($token->getErrorMessage());
}
// TODO: delete logic...
return $app->make(ResponseFactoryInterface::class)->json(['deleted' => (int) $id]);
}
public function cloneFeed($id)
{
$app = Application::getFacadeApplication();
$token = $app->make(Token::class);
if (!$token->validate('shopping_feed_clone')) {
return $app->make(ResponseFactoryInterface::class)->forbidden($token->getErrorMessage());
}
// TODO: clone logic...
return $app->make(ResponseFactoryInterface::class)->json(['cloned' => (int) $id]);
}
}
# 2) Ensure PSR‑4 autoloading is set up
**Option A (recommended): `composer.json` in the package root**
{
"name": "your-vendor/shopping-feed-manager",
"type": "concrete5-package",
"require": {
"php": ">=7.4",
"concrete5/core": ">=9.0"
},
"autoload": {
"psr-4": {
"Concrete\Package\ShoppingFeedManager\": "src/"
}
},
"extra": { "concrete5-package": { "handle": "shopping_feed_manager" } }
}
Then run in `packages/shopping_feed_manager/`:
composer dump-autoload
**Option B (no Composer): declare autoload registries in your package controller**
Add this method to your `packages/shopping_feed_manager/controller.php`:
public function getPackageAutoloaderRegistries()
{
// Map the namespace root to /src
return [
'src' => [
'Concrete\Package\ShoppingFeedManager' => 'Concrete\Package\ShoppingFeedManager'
],
];
}
Finally: **Clear cache** (Dashboard → System & Settings → Optimization → Clear Cache) and retry the delete/clone calls.
If you want, I can drop this `FeedApi.php` into your canvas and add the `getPackageAutoloaderRegistries()` snippet to your controller so you can copy-paste directly.
{
"error": true,
"errors": [
"Class "Concrete\Package\ShoppingFeedManager\Api\FeedApi" does not exist"
],
evend create above
## 1) Ensure the file + namespace are exactly right
**Path:**
`packages/shopping_feed_manager/src/Api/FeedApi.php`
**Top of file must be:**
<?php
namespace ConcretePackageShoppingFeedManagerApi;
use ConcreteCoreHttpResponseFactoryInterface;
use ConcreteCoreSupportFacadeApplication;
use ConcreteCoreValidationCSRFToken;
class FeedApi
{
public function view() { /* ... */ }
public function delete($id) { /* ... */ }
public function cloneFeed($id) { /* ... */ }
}
## 2) Add the (core) package autoloader in your package controller
If you’re **not** using Composer inside the package (i.e., no `vendor/autoload.php` present or you didn’t run `composer dump-autoload`), add this **property** to `packages/shopping_feed_manager/controller.php`:
class Controller extends Package
{
protected $pkgHandle = 'shopping_feed_manager';
protected $appVersionRequired = '9.0.0';
protected $pkgVersion = '1.0.0';
// 👇 This tells Concrete to PSR-4 autoload everything under /src
protected $pkgAutoloaderRegistries = [
'src' => [
'Concrete\Package\ShoppingFeedManager' => 'Concrete\Package\ShoppingFeedManager',
],
];
// ...
}
> Note: this is a **property**, not a method.
If you **are** using Composer in the package, make sure you:
– Have `composer.json` with:
"autoload": { "psr-4": { "Concrete\Package\ShoppingFeedManager\": "src/" } }
– Ran `composer dump-autoload` in `packages/shopping_feed_manager/`
– (Optional) keep your existing `on_start()` `require __DIR__.'/vendor/autoload.php';`
You can use **either** Composer autoload **or** the `$pkgAutoloaderRegistries` property. Using both is fine too.
## 3) Make the route callback a plain string (remove array callable)
In your RouteList (the one currently open in canvas), change the API routes to string targets (Concrete will instantiate the class for you once autoloading works):
$router->buildGroup()
->setPrefix('/shopping_feed')
->routes(function (ConcreteCoreRoutingRouter $group) {
$group->get('/api/feeds',
'Concrete\Package\ShoppingFeedManager\Api\FeedApi::view');
$group->post('/api/delete/{id}',
'Concrete\Package\ShoppingFeedManager\Api\FeedApi::delete');
$group->post('/api/clone/{id}',
'Concrete\Package\ShoppingFeedManager\Api\FeedApi::cloneFeed');
});
// Public route:
$router->get('/google-merchant-feed/{feedSlug}',
'Concrete\Package\ShoppingFeedManager\Controller\MerchantFeed::generateXml');
Array callables are fine, but using the string form avoids any oddities while you’re debugging autoloading.
—
## Finally: Clear caches
Dashboard → System & Settings → Optimization → **Clear Cache**
(Or delete `application/files/cache` contents if you’re in dev and know what you’re doing.)
—
### Quick sanity check route (optional)
Add this temporary route to your RouteList to confirm autoloading:
$router->get('/_debug/feedapi-exists', function() {
return SymfonyComponentHttpFoundationResponse::create(
class_exists(ConcretePackageShoppingFeedManagerApiFeedApi::class) ? 'YES' : 'NO'
);
});
Hit `/index.php/_debug/feedapi-exists`. If it says **YES**, autoloading is fixed—your delete/clone endpoints will work.
If you still see the error after these changes, send me your current `controller.php` header (top ~40 lines) and the exact path + first 5 lines of `src/Api/FeedApi.php`—I’ll spot the mismatch fast.
Try hitting `/index.php/_debug/feedapi-exists` in your browser—if it prints YES, your API class is loading fine and delete/clone should work.
Do you want me to also add the `$pkgAutoloaderRegistries` property into your package controller so you don’t rely on Composer for autoloading?