Overview

Namespaces

  • CRUDlex

Classes

  • CRUDControllerProvider
  • CRUDData
  • CRUDEntity
  • CRUDEntityDefinition
  • CRUDMySQLData
  • CRUDMySQLDataFactory
  • CRUDServiceProvider
  • CRUDSimpleFilesystemFileProcessor

Interfaces

  • CRUDDataFactoryInterface
  • CRUDFileProcessorInterface
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: /*
  4:  * This file is part of the CRUDlex package.
  5:  *
  6:  * (c) Philip Lehmann-Böhm <philip@philiplb.de>
  7:  *
  8:  * For the full copyright and license information, please view the LICENSE
  9:  * file that was distributed with this source code.
 10:  */
 11: 
 12: namespace CRUDlex;
 13: 
 14: use Silex\Application;
 15: use Silex\ControllerProviderInterface;
 16: use Symfony\Component\HttpFoundation\Response;
 17: use Symfony\Component\HttpFoundation\StreamedResponse;
 18: 
 19: use CRUDlex\CRUDEntity;
 20: 
 21: /**
 22:  * This is the ControllerProvider offering all CRUD pages.
 23:  *
 24:  * It offers this routes:
 25:  *
 26:  * "/resource/static" serving static resources
 27:  *
 28:  * "/{entity}/create" creation page of the entity
 29:  *
 30:  * "/{entity}" list page of the entity
 31:  *
 32:  * "/{entity}/{id}" details page of a single entity instance
 33:  *
 34:  * "/{entity}/{id}/edit" edit page of a single entity instance
 35:  *
 36:  * "/{entity}/{id}/delete" POST only deletion route for an entity instance
 37:  *
 38:  * "/{entity}/{id}/{field}/file" renders a file field of an entity instance
 39:  *
 40:  * "/{entity}/{id}/{field}/delete" POST only deletion of a file field of an entity instance
 41:  */
 42: class CRUDControllerProvider implements ControllerProviderInterface {
 43: 
 44:     /**
 45:      * Generates the not found page.
 46:      *
 47:      * @param Application $app
 48:      * the Silex application
 49:      * @param string $error
 50:      * the cause of the not found error
 51:      *
 52:      * @return Response
 53:      * the rendered not found page with the status code 404
 54:      */
 55:     protected function getNotFoundPage(Application $app, $error) {
 56:         return new Response($app['twig']->render('@crud/notFound.twig', array(
 57:             'error' => $error,
 58:             'crudEntity' => '',
 59:             'layout' => $app['crud.layout']
 60:         )), 404);
 61:     }
 62: 
 63:     /**
 64:      * Delivers the layout for the page in the way it is described in the
 65:      * manual.
 66:      *
 67:      * @param Application $app
 68:      * the Silex application
 69:      * @param string $action
 70:      * the current calling action like "create" or "show"
 71:      * @param string $entity
 72:      * the current calling entity
 73:      *
 74:      * @return string
 75:      * the best fitting layout
 76:      */
 77:     protected function getLayout(Application $app, $action, $entity) {
 78:         if ($app->offsetExists('crud.layout.'.$action.'.'.$entity)) {
 79:             return $app['crud.layout.'.$action.'.'.$entity];
 80:         }
 81:         if ($app->offsetExists('crud.layout.'.$entity)) {
 82:             return $app['crud.layout.'.$entity];
 83:         }
 84:         if ($app->offsetExists('crud.layout.'.$action)) {
 85:             return $app['crud.layout.'.$action];
 86:         }
 87:         return $app['crud.layout'];
 88:     }
 89: 
 90: 
 91:     /**
 92:      * Implements ControllerProviderInterface::connect() connecting this
 93:      * controller.
 94:      *
 95:      * @param Application $app
 96:      * the Application instance of the Silex application
 97:      *
 98:      * @return SilexController\Collection
 99:      * this method is expected to return the used ControllerCollection instance
100:      */
101:     public function connect(Application $app) {
102:         if ($app->offsetExists('twig.loader.filesystem')) {
103:             $app['twig.loader.filesystem']->addPath(__DIR__ . '/../views/', 'crud');
104:         }
105: 
106:         if (!$app->offsetExists('crud.layout')) {
107:             $app['crud.layout'] = '@crud/layout.twig';
108:         }
109: 
110:         $class = get_class($this);
111:         $factory = $app['controllers_factory'];
112:         $factory->get('/resource/static', $class.'::staticFile')
113:                 ->bind('static');
114:         $factory->match('/{entity}/create', $class.'::create')
115:                 ->bind('crudCreate');
116:         $factory->match('/{entity}', $class.'::showList')
117:                 ->bind('crudList');
118:         $factory->match('/{entity}/{id}', $class.'::show')
119:                 ->bind('crudShow');
120:         $factory->match('/{entity}/{id}/edit', $class.'::edit')
121:                 ->bind('crudEdit');
122:         $factory->post('/{entity}/{id}/delete', $class.'::delete')
123:                 ->bind('crudDelete');
124:         $factory->match('/{entity}/{id}/{field}/file', $class.'::renderFile')
125:                 ->bind('crudRenderFile');
126:         $factory->post('/{entity}/{id}/{field}/delete', $class.'::deleteFile')
127:                 ->bind('crudDeleteFile');
128:         return $factory;
129:     }
130: 
131:     /**
132:      * The controller for the "create" action.
133:      *
134:      * @param Application $app
135:      * the Silex application
136:      * @param string $entity
137:      * the current entity
138:      *
139:      * @return Response
140:      * the HTTP response of this action
141:      */
142:     public function create(Application $app, $entity) {
143:         $crudData = $app['crud']->getData($entity);
144:         if (!$crudData) {
145:             return $this->getNotFoundPage($app, $app['crud']->translate('entityNotFound'));
146:         }
147: 
148:         $errors = array();
149:         $instance = $crudData->createEmpty();
150:         $definition = $crudData->getDefinition();
151:         $fields = $definition->getEditableFieldNames();
152: 
153:         if ($app['request']->getMethod() == 'POST') {
154:             foreach ($fields as $field) {
155:                 if ($definition->getType($field) == 'file') {
156:                     $file = $app['request']->files->get($field);
157:                     if ($file) {
158:                         $instance->set($field, $file->getClientOriginalName());
159:                     }
160:                 } else {
161:                     $instance->set($field, $app['request']->get($field));
162:                 }
163:             }
164:             $validation = $instance->validate($crudData);
165:             if (!$validation['valid']) {
166:                 $errors = $validation['errors'];
167:                 $app['session']->getFlashBag()->add('danger', $app['crud']->translate('create.error'));
168:             } else {
169:                 $crudData->create($instance);
170:                 $id = $instance->get('id');
171:                 $crudData->createFiles($app['request'], $instance, $entity);
172: 
173:                 $app['session']->getFlashBag()->add('success', $app['crud']->translate('create.success', array($crudData->getDefinition()->getLabel(), $id)));
174:                 return $app->redirect($app['url_generator']->generate('crudShow', array('entity' => $entity, 'id' => $id)));
175:             }
176:         }
177: 
178:         $definition = $crudData->getDefinition();
179: 
180:         return $app['twig']->render('@crud/form.twig', array(
181:             'crudEntity' => $entity,
182:             'crudData' => $crudData,
183:             'entity' => $instance,
184:             'mode' => 'create',
185:             'errors' => $errors,
186:             'layout' => $this->getLayout($app, 'create', $entity)
187:         ));
188:     }
189: 
190:     /**
191:      * The controller for the "show list" action.
192:      *
193:      * @param Application $app
194:      * the Silex application
195:      * @param string $entity
196:      * the current entity
197:      *
198:      * @return Response
199:      * the HTTP response of this action or 404 on invalid input
200:      */
201:     public function showList(Application $app, $entity) {
202:         $crudData = $app['crud']->getData($entity);
203:         if (!$crudData) {
204:             return $this->getNotFoundPage($app, $app['crud']->translate('entityNotFound'));
205:         }
206:         $entitiesRaw = $crudData->listEntries();
207:         $entities = array();
208:         foreach ($entitiesRaw as $curEntity) {
209:             $crudData->fetchReferences($curEntity);
210:             $entities[] = $curEntity;
211:         }
212:         $definition = $crudData->getDefinition();
213:         return $app['twig']->render('@crud/list.twig', array(
214:             'crudEntity' => $entity,
215:             'definition' => $definition,
216:             'entities' => $entities,
217:             'layout' => $this->getLayout($app, 'list', $entity)
218:         ));
219:     }
220: 
221:     /**
222:      * The controller for the "show" action.
223:      *
224:      * @param Application $app
225:      * the Silex application
226:      * @param string $entity
227:      * the current entity
228:      * @param string $id
229:      * the instance id to show
230:      *
231:      * @return Response
232:      * the HTTP response of this action or 404 on invalid input
233:      */
234:     public function show(Application $app, $entity, $id) {
235:         $crudData = $app['crud']->getData($entity);
236:         if (!$crudData) {
237:             return $this->getNotFoundPage($app, $app['crud']->translate('entityNotFound'));
238:         }
239:         $instance = $crudData->get($id);
240:         $crudData->fetchReferences($instance);
241:         if (!$instance) {
242:             return $this->getNotFoundPage($app, $app['crud']->translate('instanceNotFound'));
243:         }
244:         $definition = $crudData->getDefinition();
245: 
246:         $childrenLabelFields = $definition->getChildrenLabelFields();
247:         $children = array();
248:         if (count($childrenLabelFields) > 0) {
249:             foreach ($definition->getChildren() as $child) {
250:                 $childField = $child[1];
251:                 $childEntity = $child[2];
252:                 $childLabelField = key_exists($childEntity, $childrenLabelFields) ? $childrenLabelFields[$childEntity] : 'id';
253:                 $childCrud = $app['crud']->getData($childEntity);
254:                 $children[] = array(
255:                     $childCrud->getDefinition()->getLabel(),
256:                     $childEntity,
257:                     $childLabelField,
258:                     $childCrud->listEntries(array($childField => $instance->get('id')))
259:                 );
260:             }
261:         }
262: 
263:         return $app['twig']->render('@crud/show.twig', array(
264:             'crudEntity' => $entity,
265:             'entity' => $instance,
266:             'children' => $children,
267:             'layout' => $this->getLayout($app, 'show', $entity)
268:         ));
269:     }
270: 
271:     /**
272:      * The controller for the "edit" action.
273:      *
274:      * @param Application $app
275:      * the Silex application
276:      * @param string $entity
277:      * the current entity
278:      * @param string $id
279:      * the instance id to edit
280:      *
281:      * @return Response
282:      * the HTTP response of this action or 404 on invalid input
283:      */
284:     public function edit(Application $app, $entity, $id) {
285:         $crudData = $app['crud']->getData($entity);
286:         if (!$crudData) {
287:             return $this->getNotFoundPage($app, $app['crud']->translate('entityNotFound'));
288:         }
289:         $instance = $crudData->get($id);
290:         if (!$instance) {
291:             return $this->getNotFoundPage($app, $app['crud']->translate('instanceNotFound'));
292:         }
293: 
294:         $definition = $crudData->getDefinition();
295: 
296:         $errors = array();
297:         $fields = $definition->getEditableFieldNames();
298: 
299: 
300:         if ($app['request']->getMethod() == 'POST') {
301:             foreach ($fields as $field) {
302:                 if ($definition->getType($field) == 'file') {
303:                     $file = $app['request']->files->get($field);
304:                     if ($file) {
305:                         $instance->set($field, $file->getClientOriginalName());
306:                     }
307:                 } else {
308:                     $instance->set($field, $app['request']->get($field));
309:                 }
310:             }
311:             $validation = $instance->validate($crudData);
312:             if (!$validation['valid']) {
313:                 $app['session']->getFlashBag()->add('danger', $app['crud']->translate('edit.error'));
314:                 $errors = $validation['errors'];
315:             } else {
316:                 $crudData->update($instance);
317:                 $crudData->updateFiles($app['request'], $instance, $entity);
318:                 $app['session']->getFlashBag()->add('success', $app['crud']->translate('edit.success', array($crudData->getDefinition()->getLabel(), $id)));
319:                 return $app->redirect($app['url_generator']->generate('crudShow', array('entity' => $entity, 'id' => $id)));
320:             }
321:         }
322: 
323:         return $app['twig']->render('@crud/form.twig', array(
324:             'crudEntity' => $entity,
325:             'crudData' => $crudData,
326:             'entity' => $instance,
327:             'mode' => 'edit',
328:             'errors' => $errors,
329:             'layout' => $this->getLayout($app, 'edit', $entity)
330:         ));
331:     }
332: 
333:     /**
334:      * The controller for the "delete" action.
335:      *
336:      * @param Application $app
337:      * the Silex application
338:      * @param string $entity
339:      * the current entity
340:      * @param string $id
341:      * the instance id to delete
342:      *
343:      * @return Response
344:      * redirects to the entity list page or 404 on invalid input
345:      */
346:     public function delete(Application $app, $entity, $id) {
347:         $crudData = $app['crud']->getData($entity);
348:         if (!$crudData) {
349:             return $this->getNotFoundPage($app, $app['crud']->translate('entityNotFound'));
350:         }
351:         $instance = $crudData->get($id);
352:         if (!$instance) {
353:             return $this->getNotFoundPage($app, $app['crud']->translate('instanceNotFound'));
354:         }
355: 
356:         $crudData->deleteFiles($instance, $entity);
357:         $deleted = $crudData->delete($id);
358:         if ($deleted) {
359:             $app['session']->getFlashBag()->add('success', $app['crud']->translate('delete.success', array($crudData->getDefinition()->getLabel())));
360:             return $app->redirect($app['url_generator']->generate('crudList', array('entity' => $entity)));
361:         } else {
362:             $app['session']->getFlashBag()->add('danger', $app['crud']->translate('delete.error', array($crudData->getDefinition()->getLabel())));
363:             return $app->redirect($app['url_generator']->generate('crudShow', array('entity' => $entity, 'id' => $id)));
364:         }
365:     }
366: 
367:     /**
368:      * The controller for the "render file" action.
369:      *
370:      * @param Application $app
371:      * the Silex application
372:      * @param string $entity
373:      * the current entity
374:      * @param string $id
375:      * the instance id
376:      * @param string $field
377:      * the field of the file to render of the instance
378:      *
379:      * @return Response
380:      * the rendered file
381:      */
382:     public function renderFile(Application $app, $entity, $id, $field) {
383:         $crudData = $app['crud']->getData($entity);
384:         if (!$crudData) {
385:             return $this->getNotFoundPage($app, $app['crud']->translate('entityNotFound'));
386:         }
387:         $instance = $crudData->get($id);
388:         $definition = $crudData->getDefinition();
389:         if (!$instance) {
390:             return $this->getNotFoundPage($app, $app['crud']->translate('instanceNotFound'));
391:         }
392:         if ($definition->getType($field) != 'file' || !$instance->get($field)) {
393:             return $this->getNotFoundPage($app, $app['crud']->translate('instanceNotFound'));
394:         }
395:         return $crudData->renderFile($instance, $entity, $field);
396:     }
397: 
398:     /**
399:      * The controller for the "delete file" action.
400:      *
401:      * @param Application $app
402:      * the Silex application
403:      * @param string $entity
404:      * the current entity
405:      * @param string $id
406:      * the instance id
407:      * @param string $field
408:      * the field of the file to delete of the instance
409:      *
410:      * @return Response
411:      * redirects to the instance details page or 404 on invalid input
412:      */
413:     public function deleteFile(Application $app, $entity, $id, $field) {
414:         $crudData = $app['crud']->getData($entity);
415:         if (!$crudData) {
416:             return $this->getNotFoundPage($app, $app['crud']->translate('entityNotFound'));
417:         }
418:         $instance = $crudData->get($id);
419:         if (!$instance) {
420:             return $this->getNotFoundPage($app, $app['crud']->translate('instanceNotFound'));
421:         }
422:         if (!$crudData->getDefinition()->isRequired($field)) {
423:             $crudData->deleteFile($instance, $entity, $field);
424:             $instance->set($field, '');
425:             $crudData->update($instance);
426:             $app['session']->getFlashBag()->add('success', $app['crud']->translate('file.deleted'));
427:         } else {
428:             $app['session']->getFlashBag()->add('danger', $app['crud']->translate('file.notdeleted'));
429:         }
430:         return $app->redirect($app['url_generator']->generate('crudShow', array('entity' => $entity, 'id' => $id)));
431:     }
432: 
433:     /**
434:      * The controller for serving static files.
435:      *
436:      * @param Application $app
437:      * the Silex application
438:      *
439:      * @return Response
440:      * redirects to the instance details page or 404 on invalid input
441:      */
442:     public function staticFile(Application $app) {
443:         $fileParam = $app['request']->get('file');
444:         if (!$fileParam) {
445:             return $this->getNotFoundPage($app, $app['crud']->translate('resourceNotFound'));
446:         }
447: 
448:         $file = __DIR__.'/../static/'.$fileParam;
449:         if (!file_exists($file)) {
450:             return $this->getNotFoundPage($app, $app['crud']->translate('resourceNotFound'));
451:         }
452: 
453:         $extension = pathinfo($file, PATHINFO_EXTENSION);
454:         $mimeType = '';
455:         if (strtolower($extension) === 'css') {
456:             $mimeType = 'text/css';
457:         } else {
458:             $finfo = finfo_open(FILEINFO_MIME_TYPE);
459:             $mimeType = finfo_file($finfo, $file);
460:             finfo_close($finfo);
461:         }
462: 
463:         $size = filesize($file);
464: 
465:         $response = new StreamedResponse(function () use ($file) {
466:             set_time_limit(0);
467:             $handle = fopen($file,"rb");
468:             if ($handle !== false) {
469:                 $chunkSize = 1024 * 1024;
470:                 while (!feof($handle)) {
471:                     $buffer = fread($handle, $chunkSize);
472:                     echo $buffer;
473:                     flush();
474:                 }
475:                 fclose($handle);
476:             }
477:         }, 200, array(
478:             'Content-Type' => $mimeType,
479:             'Content-Disposition' => 'attachment; filename="'.basename($file).'"',
480:             'Content-length' => $size
481:         ));
482:         $response->send();
483: 
484:         return $response;
485:     }
486: }
487: 
CRUDlex API API documentation generated by ApiGen