PHP, framework - Class must be declared abstract - php

I'm introducing to Phalcon, a php framework, by following the tutorial: https://docs.phalconphp.com/en/latest/reference/tutorial-rest.html
I'm facing with this problem: I have this error in the code below
class Robots must be declared abstract or implement methods
'getConnectionService(), setForceExists() etc..'
<?php
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Message;
use Phalcon\Mvc\Model\Validator\Uniqueness;
use Phalcon\Mvc\Model\Validator\InclusionIn;
class Robots extends Model{
public function validation()
{
// Type must be: droid, mechanical or virtual
$this->validate(
new InclusionIn(
array(
"field" => "type",
"domain" => array(
"droid",
"mechanical",
"virtual"
)
)
)
);
// Robot name must be unique
$this->validate(
new Uniqueness(
array(
"field" => "name",
"message" => "The robot name must be unique"
)
)
);
// Year cannot be less than zero
if ($this->year < 0) {
$this->appendMessage(new Message("The year cannot be less than zero"));
}
// Check if any messages have been produced
if ($this->validationHasFailed() == true) {
return false;
}
}
}
?>
And even if I try to execute an HTTP request i get:
Cannot instantiate abstract class Robots
Any ideas?

You probably didn't set your database service properly. Make sure that:
The service name is db (I've experienced problems changing the default services names)
You have set the db service in your main $di (some might instantiate another DI container and isolate the database service there)
Always use $di->setShared(...) for registering global services
Please provide more information and will be glad to help.

Related

Queue::fake not work with Laravel5 codeception module

I write test for Laravel app with codeception and modules Laravel5, REST.
One of api test:
public function testEmailRegistration(ApiTester $I) {
...
// Not correct data
$I->sendPOST($route, [
'first_name' => (string)$this->faker->randomNumber(),
'password' => $this->faker->password(1, 7),
'email' => 'not_valid_email',
]);
$I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY);
// Correct data
\Illuminate\Support\Facades\Queue::fake();
$I->sendPOST($route, [
'first_name' => $firstName,
'password' => $password,
'email' => $email,
]);
\Illuminate\Support\Facades\Queue::assertPushed(\App\Jobs\SendEmail::class);
...
}
I send requests on incorrect and correct data and make some assertions. In addition I check, that job is present in queue.
After execute test I give error:
[Error] Call to undefined method Illuminate\Queue\SyncQueue::assertPushed()
After Queue:fake facade \Illuminate\Support\Facades\Queue must resolves to QueueFake, but in fact is still QueueManager, thus assertPushed function is undefined.
Execution of $I->sendPOST() reset call Queue::fake. It happened in laravel 5 module \Codeception\Lib\Connector\Laravel5, method doRequest.
protected function doRequest($request)
{
if (!$this->firstRequest) {
$this->initialize($request);
}
$this->firstRequest = false;
$this->applyBindings();
$this->applyContextualBindings();
$this->applyInstances();
$this->applyApplicationHandlers();
$request = Request::createFromBase($request);
$response = $this->kernel->handle($request);
$this->app->make('Illuminate\Contracts\Http\Kernel')->terminate($request, $response);
return $response;
}
Each call of doRequest except the first init app again and some configurations as Queue::fake are cleared.
One of decision is one request per test. Is there another variant to work Queue::fake when in test make more then one request?
I am not sure why the Laravel module does this but I found a work-around that allows you to use fakes:
public function someTest(ApiTester $I): void
{
// what the SomeFacade::fake method call does is basically create
// a fake object and swaps it for the original implementation in
// the app container, so the we're recreating that behavior here
// only this will be persisted even after the request is issued:
$notification_fake = new NotificationFake();
// `haveInstance` is a method from Laravel Codeception Module
// which sets an object in the app container for you:
$I->haveInstance(ChannelManager::class, $notification_fake);
// making the request
$I->sendPUT('some url', $some_payload);
// assertions
$I->canSeeResponseCodeIs(Response::HTTP_OK);
$notification_fake->assertSentToTimes($expected_user, MyNotification::class, 1);
}
note that this test method is only for illustrative purposes and it misses so of the details, hence the undefined variables and such.
Also note that I user the notification fake which get registered at Illuminate\Notifications\ChannelManager, unlike most fakes that you can register under their aliased name e.g. queue. So you have to check what is being instantiated and how to swap it on your own. You can either find this in respective service providers for each service. Most of the time it's lowercase name of the facade.

Class not found when creating a class with class name being a string

I need to create classes based on the parameter passed to a function. I do it this way:
public function index($source)
{
if(in_array($source, ModuleManager::getAllModules()))
{
$provider = new $source();
if($image)
{
return $provider->getAll(true);
}
else
{
return $provider->getAll(false);
}
}
}
Notice that on line 5 I'm trying to create an object of class $source which will definitely be available. I understand that the above code is actually an eval call. I'm using Laravel 5.2 and the above code returns:
FatalThrowableError in ProcReqController.php line 19:
Fatal error: Class 'Example' not found
In the above error Example can be any class that I made. Now if I hard code the value of $source then it works just fine.
What am I getting that error?
I believe what's happening is PHP gets confused when you try to instantiate a class whose class name is in a variable and it has to do with imports.
Solution 1
Set your $class variable to the fully qualified class name including the namespace and it should work.
In this way, new $class() should work even while including parenthesis.
Solution 2
After further testing, it seems when you instantiate a variable class, it always assumes global namespace.
With this in mind, you can use class_alias to alias each of your classes. In config/app.php, you can add each class to the aliases array.
'aliases' => [
....
'Example' => App\Example::class
]
The autoloader allows you to use classes without fully qualifying them... in the php interactive shell you'll have to manually include classes AND fully qualify them.
if you have a composer project, go to it's directory and do the following to load the Primal color classes:
set_include_path(getcwd().'/vendor/primal/color/lib/Primal/Color/');
include 'Color.php';
include 'Parser.php';
include 'RGBColor.php';
include 'HSVColor.php';
$hello = Primal\Color\Parser::parse('#666');
var_export($hello->toHSV());
/*
returns
Primal\Color\HSVColor::__set_state(array(
'hue' => 0,
'saturation' => 0,
'value' => 37.647058823529413,
'alpha' => 1,
))
*/
Remove the parentheses at the end of the instantiation call, I think.
Check out this php interactive shell session:
php > class Foo { };
php > $fooname = 'Foo';
php > $bar = new $fooname;
php > var_dump($bar);
object(Foo)#2 (0) {
}
src: https://stackoverflow.com/a/4578350/2694851

Laravel: Where to store global arrays data and constants?

I just started working with Laravel. I need to rewrite a whole system I made some years ago, using Laravel 4 as base framework. In my old system, I used to have a constant.php file with some constants declared, and a globals.php file which contained lots of array sets (for example, categories statuses, type of events, langs, etc.). By doing so, I could use something like
foreach ( $langs as $code => $domain ) {
// Some stuff
}
anywhere in my app.
My question is, how can I store that info in the so called "laravel way". I tried using some sort of object to store this info, setting this as a service and creating for it a facade:
app/libraries/Project/Constants.php
namespace PJ;
class Constants {
public static $langs = [
'es' => 'www.domain.es',
'en' => 'www.domain.us',
'uk' => 'www.domain.uk',
'br' => 'www.domain.br',
'it' => 'www.domain.it',
'de' => 'www.domain.de',
'fr' => 'www.domain.fr'
];
}
app/libraries/Project/ConstantsServiceProvider.php
namespace PJ;
use Illuminate\Support\ServiceProvider;
class ConstantsServiceProvider extends ServiceProvider {
public function register() {
$this->app->singleton('PJConstants', function() {
return new Constants;
});
}
}
app/libraries/Project/ConstantsFacade.php
namespace PJ;
use Illuminate\Support\Facades\Facade;
class ConstantsFacade extends Facade {
protected static function getFacadeAccessor() {
return 'PJConstants';
}
}
composer.json
"psr-4": {
"PJ\\": "app/libraries/Project"
},
and so I access that property as PJ\Constants::$langs.
This works, but I doubt it is the most efficient or correct way of doing it. I mean, is it the right way to "propagate" a variable by creating a whole Service Provider and facades and all such stuff? Or where should I put this data?
Thanks for any advice.
EDIT # 01
Data I want to pass to all controllers and views can be directly set in script, like in the example at the beginning of my post, but it can also be generated dynamically, from a database for example. This data could be a list of categories. I need them in all views to generate a navigation bar, but I also need them to define some routing patterns (like /category/subcategory/product), and also to parse some info in several controllers (Like get info from the category that holds X product).
My array is something like:
$categories = [
1 => ['name' => 'General', 'parent' => 0, 'description' => 'Lorem ipsum...'],
2 => ['name' => 'Nature', 'parent' => 0, 'description' => 'Lorem ipsum...'],
3 => ['name' => 'World', 'parent' => 0, 'description' => 'Lorem ipsum...'],
4 => ['name' => 'Animals', 'parent' => 2, 'description' => 'Lorem ipsum...']
]
Just as an example. Index is the id of the category, and the Value is info associated with the category.
I need this array, also, available in all Controllers and Views.
So, should I save it as a Config variable? How else could I store these data; what would be the best and semantically correct way?
For most constants used globally across the application, storing them in config files is sufficient. It is also pretty simple
Create a new file in the app/config directory. Let's call it constants.php
In there you have to return an array of config values.
return [
'langs' => [
'es' => 'www.domain.es',
'en' => 'www.domain.us'
// etc
]
];
And you can access them as follows
Config::get('constants.langs');
// or if you want a specific one
Config::get('constants.langs.en');
And you can set them as well
Config::set('foo.bar', 'test');
Note that the values you set will not persist. They are only available for the current request.
Update
The config is probably not the right place to store information generated from the database. You could just use an Eloquent Model like:
class Category extends Eloquent {
// db table 'categories' will be assumed
}
And query all categories
Category::all();
If the whole Model thing for some reason isn't working out you can start thinking about creating your own class and a facade. Or you could just create a class with all static variables and methods and then use it without the facade stuff.
For Constants
Create constants.php file in the config directory:-
define('YOUR_DEFINED_CONST', 'Your defined constant value!');
return [
'your-returned-const' => 'Your returned constant value!'
];
You can use them like:-
echo YOUR_DEFINED_CONST . '<br>';
echo config('constants.your-returned-const');
For Static Arrays
Create static_arrays.php file in the config directory:-
class StaticArray
{
public static $langs = [
'es' => 'www.domain.es',
'en' => 'www.domain.us',
'uk' => 'www.domain.uk',
'br' => 'www.domain.br',
'it' => 'www.domain.it',
'de' => 'www.domain.de',
'fr' => 'www.domain.fr'
];
}
You can use it like:-
echo StaticArray::$langs['en'];
Note: Laravel includes all config files automatically, so no need of manual include :)
Create common constants file in Laravel
app/constants.php
define('YOUR_CONSTANT_VAR', 'VALUE');
//EX
define('COLOR_TWO', 'red');
composer.json
add file location at autoload in composer.json
"autoload": {
"files": [
"app/constants.php"
]
}
Before this change can take effect, you must run the following command in Terminal to regenerate Laravel’s autoload files:
composer dump-autoload
For global constants in Laravel 5, I don't like calling Config for them. I define them in Route group like this:
// global contants for all requests
Route::group(['prefix' => ''], function() {
define('USER_ROLE_ADMIN','1');
define('USER_ROLE_ACCOUNT','2');
});
I think the best way is to use localization.
Create a new file messages.php in resources/lang/en (en because that is what is set in my config/app 'locale'=>'en')
return an array of all your values
return [
'welcome' => 'Welcome to our application'
];
to retrieve for laravel 5.3 and below
echo trans('messages.welcome');
or
echo Lang::get('messages.welcome');
for 5.4 use
echo __('messages.welcome')
laravel 5.0 localization
or
laravel 5.4 localization
Just to add to the above answer you will have to include the config class before you could start using it in Laravel 5.3
use Illuminate\Support\Facades\Config;
Atleast in Laravel 5.4, in your constructor you can create them;
public function __construct()
{
\Config::set('privileged', array('user1','user2');
\Config::set('SomeOtherConstant', 'my constant');
}
Then you can call them like this in your methods;
\Config::get('privileged');
Especially useful for static methods in the Model, etc...
Reference on Laracasts.com https://laracasts.com/discuss/channels/general-discussion/class-apphttpcontrollersconfig-not-found
Just put a file constants.php file into the config directory and define your constants in that file, that file will be auto loaded,
Tested in Laravel 6+
Create a constants class:
<?php
namespace App\Support;
class Constants {
/* UNITS */
public const UNIT_METRIC = 0;
public const UNIT_IMPERIAL = 1;
public const UNIT_DEFAULT = UNIT_METRIC;
}
Then use it in your model, controller, whatever:
<?php
namespace App\Models;
use App\Support\Constants;
class Model
{
public function units()
{
return Constants::UNIT_DEFAULT;
}
}

Routing, Navigation and State in MVC

I am attempting to refactor my app using the MVC paradigm.
My site displays charts. The URLs are of the form
app.com/category1/chart1
app.com/category1/chart2
app.com/category2/chart1
app.com/category2/chart2
I am using Apache Rewrite to route all requests to index.php, and so am doing my URL parsing in PHP.
I am working on the enduring task of adding an active class to my navigation links when a certain page is selected. Specifically, I have both category-level navigation, and chart-level sub-navigation. My question is, what is the best way to do this while staying in the spirit of MVC?
Before my refactoring, since the nav was getting relatively complicated, I decided to put it into an array:
$nav = array(
'25th_monitoring' => array(
'title' => '25th Monitoring',
'charts' => array(
'month_over_month' => array(
'default' => 'month_over_month?who=total&deal=loan&prev='.date('MY', strtotime('-1 month')).'&cur='.date('MY'),
'title' => 'Month over Month'),
'cdu_tracker' => array(
'default' => 'cdu_tracker',
'title' => 'CDU Tracker')
)
),
'internet_connectivity' => array(
'title' => 'Internet Connectivity',
'default' => 'calc_end_to_end',
'charts' => array(
'calc_end_to_end' => array(
'default' => 'calc_end_to_end',
'title' => 'calc End to End'),
'quickcontent_requests' => array(
'default' => 'quickcontent_requests',
'title' => 'Quickcontent Requests')
)
)
);
Again, I need to know both the current category and current chart being accessed. My main nav was
<nav>
<ul>
<?php foreach ($nav as $category => $category_details): ?>
<li class='<?php echo ($current_category == $category) ? null : 'active'; ?>'>
<?php echo $category_details['title']; ?>
</li>
<?php endforeach; ?>
</ul>
</nav>
and the sub-nav was something similar, checking for current_chart instead of current_category.
Before, during parsing, I was exploding $_SERVER['REQUEST_URI'] by /, and breaking the pieces up into $current_category and $current_chart. I was doing this in index.php. Now, I feel this is not in the spirit of the font controller. From references like Symfony 2's docs, it seems like each route should have its own controller. But then, I find myself having to define the current category & chart multiple times, either within the template files themselves (which doesn't seem to be in the spirit of MVC), or in an arbitrary function in the model (which would then have to be called by multiple controllers, which is seemingly redundant).
What is the best practice here?
Update: Here's what my front controller looks like:
// index.php
<?php
// Load libraries
require_once 'model.php';
require_once 'controllers.php';
// Route the request
$uri = str_replace('?'.$_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI']);
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && (!empty($_GET)) && $_GET['action'] == 'get_data') {
$function = $_GET['chart'] . "_data";
$dataJSON = call_user_func($function);
header('Content-type: application/json');
echo $dataJSON;
} elseif ( $uri == '/' ) {
index_action();
} elseif ( $uri == '/25th_monitoring/month_over_month' ) {
month_over_month_action();
} elseif ( $uri == '/25th_monitoring/cdu_tracker' ) {
cdu_tracker_action();
} elseif ( $uri == '/internet_connectivity/intexcalc_end_to_end' ) {
intexcalc_end_to_end_action();
} elseif ( $uri == '/internet_connectivity/quickcontent_requests' ) {
quickcontent_requests_action();
} else {
header('Status: 404 Not Found');
echo '<html><body><h1>Page Not Found</h1></body></html>';
}
?>
It seems like when month_over_month_action() is called, for instance, since the controller knows the current_chart is month_over_month, it should just pass that along. This is where I'm getting tripped up.
There are not "best practices" in this area. Though, there are some, that are more often used then others, and some, that are extremely bad ideas (unfortunately, these two groups tend to overlap).
Routing in MVC
While technically not a part of MVC design pattern, when applied to Web, your application needs to know which controller to initialize and what method(s) to call on it.
Doing explode() to gather this sort of information is a bad idea. It is both hard to debug and maintain. A much better solution is to use regular expressions.
Basically you end up having a list of routes, that contain a regular expression and some fallback values. You loop through that list and on fists match extract the data and apply default values, where data was missing.
This approach also frees you to have much wider possibilities for order of parameters.
To make the solution easier to use, you can also add functionality, that turns a notation string into a regular expression.
For example (taken from some unit-test, that I have):
notation:     test[/:id]
expression: #^/test(:?/(?P<id>[^/\.,;?\n]+))?$#
notation:     [[/:minor]/:major]
expression: #^(:?(:?/(?P<minor>[^/\.,;?\n]+))?/(?P<major>[^/\.,;?\n]+))?$#
notation:     user/:id/:nickname
expression: #^/user/(?P<id>[^/\.,;?\n]+)/(?P<nickname>[^/\.,;?\n]+)$#
While creating such a generator will not be all that easy, it would be quite reusable. IMHO the time invested in making it would be well spent. Also, the use of (?P<key>expression) construct in regular expressions provides you with a very useful array of key-value pairs from the matched route.
Menus and MVC
The decision about which menu item to highlight as active should always be the responsibility of current view instance.
More complicated issue is where the information, that is necessary for making such decision, comes from. There are two source if data that are available to a view instance: information that was passed to view by controller and data, that view requested from model layer.
The controller in MVC takes the user's input and, based on this input, it changes the state of current view and model layer, by passing said values. Controller should not be extracting information from model layer.
IMHO, the better approach in this case is to relay on model layer for information about both menu content and the currently active element in it. While it's possible to both hardcode the currently active element in view and relay on controllers passed informations, MVC is usually used in large scale application, where such practices would end up hurting you.
The view in MVC design pattern is not a dumb template. It's a structure, that is responsible for UI logic. In context of Web that would mean creating a response from multiple template, when necessary, or sometimes just simply sending an HTTP location header.
Well, I had almost the same trouble when was writing CMS-like product.
So I've spend some time trying to figure out how to make this work and keep the code more maintainable and clean as well.
Both CakePHP and Symfony route-mecanisms have a bit inspired me but it wasn't good enough for me.
So I'll try to give you an example of how I do this now.
My question is, what is the best way to do this while staying in the
spirit of MVC?
First, In general, best practice is NOT TO USE procedural approach with MVC in web development at all.
Second, keep the SRP.
From references like Symfony 2's docs, it seems like each route should
have its own controller.
Yeah, that's right approach, but it doesn't mean that another route match can't have the same controller, but different action.
The main disadvantage of your approach (code that you have posted) is that you mix responsibilities and you're not implementing MVC-inspired pattern.
Anyway, MVC in PHP with procedural approach is just a horrible thing.
So, what exactly you are mixing is:
Route mechanism logic (It should be another class) not in a "controller" and route map as well
Request and Response responsibilites (I see that it isn't obvious to you)
Class autoloading
Controller logic
All those "parts" should have one class. Basically, they have to be included in index or bootstrap files.
Also, by doing so:
require_once 'controllers.php';
You automatically include ALL controllers per match (even on no-match). It actually has nothing to do with MVC and leads to memory leaks.
Instead, you should ONLY include and instantiate the controller that matches against URI string.
Also, be careful with include() and require() as they may lead to code duplication if you include the same file somewhere twice.
And also,
} elseif ( $uri == '/' ) {
index_action();
} elseif ( $uri == '/25th_monitoring/month_over_month' ) {
month_over_month_action();
} elseif ( $uri == '/25th_monitoring/cdu_tracker' ) {
cdu_tracker_action();
} elseif ( $uri == '/internet_connectivity/intexcalc_end_to_end' ) {
intexcalc_end_to_end_action();
It's extremely unwise to do a match using if/else/elseif control structures.
Okay, what if you have 50 matches? or even 100? Then you need to write 50 or 100 times to write else/elseif accordingly.
Instead, you should have a map and (an array for example) iterate over it on each HTTP request.
The general approach of using MVC with routing mechanism comes down to:
Matching the request against route map (and keep somewhere parameters if we have them)
Then instantiate appropriate controller
Then pass parameters if we have them
In PHP, the implementation would look like:
File: index.php
<?php
//.....
// -> Load classes here via SPL autoloader or smth like this
// .......
// Then -> define or (better include route map from config dir)
$routes = array(
// -> This should default one
'/' => array('controller' => 'Path_To_home_Controller', 'action' => 'indexAction'),
'/user/:id' => array('controller' => 'Path_to_user_controller', 'action' => 'ViewAction'),
// -> Define the same controller
'/user/:id/edit' => array('controller' => 'Path_to_user_controller', 'action' => 'editAction'),
// -> This match we are going to hanlde in example below:
'/article/:id/:user' => array('controller' => 'SomeArticleController', 'action' => )
);
// -> Also, note you can differently handle this: array('controller' => 'SomeArticleController', 'action' => )
// -> Generally controller key should point to the path of a matched controller, and action should be a method of the controller instance
// -> But if you're still on your own, you can define it the way you want.
// -> Then instantiate common classes
$request = new Request();
$response = new Response();
$router = new Router();
$router->setMap( $routes );
// -> getURI() should return $_SERVER['REQUEST_URI']
$router->setURI( $request->getURI() );
if ( $router->match() !== FALSE ) {
// -> So, let's assume that URI was: '/article/1/foo'
$info = $router->getAll();
print_r ( $info );
/**
* Array( 'parameters' => Array(':id' => '1', ':user' => 'foo'))
* 'controller' => 'Path_To_Controller.php'
* 'action' => 'indexAction'
*/
// -> The next things we are going to do are:
// -> 1. Instantiate the controller
// -> 2. Pass those parameters we got to the indexAction method
$controller = $info['controller'];
// -> Assume that the name of the controller is User_Controller
require ( $controller );
// -> The name of class should also be dynamic, not like this, thats just an example
$controller = new User_Controller();
$arguments = array_values( $info['parameters'] );
call_user_func_array( array($controller, $info['action']), $arguments );
// -> i.e we just called $controller->indexAction('1', 'foo') "dynamically" according to the matched URI string
// -> idealy this should be done like: $response->send( $content ), however
} else {
// -> In order not to show any error
// -> redirect back to "default" controller
$request->redirect('/');
}
In my MVC-inspired applications I do route like this:
(Where I use Dependecy Injection and keep the SRP)
<?php
require (__DIR__ . '/core/System/Auload/Autoloader.php');
Autoloader::boot(); // one method includes all required classes
$map = require(__DIR__ . '/core/System/Route/map.php');
$request = new Request();
$response = new Response();
$mvc = new MVC();
$mvc->setMap( array_values($map) );
// -> array_values($map) isn't accurate here, it'd be a map of controllers
// -> take this as a quick example
$router = new Router();
$router->setMap( $map );
$router->setURI( $request()->getURI() );
if ( $router->match() !== FALSE ) {
// -> Internally, it would automatically find both model and view instances
// -> then do instantiate and invoke appropriate action
$router->run( $mvc );
} else {
// No matches handle here
$request->redirect('/');
}
I found this to be more appropriate for me, after poking around Cake and Symfony.
One thing I want to note:
It's not that easy to find good articles about MVC in PHP. Most of them are just wrong.
(I know how it feels, because first time I've started to learn from them, like so many people do)
So my point here is:
Don't make the same mistake like I did before. If you want to learn MVC, start doing this by reading
Zend Framework or Symfony Tutorials. Even the ones are bit different, the idea behing the scene is the same.
Back to the another part of the question
Again, I need to know both the current category and current chart
being accessed. My main nav was
<nav>
<ul>
<?php foreach($nav as $category => $category_details): ?>
<li class='<?php echo ($current_category == $category) ? null : 'active'; ?>'>
<?php echo $category_details['title']; ?>
</li>
<?php endforeach; ?>
</ul>
</nav>
First of all, don't concatenate the string, instead use printf() like:
<?php echo $category_details['title']; ?>
If you need this to be everywhere (or at least in many different templates), I'd suggest to this to have in a common abstact View class.
For example,
abstract class View
{
// -> bunch of view reusable methods here...
// -> Including this one
final protected function getCategories()
{
return array(
//....
);
}
}
class Customers_View extends View
{
public function render()
{
$categories =& $this->getCategories();
// -> include HTML template and then interate over $categories
}
}

Clearing Validation Error Messages from a Zend Form Element

I have a form element for capturing email addresses. I am using Zend_Validate_EmailAddress on the element, and it generates error messages that aren't very user-friendly.
My first step was to specify new messages that were more user-friendly, but some of the checks simply don't lend themselves to a user-friendly message. I then tried to simply clear those messages after running isValid() on the form and specify my own, but none of the functions I've found will clear the messages.
What I've tried and results
setErrorMessages() - Values set here seem to be ignored altogether
clearErrorMessages() - Seems to be ignored
setErrors() - Adds my message, but leaves the others intact
This is the code that displays the errors in my custom view script:
<?php if ($this->element->hasErrors()): ?>
<?php echo $this->formErrors($this->element->getMessages()); ?>
<?php endif; ?>
MY SOLUTION
I'm awarding Gordon with the answer, because his solution is most complete, but I ended up using the addErrorMessage() function on the element like this:
$element->addValidator('EmailAddress', false, $this->_validate['EmailAddress'])
->addErrorMessage("'%value%' is not a valid email address");
$element->addValidator('Date', false, array('MM/dd/yyyy'))
->addErrorMessage("Date must be in MM/DD/YYYY format");
From the Reference Guide (emphasis mine):
Some developers may wish to provide custom error messages for a validator. The $options argument of the Zend_Form_Element::addValidator() method allows you to do so by providing the key 'messages' and mapping it to an array of key/value pairs for setting the message templates. You will need to know the error codes of the various validation error types for the particular validator.
So you can do:
$form = new Zend_Form;
$username = new Zend_Form_Element_Text('username');
$username->addValidator('regex', false, array(
'/^[a-z]/i',
'messages' => array(
'regexInvalid' => 'foo',
'regexNotMatch' => 'bar',
'regexErrorous' => 'baz'
)
));
$form->addElement($username);
$form->isValid(array('username' => '!foo'));
which will then render 'bar' for the error message, because the regex does not match because it doesnt start with a letter from a-Z.
This is equivalent to using:
$username->setErrorMessages(
array(
'regexNotMatch' => 'The value %value% must start with a-Z',
…
)
);
I've used a different pattern to illustrate how to use the validated value in the pattern.
You can also use setErrors, if you want to delete any default templates, e.g.
$username->setErrors(array('The value must start with a-Z'));
Whatever you do, you have to configure that before validating with isValid. Once the validation is run, the Zend_Form_Element will contain the default error message otherwise. I am not aware of any way to reset that then (though someone might want to correct me on that).
Further quoting the reference guide:
A better option is to use a Zend_Translate_Adapter with your form. Error codes are automatically passed to the adapter by the default Errors decorator; you can then specify your own error message strings by setting up translations for the various error codes of your validators.
All the validation messages can be customized from the file in
http://framework.zend.com/svn/framework/standard/trunk/resources/languages/en
The file should be in APPLICATION_PATH/resources/languages, but can really be placed anywhere as long as you tell Zend_Translate where to find it.
When you define a form element like this
$titel = new Zend_Form_Element_Text ( "titel" );
$titel->setLabel ( "Titel" )->setRequired ( true )
->addValidator ( 'regex', false, array ("/[\pL\pN_\-]+/" ) );
you can specify a error message in your view script
<?php
$form = $this->form;
$errorsMessages =$this->form->getMessages();
?>
<div>
<label>Titel</label> <?php echo $form->titel->renderViewHelper()?>
<?php
if(isset($errorsMessages['titel'])){
echo "<p class='error'>There's something wrong!</p>";
}
?>
</div>
I don't know if this conforms your way but I really like defining my forms this way ;)
One way you can attack it is to create your own custom validator by extending the validator you plan on using and overriding the messages. For instance, looking at Zend_Validate_Alnum, it looks like this:
class Zend_Validate_Alnum extends Zend_Validate_Abstract
{
const INVALID = 'alnumInvalid';
const NOT_ALNUM = 'notAlnum';
const STRING_EMPTY = 'alnumStringEmpty';
[ ... ]
protected $_messageTemplates = array(
self::INVALID => "Invalid type given. String, integer or float expected",
self::NOT_ALNUM => "'%value%' contains characters which are non alphabetic and no digits",
self::STRING_EMPTY => "'%value%' is an empty string",
);
[ ... ]
}
Override the $_messageTemplates array in your own class like this
class My_Validate_Alnum extends Zend_Validate_Alnum
{
protected $_messageTemplates = array(
self::INVALID => "My invalid message",
self::NOT_ALNUM => "foo",
self::STRING_EMPTY => "'%value%' is bar",
);
[ ... ]
}
Then instead of using Zend_Validate_Alnum, use My_Validate_Alnum as your validator. Custom validators are very simple to create.

Categories