graham-campbell/cms-core
Core package for Graham Campbell's Laravel-based CMS, providing the foundational services, base classes, and shared utilities that power the CMS and its companion packages.
Before you type composer require, set the expectation: this package declares "Support only laravel 4.1", sits at v0.2.0-alpha, and lives under a GrahamDeprecated namespace. The only defensible day-to-day use is inside an existing Laravel 4.1 / Bootstrap CMS-derived application, or as read-only reference material while you reimplement the same primitives on a supported Laravel version. Everything below assumes the former.
Pin it exactly. No caret, no ~, no dev-master:
{
"require": {
"graham-campbell/cms-core": "0.2.0-alpha"
}
}
Run composer update --dry-run graham-campbell/cms-core in a throwaway branch first and read the require/conflict block against your PHP and Laravel constraints. Then composer dump-autoload and confirm the namespace landed in vendor/composer/autoload_psr4.php — PSR-4 autoloading is the headline change in this release, so a stale classmap-era install is a real failure mode.
Where to look first, in order:
composer.json — resolve the actual root namespace (autoload.psr-4). In the Bootstrap CMS lineage this has historically been Cms\Core; confirm rather than assume, because every class reference you write depends on it.src/ for the service provider — the single most informative file. It tells you what gets bound into the container, which config keys are merged, which facades are aliased, and which models/observers are registered.config/ and any migrations — check whether schema is shipped, and whether it is safe to extend.Wire it up (Laravel 4.1 style). Add the provider to app/config/app.php:
'providers' => array(
// ...
'Cms\Core\CoreServiceProvider',
),
Publish config if the package offers it, then read the published file before editing anything:
php artisan config:publish graham-campbell/cms-core
Run package migrations only against a disposable/local database first:
php artisan migrate --package="graham-campbell/cms-core"
First use case: define one concrete content type — say a Page — using the package's content trait, expose it through a route, render it, and then deliberately break the slug and change the publish state. That single loop exercises the three things the package actually gives you (shared model behaviour, publishing scope, slug resolution) and tells you within an hour whether the remaining surface is worth wiring.
Content types via the trait. The package's core value is a shared model trait so every content type behaves identically. Build your domain models on top of it rather than extending package models directly:
class Article extends Eloquent
{
use Cms\Core\Traits\Sluggable; // confirm exact names in src/
use Cms\Core\Traits\Publishable;
use Cms\Core\Traits\Metadata;
protected $table = 'articles';
protected $fillable = array('title', 'slug', 'body', 'published_at');
}
Keep your own model as the boundary. If the trait method signatures shift in a later alpha — and they may — you change one file, not forty call sites.
Publishing as a query scope, not a column check. The pattern that pays off is treating publish state as an ambient scope on the read path: a published() scope that filters on status and published_at <= now(), applied by default in your repository, with an explicit withUnpublished() escape hatch for admin. Do not scatter where('published', 1) through controllers; you will miss one and leak a draft.
Slugs belong to a resolver, not a route. Laravel 4.1 has no route-model binding by default. Wrap slug lookup in a small resolver/repository so the "find by slug, 404 if missing or unpublished" decision lives in one place:
Route::get('{slug}', function ($slug) {
$page = App::make('ContentRepository')->findPublishedBySlug($slug);
if (!$page) App::abort(404);
return View::make('page')->with('page', $page);
});
This is also your future migration seam — swap ContentRepository for an implementation on a supported Laravel version and the route does not change.
Menus as a resolved tree, cached by key. Build the menu tree once (parent/child ordering, visibility filtering), cache the rendered structure under a versioned key, and invalidate on any menu model save/delete via Eloquent model events. Never let a view walk the menu table directly — that is how you end up with N+1 queries and a menu that disagrees with itself between requests.
Redirects with real semantics. A flat path→path table is the naive version and it will bite you. If you implement or extend this, budget for: status-code selection (301 vs 302), loop detection (resolve chains with a depth cap), collision/ordering rules when two rules match, and 404 fallthrough so an unmatched request still reaches your normal router. In 4.1 the natural hook is App::missing() or a route filter — pick one and be consistent, because two 404 paths that behave differently is a support ticket generator.
Cache invalidation wired to model events. Register saved and deleted listeners that bump the relevant cache key. Cache keys should be namespaced and versioned (cms.content.{type}.{id}.v{n}) so you can invalidate an entire content class by incrementing the version rather than enumerating keys — Laravel 4.1's cache drivers do not universally support tagging.
Provider and facade wiring. Bind your own implementations against the package's contracts in your app's service provider, after the package provider is registered. That gives you a single place to swap behaviour and a single place to look when something is silently resolved to the wrong class.
Isolate every touchpoint. No controller, view, or job should reference a Cms\Core\* class directly. Everything goes through your own content repository, menu service, and redirect resolver interfaces. The package is alpha with no upgrade path; the exit must be a contained change, not a rewrite.
config:cache will make your config edits appear to do nothing. This is the classic one. Cached config freezes package config values; edit the file, see no change, lose an hour. Flush the cache after any config change, and check whether the package's config is merged at register-time or publish-time.0.2.0-alpha plus an archived repo means breaking changes are plausible and possibly permanent. Pin the exact version in composer.json and commit composer.lock; do not let CI resolve fresh.Auth::attempt, guard drivers, or Authenticatable contracts does not apply. Read the 4.1 docs specifically.composer operation, if classes are "not found" that clearly exist, run composer dump-autoload. PSR-4 was new to this release and stale classmaps are the usual culprit.hello-world-2) and enforce uniqueness at the database level as well as the application level.app/config/app.php before registering them; a collision fails quietly or, worse, shadows an alias your app already relies on.How can I help you explore Laravel packages today?